diaspora-vines 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/Rakefile +23 -0
- data/bin/vines +4 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3895 -0
- data/conf/config.rb +42 -0
- data/lib/vines/cli.rb +132 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/ldap.rb +38 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config/host.rb +125 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +223 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/message.rb +40 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/presence.rb +141 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/storage/ldap.rb +71 -0
- data/lib/vines/storage/local.rb +139 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +138 -0
- data/lib/vines/storage.rb +239 -0
- data/lib/vines/store.rb +110 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/parser.rb +79 -0
- data/lib/vines/stream/sasl.rb +128 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +6 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/lib/vines.rb +203 -0
- data/test/cluster/publisher_test.rb +57 -0
- data/test/cluster/sessions_test.rb +47 -0
- data/test/cluster/subscriber_test.rb +109 -0
- data/test/config/host_test.rb +369 -0
- data/test/config/pubsub_test.rb +187 -0
- data/test/config_test.rb +732 -0
- data/test/contact_test.rb +102 -0
- data/test/error_test.rb +58 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +147 -0
- data/test/kit_test.rb +31 -0
- data/test/router_test.rb +243 -0
- data/test/stanza/iq/disco_info_test.rb +78 -0
- data/test/stanza/iq/disco_items_test.rb +49 -0
- data/test/stanza/iq/private_storage_test.rb +184 -0
- data/test/stanza/iq/roster_test.rb +229 -0
- data/test/stanza/iq/session_test.rb +25 -0
- data/test/stanza/iq/vcard_test.rb +146 -0
- data/test/stanza/iq/version_test.rb +64 -0
- data/test/stanza/iq_test.rb +70 -0
- data/test/stanza/message_test.rb +126 -0
- data/test/stanza/presence/probe_test.rb +50 -0
- data/test/stanza/presence/subscribe_test.rb +83 -0
- data/test/stanza/pubsub/create_test.rb +116 -0
- data/test/stanza/pubsub/delete_test.rb +169 -0
- data/test/stanza/pubsub/publish_test.rb +309 -0
- data/test/stanza/pubsub/subscribe_test.rb +205 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
- data/test/stanza_test.rb +85 -0
- data/test/storage/ldap_test.rb +201 -0
- data/test/storage/local_test.rb +59 -0
- data/test/storage/mock_redis.rb +97 -0
- data/test/storage/null_test.rb +29 -0
- data/test/storage/storage_tests.rb +182 -0
- data/test/storage_test.rb +85 -0
- data/test/store_test.rb +130 -0
- data/test/stream/client/auth_test.rb +137 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/client/session_test.rb +27 -0
- data/test/stream/component/handshake_test.rb +52 -0
- data/test/stream/component/ready_test.rb +103 -0
- data/test/stream/component/start_test.rb +39 -0
- data/test/stream/http/auth_test.rb +70 -0
- data/test/stream/http/ready_test.rb +86 -0
- data/test/stream/http/request_test.rb +209 -0
- data/test/stream/http/sessions_test.rb +49 -0
- data/test/stream/http/start_test.rb +50 -0
- data/test/stream/parser_test.rb +122 -0
- data/test/stream/sasl_test.rb +195 -0
- data/test/stream/server/auth_test.rb +61 -0
- data/test/stream/server/outbound/auth_test.rb +75 -0
- data/test/stream/server/ready_test.rb +98 -0
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +44 -0
- data/test/user_test.rb +96 -0
- data/vines.gemspec +30 -0
- metadata +387 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Auth < State
|
7
|
+
NS = NAMESPACES[:sasl]
|
8
|
+
MECHANISM = 'mechanism'.freeze
|
9
|
+
AUTH = 'auth'.freeze
|
10
|
+
PLAIN = 'PLAIN'.freeze
|
11
|
+
EXTERNAL = 'EXTERNAL'.freeze
|
12
|
+
SUCCESS = %Q{<success xmlns="#{NS}"/>}.freeze
|
13
|
+
MAX_AUTH_ATTEMPTS = 3
|
14
|
+
|
15
|
+
def initialize(stream, success=BindRestart)
|
16
|
+
super
|
17
|
+
@attempts = 0
|
18
|
+
@sasl = SASL.new(stream)
|
19
|
+
end
|
20
|
+
|
21
|
+
def node(node)
|
22
|
+
raise StreamErrors::NotAuthorized unless auth?(node)
|
23
|
+
if node.text.empty?
|
24
|
+
send_auth_fail(SaslErrors::MalformedRequest.new)
|
25
|
+
elsif stream.authentication_mechanisms.include?(node[MECHANISM])
|
26
|
+
case node[MECHANISM]
|
27
|
+
when PLAIN then plain_auth(node)
|
28
|
+
when EXTERNAL then external_auth(node)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
send_auth_fail(SaslErrors::InvalidMechanism.new)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def auth?(node)
|
38
|
+
node.name == AUTH && namespace(node) == NS
|
39
|
+
end
|
40
|
+
|
41
|
+
def plain_auth(node)
|
42
|
+
stream.user = @sasl.plain_auth(node.text)
|
43
|
+
send_auth_success
|
44
|
+
rescue => e
|
45
|
+
send_auth_fail(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
def external_auth(node)
|
49
|
+
@sasl.external_auth(node.text)
|
50
|
+
send_auth_success
|
51
|
+
rescue => e
|
52
|
+
send_auth_fail(e)
|
53
|
+
stream.write('</stream:stream>')
|
54
|
+
stream.close_connection_after_writing
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_auth_success
|
58
|
+
stream.write(SUCCESS)
|
59
|
+
stream.reset
|
60
|
+
advance
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_auth_fail(condition)
|
64
|
+
@attempts += 1
|
65
|
+
if @attempts >= MAX_AUTH_ATTEMPTS
|
66
|
+
stream.error(StreamErrors::PolicyViolation.new("max authentication attempts exceeded"))
|
67
|
+
else
|
68
|
+
stream.error(condition)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class AuthRestart < State
|
7
|
+
def initialize(stream, success=Auth)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('mechanisms') do |parent|
|
17
|
+
parent.default_namespace = NAMESPACES[:sasl]
|
18
|
+
stream.authentication_mechanisms.each do |name|
|
19
|
+
parent << doc.create_element('mechanism', name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
stream.write(features)
|
24
|
+
advance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Bind < State
|
7
|
+
NS = NAMESPACES[:bind]
|
8
|
+
MAX_ATTEMPTS = 5
|
9
|
+
|
10
|
+
def initialize(stream, success=Ready)
|
11
|
+
super
|
12
|
+
@attempts = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def node(node)
|
16
|
+
@attempts += 1
|
17
|
+
raise StreamErrors::NotAuthorized unless bind?(node)
|
18
|
+
raise StreamErrors::PolicyViolation.new('max bind attempts reached') if @attempts > MAX_ATTEMPTS
|
19
|
+
raise StanzaErrors::ResourceConstraint.new(node, 'wait') if resource_limit_reached?
|
20
|
+
|
21
|
+
stream.bind!(resource(node))
|
22
|
+
doc = Document.new
|
23
|
+
result = doc.create_element('iq', 'id' => node['id'], 'type' => 'result') do |el|
|
24
|
+
el << doc.create_element('bind') do |bind|
|
25
|
+
bind.default_namespace = NS
|
26
|
+
bind << doc.create_element('jid', stream.user.jid.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
stream.write(result)
|
30
|
+
send_empty_features
|
31
|
+
advance
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Write the final <stream:features/> element to the stream, indicating
|
37
|
+
# stream negotiation is complete and the client is cleared to send
|
38
|
+
# stanzas.
|
39
|
+
def send_empty_features
|
40
|
+
stream.write('<stream:features/>')
|
41
|
+
end
|
42
|
+
|
43
|
+
def bind?(node)
|
44
|
+
node.name == 'iq' && node['type'] == 'set' && node.xpath('ns:bind', 'ns' => NS).any?
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource(node)
|
48
|
+
el = node.xpath('ns:bind/ns:resource', 'ns' => NS).first
|
49
|
+
resource = el ? el.text.strip : ''
|
50
|
+
generate = resource.empty? || !resource_valid?(resource) || resource_used?(resource)
|
51
|
+
generate ? Kit.uuid : resource
|
52
|
+
end
|
53
|
+
|
54
|
+
def resource_limit_reached?
|
55
|
+
used = stream.connected_resources(stream.user.jid.bare).size
|
56
|
+
used >= stream.max_resources_per_account
|
57
|
+
end
|
58
|
+
|
59
|
+
def resource_used?(resource)
|
60
|
+
stream.available_resources(stream.user.jid).any? do |c|
|
61
|
+
c.user.jid.resource == resource
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def resource_valid?(resource)
|
66
|
+
jid = stream.user.jid
|
67
|
+
JID.new(jid.node, jid.domain, resource) rescue false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class BindRestart < State
|
7
|
+
def initialize(stream, success=Bind)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
|
17
|
+
end
|
18
|
+
stream.write(features)
|
19
|
+
advance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Ready < State
|
7
|
+
def node(node)
|
8
|
+
stanza = to_stanza(node)
|
9
|
+
raise StreamErrors::UnsupportedStanzaType unless stanza
|
10
|
+
stanza.validate_to
|
11
|
+
stanza.validate_from
|
12
|
+
stanza.process
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
# A Session tracks the state of a client stream over its lifetime from
|
7
|
+
# negotiation to processing stanzas to shutdown. By disconnecting the
|
8
|
+
# stream's state from the stream, we can allow multiple TCP connections
|
9
|
+
# to access one logical session (e.g. HTTP streams).
|
10
|
+
class Session
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
attr_accessor :domain, :user
|
14
|
+
attr_reader :id, :last_broadcast_presence, :state
|
15
|
+
|
16
|
+
def initialize(stream)
|
17
|
+
@stream = stream
|
18
|
+
@id = Kit.uuid
|
19
|
+
@config = stream.config
|
20
|
+
@state = Client::Start.new(stream)
|
21
|
+
@available = false
|
22
|
+
@domain = nil
|
23
|
+
@last_broadcast_presence = nil
|
24
|
+
@requested_roster = false
|
25
|
+
@unbound = false
|
26
|
+
@user = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(session)
|
30
|
+
session.is_a?(Session) ? self.id <=> session.id : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :eql? :==
|
34
|
+
|
35
|
+
def hash
|
36
|
+
@id.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def advance(state)
|
40
|
+
@state = state
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns true if this client has properly authenticated with
|
44
|
+
# the server.
|
45
|
+
def authenticated?
|
46
|
+
!@user.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Notify the session that the client has sent an initial presence
|
50
|
+
# broadcast and is now considered to be an "available" resource.
|
51
|
+
# Available resources are sent presence subscription stanzas.
|
52
|
+
def available!
|
53
|
+
@available = true
|
54
|
+
save_to_cluster
|
55
|
+
end
|
56
|
+
|
57
|
+
# An available resource has sent initial presence and can
|
58
|
+
# receive presence subscription requests.
|
59
|
+
def available?
|
60
|
+
@available && connected?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Complete resource binding with the given resource name, provided by the
|
64
|
+
# client or generated by the server. Once resource binding is completed,
|
65
|
+
# the stream is considered to be "connected" and ready for traffic.
|
66
|
+
def bind!(resource)
|
67
|
+
@user.jid.resource = resource
|
68
|
+
router << self
|
69
|
+
save_to_cluster
|
70
|
+
end
|
71
|
+
|
72
|
+
# A connected resource has authenticated and bound a resource
|
73
|
+
# identifier.
|
74
|
+
def connected?
|
75
|
+
!@unbound && authenticated? && !@user.jid.bare?
|
76
|
+
end
|
77
|
+
|
78
|
+
# An interested resource has requested its roster and can
|
79
|
+
# receive roster pushes.
|
80
|
+
def interested?
|
81
|
+
@requested_roster && connected?
|
82
|
+
end
|
83
|
+
|
84
|
+
def last_broadcast_presence=(node)
|
85
|
+
@last_broadcast_presence = node
|
86
|
+
save_to_cluster
|
87
|
+
end
|
88
|
+
|
89
|
+
def ready?
|
90
|
+
@state.class == Client::Ready
|
91
|
+
end
|
92
|
+
|
93
|
+
# Notify the session that the client has requested its roster and is now
|
94
|
+
# considered to be an "interested" resource. Interested resources are sent
|
95
|
+
# roster pushes when changes are made to their contacts.
|
96
|
+
def requested_roster!
|
97
|
+
@requested_roster = true
|
98
|
+
save_to_cluster
|
99
|
+
end
|
100
|
+
|
101
|
+
def stream_type
|
102
|
+
:client
|
103
|
+
end
|
104
|
+
|
105
|
+
def write(data)
|
106
|
+
@stream.write(data)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Called by the stream when it's disconnected from the client. The stream
|
110
|
+
# passes itself to this method in case multiple streams are accessing this
|
111
|
+
# session (e.g. BOSH/HTTP).
|
112
|
+
def unbind!(stream)
|
113
|
+
router.delete(self)
|
114
|
+
delete_from_cluster
|
115
|
+
unsubscribe_pubsub
|
116
|
+
@unbound = true
|
117
|
+
@available = false
|
118
|
+
broadcast_unavailable
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns streams for available resources to which this user
|
122
|
+
# has successfully subscribed.
|
123
|
+
def available_subscribed_to_resources
|
124
|
+
subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
|
125
|
+
router.available_resources(subscribed, @user.jid)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns streams for available resources that are subscribed
|
129
|
+
# to this user's presence updates.
|
130
|
+
def available_subscribers
|
131
|
+
subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
|
132
|
+
router.available_resources(subscribed, @user.jid)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns contacts hosted at remote servers to which this user has
|
136
|
+
# successfully subscribed.
|
137
|
+
def remote_subscribed_to_contacts
|
138
|
+
@user.subscribed_to_contacts.reject do |c|
|
139
|
+
@config.local_jid?(c.jid)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns contacts hosted at remote servers that are subscribed
|
144
|
+
# to this user's presence updates.
|
145
|
+
def remote_subscribers(to=nil)
|
146
|
+
jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
|
147
|
+
@user.subscribed_from_contacts.reject do |c|
|
148
|
+
@config.local_jid?(c.jid) || (jid && c.jid.bare != jid)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def broadcast_unavailable
|
155
|
+
return unless authenticated?
|
156
|
+
Fiber.new do
|
157
|
+
broadcast(unavailable, available_subscribers)
|
158
|
+
broadcast(unavailable, router.available_resources(@user.jid, @user.jid))
|
159
|
+
remote_subscribers.each do |contact|
|
160
|
+
node = unavailable
|
161
|
+
node['to'] = contact.jid.bare.to_s
|
162
|
+
router.route(node) rescue nil # ignore RemoteServerNotFound
|
163
|
+
end
|
164
|
+
end.resume
|
165
|
+
end
|
166
|
+
|
167
|
+
def unavailable
|
168
|
+
doc = Nokogiri::XML::Document.new
|
169
|
+
doc.create_element('presence',
|
170
|
+
'from' => @user.jid.to_s,
|
171
|
+
'type' => 'unavailable')
|
172
|
+
end
|
173
|
+
|
174
|
+
def broadcast(stanza, recipients)
|
175
|
+
recipients.each do |recipient|
|
176
|
+
stanza['to'] = recipient.user.jid.to_s
|
177
|
+
recipient.write(stanza)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def router
|
182
|
+
@config.router
|
183
|
+
end
|
184
|
+
|
185
|
+
def save_to_cluster
|
186
|
+
if @config.cluster?
|
187
|
+
@config.cluster.save_session(@user.jid, to_hash)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete_from_cluster
|
192
|
+
if connected? && @config.cluster?
|
193
|
+
@config.cluster.delete_session(@user.jid)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def unsubscribe_pubsub
|
198
|
+
if connected?
|
199
|
+
@config.vhost(@user.jid.domain).unsubscribe_pubsub(@user.jid)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def to_hash
|
204
|
+
presence = @last_broadcast_presence ? @last_broadcast_presence.to_s : nil
|
205
|
+
{available: @available, interested: @requested_roster, presence: presence.to_s}
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Start < State
|
7
|
+
def initialize(stream, success=TLS)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('starttls') do |tls|
|
17
|
+
tls.default_namespace = NAMESPACES[:tls]
|
18
|
+
tls << doc.create_element('required')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
stream.write(features)
|
22
|
+
advance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class TLS < State
|
7
|
+
NS = NAMESPACES[:tls]
|
8
|
+
PROCEED = %Q{<proceed xmlns="#{NS}"/>}.freeze
|
9
|
+
FAILURE = %Q{<failure xmlns="#{NS}"/>}.freeze
|
10
|
+
STARTTLS = 'starttls'.freeze
|
11
|
+
|
12
|
+
def initialize(stream, success=AuthRestart)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def node(node)
|
17
|
+
raise StreamErrors::NotAuthorized unless starttls?(node)
|
18
|
+
if stream.encrypt?
|
19
|
+
stream.write(PROCEED)
|
20
|
+
stream.encrypt
|
21
|
+
stream.reset
|
22
|
+
advance
|
23
|
+
else
|
24
|
+
stream.write(FAILURE)
|
25
|
+
stream.write('</stream:stream>')
|
26
|
+
stream.close_connection_after_writing
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def starttls?(node)
|
33
|
+
node.name == STARTTLS && namespace(node) == NS
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
# Implements the XMPP protocol for client-to-server (c2s) streams. This
|
6
|
+
# serves connected streams using the jabber:client namespace.
|
7
|
+
class Client < Stream
|
8
|
+
MECHANISMS = %w[PLAIN].freeze
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
super
|
12
|
+
@session = Client::Session.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Delegate behavior to the session that's storing our stream state.
|
16
|
+
def method_missing(name, *args)
|
17
|
+
@session.send(name, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
%w[advance domain state user user=].each do |name|
|
21
|
+
define_method name do |*args|
|
22
|
+
@session.send(name, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
%w[max_stanza_size max_resources_per_account].each do |name|
|
27
|
+
define_method name do |*args|
|
28
|
+
config[:client].send(name, *args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return an array of allowed authentication mechanisms advertised as
|
33
|
+
# client stream features.
|
34
|
+
def authentication_mechanisms
|
35
|
+
MECHANISMS
|
36
|
+
end
|
37
|
+
|
38
|
+
def ssl_handshake_completed
|
39
|
+
if get_peer_cert
|
40
|
+
close_connection unless cert_domain_matches?(@session.domain)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def unbind
|
45
|
+
@session.unbind!(self)
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def start(node)
|
50
|
+
to, from = %w[to from].map {|a| node[a] }
|
51
|
+
@session.domain = to unless @session.domain
|
52
|
+
send_stream_header(from)
|
53
|
+
raise StreamErrors::NotAuthorized if domain_change?(to)
|
54
|
+
raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
|
55
|
+
raise StreamErrors::ImproperAddressing unless valid_address?(@session.domain)
|
56
|
+
raise StreamErrors::HostUnknown unless config.vhost?(@session.domain)
|
57
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:client]
|
58
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# The +to+ domain address set on the initial stream header must not change
|
64
|
+
# during stream restarts. This prevents a user from authenticating in one
|
65
|
+
# domain, then using a stream in a different domain.
|
66
|
+
def domain_change?(to)
|
67
|
+
to != @session.domain
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_stream_header(to)
|
71
|
+
attrs = {
|
72
|
+
'xmlns' => NAMESPACES[:client],
|
73
|
+
'xmlns:stream' => NAMESPACES[:stream],
|
74
|
+
'xml:lang' => 'en',
|
75
|
+
'id' => Kit.uuid,
|
76
|
+
'from' => @session.domain,
|
77
|
+
'version' => '1.0'
|
78
|
+
}
|
79
|
+
attrs['to'] = to if to
|
80
|
+
write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Component
|
6
|
+
class Handshake < State
|
7
|
+
def initialize(stream, success=Ready)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless handshake?(node)
|
13
|
+
stream.write('<handshake/>')
|
14
|
+
stream.router << stream
|
15
|
+
advance
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def handshake?(node)
|
21
|
+
node.name == 'handshake' && node.text == stream.secret
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Component
|
6
|
+
class Ready < State
|
7
|
+
def node(node)
|
8
|
+
stanza = to_stanza(node)
|
9
|
+
raise StreamErrors::UnsupportedStanzaType unless stanza
|
10
|
+
to, from = stanza.validate_to, stanza.validate_from
|
11
|
+
raise StreamErrors::ImproperAddressing unless to && from
|
12
|
+
raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
|
13
|
+
stream.user = User.new(jid: from)
|
14
|
+
if stanza.local? || stanza.to_pubsub_domain?
|
15
|
+
stanza.process
|
16
|
+
else
|
17
|
+
stanza.route
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Component
|
6
|
+
class Start < State
|
7
|
+
def initialize(stream, success=Handshake)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
advance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|