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,157 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Http < Client
|
6
|
+
attr_accessor :session
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
super
|
10
|
+
@session = Http::Session.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Override +Stream#create_parser+ to provide an HTTP parser rather than
|
14
|
+
# a Nokogiri XML parser.
|
15
|
+
def create_parser
|
16
|
+
@parser = ::Http::Parser.new.tap do |p|
|
17
|
+
body = ''
|
18
|
+
p.on_body = proc {|data| body << data }
|
19
|
+
p.on_message_complete = proc {
|
20
|
+
process_request(Request.new(self, @parser, body))
|
21
|
+
body = ''
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# If the session ID is valid, switch this stream's session to the new
|
27
|
+
# ID and return true. Some clients, like Google Chrome, reuse one stream
|
28
|
+
# for multiple sessions.
|
29
|
+
def valid_session?(sid)
|
30
|
+
if session = Sessions[sid]
|
31
|
+
@session = session
|
32
|
+
end
|
33
|
+
!!session
|
34
|
+
end
|
35
|
+
|
36
|
+
%w[max_stanza_size max_resources_per_account bind root].each do |name|
|
37
|
+
define_method name do |*args|
|
38
|
+
config[:http].send(name, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_request(request)
|
43
|
+
if request.path == self.bind && request.options?
|
44
|
+
request.reply_to_options
|
45
|
+
elsif request.path == self.bind
|
46
|
+
body = Nokogiri::XML(request.body).root
|
47
|
+
if session = Sessions[body['sid']]
|
48
|
+
@session = session
|
49
|
+
else
|
50
|
+
@session = Http::Session.new(self)
|
51
|
+
end
|
52
|
+
@session.request(request)
|
53
|
+
@nodes.push(body)
|
54
|
+
else
|
55
|
+
request.reply_with_file(self.root)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Alias the Stream#write method before overriding it so we can call
|
60
|
+
# it later from a Session instance.
|
61
|
+
alias :stream_write :write
|
62
|
+
|
63
|
+
# Override Stream#write to queue stanzas rather than immediately writing
|
64
|
+
# to the stream. Stanza responses must be paired with a queued request.
|
65
|
+
def write(data)
|
66
|
+
@session.write(data)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return an array of Node objects inside the body element.
|
70
|
+
# TODO This parses the XML again just to strip namespaces. Figure out
|
71
|
+
# Nokogiri namespace handling instead.
|
72
|
+
def parse_body(body)
|
73
|
+
body.namespace = nil
|
74
|
+
body.elements.map do |node|
|
75
|
+
Nokogiri::XML(node.to_s.sub(' xmlns="jabber:client"', '')).root
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def start(node)
|
80
|
+
domain, type, hold, wait, rid = %w[to content hold wait rid].map {|a| (node[a] || '').strip }
|
81
|
+
version = node.attribute_with_ns('version', NAMESPACES[:bosh]).value rescue nil
|
82
|
+
|
83
|
+
@session.inactivity = 20
|
84
|
+
@session.domain = domain
|
85
|
+
@session.content_type = type unless type.empty?
|
86
|
+
@session.hold = hold.to_i unless hold.empty?
|
87
|
+
@session.wait = wait.to_i unless wait.empty?
|
88
|
+
|
89
|
+
raise StreamErrors::UndefinedCondition.new('rid required') if rid.empty?
|
90
|
+
raise StreamErrors::UnsupportedVersion unless version == '1.0'
|
91
|
+
raise StreamErrors::ImproperAddressing unless valid_address?(domain)
|
92
|
+
raise StreamErrors::HostUnknown unless config.vhost?(domain)
|
93
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:http_bind]
|
94
|
+
|
95
|
+
Sessions[@session.id] = @session
|
96
|
+
send_stream_header
|
97
|
+
end
|
98
|
+
|
99
|
+
def terminate
|
100
|
+
doc = Nokogiri::XML::Document.new
|
101
|
+
node = doc.create_element('body',
|
102
|
+
'type' => 'terminate',
|
103
|
+
'xmlns' => NAMESPACES[:http_bind])
|
104
|
+
@session.reply(node)
|
105
|
+
close_stream
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def send_stream_header
|
111
|
+
doc = Nokogiri::XML::Document.new
|
112
|
+
node = doc.create_element('body',
|
113
|
+
'charsets' => 'UTF-8',
|
114
|
+
'from' => @session.domain,
|
115
|
+
'hold' => @session.hold,
|
116
|
+
'inactivity' => @session.inactivity,
|
117
|
+
'polling' => '5',
|
118
|
+
'requests' => '2',
|
119
|
+
'sid' => @session.id,
|
120
|
+
'ver' => '1.6',
|
121
|
+
'wait' => @session.wait,
|
122
|
+
'xmpp:version' => '1.0',
|
123
|
+
'xmlns' => NAMESPACES[:http_bind],
|
124
|
+
'xmlns:xmpp' => NAMESPACES[:bosh],
|
125
|
+
'xmlns:stream' => NAMESPACES[:stream])
|
126
|
+
|
127
|
+
node << doc.create_element('stream:features') do |el|
|
128
|
+
el << doc.create_element('mechanisms') do |mechanisms|
|
129
|
+
mechanisms.default_namespace = NAMESPACES[:sasl]
|
130
|
+
mechanisms << doc.create_element('mechanism', 'PLAIN')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
@session.reply(node)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Override +Stream#send_stream_error+ to wrap the error XML in a BOSH
|
137
|
+
# terminate body tag.
|
138
|
+
def send_stream_error(e)
|
139
|
+
doc = Nokogiri::XML::Document.new
|
140
|
+
node = doc.create_element('body',
|
141
|
+
'condition' => 'remote-stream-error',
|
142
|
+
'type' => 'terminate',
|
143
|
+
'xmlns' => NAMESPACES[:http_bind],
|
144
|
+
'xmlns:stream' => NAMESPACES[:stream])
|
145
|
+
node.inner_html = e.to_xml
|
146
|
+
@session.reply(node)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Override +Stream#close_stream+ to simply close the connection without
|
150
|
+
# writing a closing stream tag.
|
151
|
+
def close_stream
|
152
|
+
close_connection_after_writing
|
153
|
+
@session.close
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Parser < Nokogiri::XML::SAX::Document
|
6
|
+
include Nokogiri::XML
|
7
|
+
STREAM_NAME = 'stream'.freeze
|
8
|
+
STREAM_URI = 'http://etherx.jabber.org/streams'.freeze
|
9
|
+
IGNORE = NAMESPACES.values_at(:client, :component, :server)
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
@listeners, @node = Hash.new {|h, k| h[k] = []}, nil
|
13
|
+
@parser = Nokogiri::XML::SAX::PushParser.new(self)
|
14
|
+
instance_eval(&block) if block
|
15
|
+
end
|
16
|
+
|
17
|
+
[:stream_open, :stream_close, :stanza].each do |name|
|
18
|
+
define_method(name) do |&block|
|
19
|
+
@listeners[name] << block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(data)
|
24
|
+
@parser << data
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_element_namespace(name, attrs=[], prefix=nil, uri=nil, ns=[])
|
29
|
+
el = node(name, attrs, prefix, uri, ns)
|
30
|
+
if stream?(name, uri)
|
31
|
+
notify(:stream_open, el)
|
32
|
+
else
|
33
|
+
@node << el if @node
|
34
|
+
@node = el
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def end_element_namespace(name, prefix=nil, uri=nil)
|
39
|
+
if stream?(name, uri)
|
40
|
+
notify(:stream_close)
|
41
|
+
elsif @node.parent != @node.document
|
42
|
+
@node = @node.parent
|
43
|
+
else
|
44
|
+
notify(:stanza, @node)
|
45
|
+
@node = nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def characters(chars)
|
50
|
+
@node << Text.new(chars, @node.document) if @node
|
51
|
+
end
|
52
|
+
alias :cdata_block :characters
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def notify(msg, node=nil)
|
57
|
+
@listeners[msg].each do |b|
|
58
|
+
(node ? b.call(node) : b.call) rescue nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def stream?(name, uri)
|
63
|
+
name == STREAM_NAME && uri == STREAM_URI
|
64
|
+
end
|
65
|
+
|
66
|
+
def node(name, attrs=[], prefix=nil, uri=nil, ns=[])
|
67
|
+
ignore = stream?(name, uri) ? [] : IGNORE
|
68
|
+
doc = @node ? @node.document : Document.new
|
69
|
+
node = doc.create_element(name) do |node|
|
70
|
+
attrs.each {|attr| node[attr.localname] = attr.value }
|
71
|
+
ns.each {|prefix, uri| node.add_namespace(prefix, uri) unless ignore.include?(uri) }
|
72
|
+
doc << node unless @node
|
73
|
+
end
|
74
|
+
node.namespace = node.add_namespace(prefix, uri) unless ignore.include?(uri)
|
75
|
+
node
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
# Provides plain (username/password) and external (TLS certificate) SASL
|
6
|
+
# authentication to client and server streams.
|
7
|
+
class SASL
|
8
|
+
include Vines::Log
|
9
|
+
EMPTY = '='.freeze
|
10
|
+
|
11
|
+
def initialize(stream)
|
12
|
+
@stream = stream
|
13
|
+
end
|
14
|
+
|
15
|
+
# Authenticate server-to-server streams, comparing their domain to their
|
16
|
+
# SSL certificate.
|
17
|
+
#
|
18
|
+
# http://xmpp.org/extensions/xep-0178.html#s2s
|
19
|
+
#
|
20
|
+
# encoded - The Base64 encoded remote domain name String sent by the
|
21
|
+
# server stream.
|
22
|
+
#
|
23
|
+
# Returns true if the Base64 encoded domain matches the TLS certificate
|
24
|
+
# presented earlier in stream negotiation.
|
25
|
+
#
|
26
|
+
# Raises a SaslError if authentication failed.
|
27
|
+
def external_auth(encoded)
|
28
|
+
unless encoded == EMPTY
|
29
|
+
authzid = decode64(encoded)
|
30
|
+
matches_from = (authzid == @stream.remote_domain)
|
31
|
+
raise SaslErrors::InvalidAuthzid unless matches_from
|
32
|
+
end
|
33
|
+
matches_from = @stream.cert_domain_matches?(@stream.remote_domain)
|
34
|
+
matches_from or raise SaslErrors::NotAuthorized
|
35
|
+
end
|
36
|
+
|
37
|
+
# Authenticate client-to-server streams using a username and password.
|
38
|
+
#
|
39
|
+
# encoded - The Base64 encoded jid and password String sent by the
|
40
|
+
# client stream.
|
41
|
+
#
|
42
|
+
# Returns the authenticated User or raises SaslError if authentication failed.
|
43
|
+
def plain_auth(encoded)
|
44
|
+
jid, password = decode_credentials(encoded)
|
45
|
+
user = authenticate(jid, password)
|
46
|
+
user or raise SaslErrors::NotAuthorized
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Storage backends should not raise errors, but if an unexpected error
|
52
|
+
# occurs during authentication, convert it to a temporary-auth-failure.
|
53
|
+
#
|
54
|
+
# jid - The user's jid String.
|
55
|
+
# password - The String password.
|
56
|
+
#
|
57
|
+
# Returns the authenticated User or nil if authentication failed.
|
58
|
+
#
|
59
|
+
# Raises TemoraryAuthFailure if the storage system failed.
|
60
|
+
def authenticate(jid, password)
|
61
|
+
log.info("Authenticating user: %s" % jid)
|
62
|
+
@stream.storage.authenticate(jid, password).tap do |user|
|
63
|
+
log.info("Authentication succeeded: %s" % user.jid) if user
|
64
|
+
end
|
65
|
+
rescue => e
|
66
|
+
log.error("Failed to authenticate: #{e.to_s}")
|
67
|
+
raise SaslErrors::TemporaryAuthFailure
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return the JID and password decoded from the Base64 encoded SASL PLAIN
|
71
|
+
# credentials formatted as authzid\0authcid\0password.
|
72
|
+
#
|
73
|
+
# http://tools.ietf.org/html/rfc6120#section-6.3.8
|
74
|
+
# http://tools.ietf.org/html/rfc4616
|
75
|
+
#
|
76
|
+
# encoded - The Base64 encoded String from which to extract jid and password.
|
77
|
+
#
|
78
|
+
# Returns an Array of jid String and password String.
|
79
|
+
def decode_credentials(encoded)
|
80
|
+
authzid, node, password = decode64(encoded).split("\x00")
|
81
|
+
raise SaslErrors::NotAuthorized if node.nil? || node.empty? || password.nil? || password.empty?
|
82
|
+
jid = JID.new(node, @stream.domain) rescue (raise SaslErrors::NotAuthorized)
|
83
|
+
validate_authzid!(authzid, jid)
|
84
|
+
[jid, password]
|
85
|
+
end
|
86
|
+
|
87
|
+
# An optional SASL authzid allows a user to authenticate with one
|
88
|
+
# user name and password and then have their connection authorized as a
|
89
|
+
# different ID (the authzid). We don't support that, so raise an error if
|
90
|
+
# the authzid is provided and different than the authcid.
|
91
|
+
#
|
92
|
+
# Most clients don't send an authzid at all because it's optional and not
|
93
|
+
# widely supported. However, Strophe and Blather send a bare JID, in
|
94
|
+
# compliance with RFC 6120, but Smack sends just the user name as the
|
95
|
+
# authzid. So, take care to handle non-compliant clients here.
|
96
|
+
#
|
97
|
+
# http://tools.ietf.org/html/rfc6120#section-6.3.8
|
98
|
+
#
|
99
|
+
# authzid - The authzid String (may be nil).
|
100
|
+
# jid - The username String.
|
101
|
+
#
|
102
|
+
# Returns nothing.
|
103
|
+
def validate_authzid!(authzid, jid)
|
104
|
+
return if authzid.nil? || authzid.empty?
|
105
|
+
authzid.downcase!
|
106
|
+
smack = authzid == jid.node
|
107
|
+
compliant = authzid == jid.to_s
|
108
|
+
raise SaslErrors::InvalidAuthzid unless compliant || smack
|
109
|
+
end
|
110
|
+
|
111
|
+
# Decode the Base64 encoded string, raising an error for invalid data.
|
112
|
+
#
|
113
|
+
# http://tools.ietf.org/html/rfc6120#section-13.9.1
|
114
|
+
#
|
115
|
+
# encoded - The Base64 encoded String.
|
116
|
+
#
|
117
|
+
# Returns a UTF-8 String.
|
118
|
+
def decode64(encoded)
|
119
|
+
Base64.strict_decode64(encoded).tap do |decoded|
|
120
|
+
decoded.force_encoding(Encoding::UTF_8)
|
121
|
+
raise SaslErrors::IncorrectEncoding unless decoded.valid_encoding?
|
122
|
+
end
|
123
|
+
rescue
|
124
|
+
raise SaslErrors::IncorrectEncoding
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class FinalRestart < State
|
7
|
+
def initialize(stream, success=Ready)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
stream.write('<stream:features/>')
|
15
|
+
stream.router << stream
|
16
|
+
advance
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class Auth < State
|
8
|
+
NS = NAMESPACES[:sasl]
|
9
|
+
|
10
|
+
def initialize(stream, success=AuthResult)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def node(node)
|
15
|
+
raise StreamErrors::NotAuthorized unless external?(node)
|
16
|
+
authzid = Base64.strict_encode64(stream.domain)
|
17
|
+
stream.write(%Q{<auth xmlns="#{NS}" mechanism="EXTERNAL">#{authzid}</auth>})
|
18
|
+
advance
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def external?(node)
|
24
|
+
external = node.xpath("ns:mechanisms/ns:mechanism[text()='EXTERNAL']", 'ns' => NS).any?
|
25
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && external
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class AuthRestart < State
|
8
|
+
def initialize(stream, success=Auth)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def node(node)
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
14
|
+
advance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class AuthResult < State
|
8
|
+
SUCCESS = 'success'.freeze
|
9
|
+
FAILURE = 'failure'.freeze
|
10
|
+
|
11
|
+
def initialize(stream, success=FinalRestart)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def node(node)
|
16
|
+
raise StreamErrors::NotAuthorized unless namespace(node) == NAMESPACES[:sasl]
|
17
|
+
case node.name
|
18
|
+
when SUCCESS
|
19
|
+
stream.start(node)
|
20
|
+
stream.reset
|
21
|
+
advance
|
22
|
+
when FAILURE
|
23
|
+
stream.close_connection
|
24
|
+
else
|
25
|
+
raise StreamErrors::NotAuthorized
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class FinalFeatures < State
|
8
|
+
def initialize(stream, success=Server::Ready)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def node(node)
|
13
|
+
raise StreamErrors::NotAuthorized unless empty_features?(node)
|
14
|
+
stream.router << stream
|
15
|
+
advance
|
16
|
+
stream.notify_connected
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def empty_features?(node)
|
22
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && node.elements.empty?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class FinalRestart < State
|
8
|
+
def initialize(stream, success=FinalFeatures)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def node(node)
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
14
|
+
advance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class Start < State
|
8
|
+
def initialize(stream, success=TLS)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def node(node)
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
14
|
+
advance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class TLS < State
|
8
|
+
NS = NAMESPACES[:tls]
|
9
|
+
|
10
|
+
def initialize(stream, success=TLSResult)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def node(node)
|
15
|
+
raise StreamErrors::NotAuthorized unless tls?(node)
|
16
|
+
stream.write("<starttls xmlns='#{NS}'/>")
|
17
|
+
advance
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def tls?(node)
|
23
|
+
tls = node.xpath('ns:starttls', 'ns' => NS).any?
|
24
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && tls
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
6
|
+
class Outbound
|
7
|
+
class TLSResult < State
|
8
|
+
NS = NAMESPACES[:tls]
|
9
|
+
PROCEED = 'proceed'.freeze
|
10
|
+
FAILURE = 'failure'.freeze
|
11
|
+
|
12
|
+
def initialize(stream, success=AuthRestart)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def node(node)
|
17
|
+
raise StreamErrors::NotAuthorized unless namespace(node) == NS
|
18
|
+
case node.name
|
19
|
+
when PROCEED
|
20
|
+
stream.encrypt
|
21
|
+
stream.start(node)
|
22
|
+
stream.reset
|
23
|
+
advance
|
24
|
+
when FAILURE
|
25
|
+
stream.close_connection
|
26
|
+
else
|
27
|
+
raise StreamErrors::NotAuthorized
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Server
|
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
|
+
raise StreamErrors::HostUnknown unless to.domain == stream.domain
|
14
|
+
stream.user = User.new(jid: from)
|
15
|
+
if stanza.local? || stanza.to_pubsub_domain?
|
16
|
+
stanza.process
|
17
|
+
else
|
18
|
+
stanza.route
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|