vinesmod 0.4.5

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.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
@@ -0,0 +1,120 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Session < Client::Session
7
+ include Nokogiri::XML
8
+
9
+ attr_accessor :content_type, :hold, :inactivity, :wait
10
+
11
+ CONTENT_TYPE = 'text/xml; charset=utf-8'.freeze
12
+
13
+ def initialize(stream)
14
+ super
15
+ @state = Http::Start.new(stream)
16
+ @inactivity, @wait, @hold = 20, 60, 1
17
+ @replied = Time.now
18
+ @requests, @responses = [], []
19
+ @content_type = CONTENT_TYPE
20
+ end
21
+
22
+ def close
23
+ Sessions.delete(@id)
24
+ router.delete(self)
25
+ delete_from_cluster
26
+ unsubscribe_pubsub
27
+ @requests.each {|req| req.stream.close_connection }
28
+ @requests.clear
29
+ @responses.clear
30
+ @state = Client::Closed.new(nil)
31
+ @unbound = true
32
+ @available = false
33
+ broadcast_unavailable
34
+ end
35
+
36
+ def ready?
37
+ @state.class == Http::Ready
38
+ end
39
+
40
+ def requests
41
+ @requests.clone
42
+ end
43
+
44
+ def expired?
45
+ respond_to_expired_requests
46
+ @requests.empty? && (Time.now - @replied > @inactivity)
47
+ end
48
+
49
+ # Resume this session from its most recent state with a new client
50
+ # stream and incoming node.
51
+ def resume(stream, node)
52
+ stream.session.requests.each do |req|
53
+ request(req)
54
+ end
55
+ stream.session = self
56
+ @state.stream = stream
57
+ @state.node(node)
58
+ end
59
+
60
+ def request(request)
61
+ if @responses.any?
62
+ request.reply(wrap_body(@responses.join), @content_type)
63
+ @replied = Time.now
64
+ @responses.clear
65
+ else
66
+ while @requests.size >= @hold
67
+ @requests.shift.reply(wrap_body(''), @content_type)
68
+ @replied = Time.now
69
+ end
70
+ @requests << request
71
+ end
72
+ end
73
+
74
+ # Send an HTTP 200 OK response wrapping the XMPP node content back
75
+ # to the client.
76
+ def reply(node)
77
+ if request = @requests.shift
78
+ request.reply(node, @content_type)
79
+ @replied = Time.now
80
+ end
81
+ end
82
+
83
+ # Write the XMPP node to the client stream after wrapping it in a BOSH
84
+ # body tag. If there's a waiting request, the node is written
85
+ # immediately. If not, it's queued until the next request arrives.
86
+ def write(node)
87
+ if request = @requests.shift
88
+ request.reply(wrap_body(node), @content_type)
89
+ @replied = Time.now
90
+ else
91
+ @responses << node.to_s
92
+ end
93
+ end
94
+
95
+ def unbind!(stream)
96
+ @requests.reject! {|req| req.stream == stream }
97
+ end
98
+
99
+ private
100
+
101
+ def respond_to_expired_requests
102
+ expired = @requests.select {|req| req.age > @wait }
103
+ expired.each do |request|
104
+ request.reply(wrap_body(''), @content_type)
105
+ @requests.delete(request)
106
+ @replied = Time.now
107
+ end
108
+ end
109
+
110
+ def wrap_body(data)
111
+ doc = Document.new
112
+ doc.create_element('body') do |node|
113
+ node.add_namespace(nil, NAMESPACES[:http_bind])
114
+ node.inner_html = data.to_s
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ # Sessions is a cache of Http::Session objects for transient HTTP
7
+ # connections. The cache is monitored for expired client connections.
8
+ class Sessions
9
+ include Vines::Log
10
+
11
+ @@instance = nil
12
+ def self.instance
13
+ @@instance ||= self.new
14
+ end
15
+
16
+ def self.[](sid)
17
+ instance[sid]
18
+ end
19
+
20
+ def self.[]=(sid, session)
21
+ instance[sid] = session
22
+ end
23
+
24
+ def self.delete(sid)
25
+ instance.delete(sid)
26
+ end
27
+
28
+ def initialize
29
+ @sessions = {}
30
+ start_timer
31
+ end
32
+
33
+ def []=(sid, session)
34
+ @sessions[sid] = session
35
+ end
36
+
37
+ def [](sid)
38
+ @sessions[sid]
39
+ end
40
+
41
+ def delete(sid)
42
+ @sessions.delete(sid)
43
+ end
44
+
45
+ private
46
+
47
+ # Check for expired clients to cleanup every second.
48
+ def start_timer
49
+ @timer ||= EventMachine::PeriodicTimer.new(1) { cleanup }
50
+ end
51
+
52
+ # Remove cached information for all expired connections. An expired
53
+ # HTTP client is one that has no queued requests and has had no activity
54
+ # for over 20 seconds.
55
+ def cleanup
56
+ @sessions.each_value do |session|
57
+ session.close if session.expired?
58
+ end
59
+ rescue => e
60
+ log.error("Expired session cleanup failed: #{e}")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Start < State
7
+ def initialize(stream, success=Auth)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless body?(node)
13
+ if session = Sessions[node['sid']]
14
+ session.resume(stream, node)
15
+ else
16
+ stream.start(node)
17
+ advance
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,78 @@
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
+ 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
+ node.namespace = node.add_namespace(prefix, uri) unless ignore.include?(uri)
73
+ doc << node unless @node
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,92 @@
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 s2s streams, comparing their domain to their SSL certificate.
16
+ # Return +true+ if the base64 encoded domain matches the TLS certificate
17
+ # presented earlier in stream negotiation. Raise a +SaslError+ if
18
+ # authentication failed.
19
+ # http://xmpp.org/extensions/xep-0178.html#s2s
20
+ def external_auth(encoded)
21
+ unless encoded == EMPTY
22
+ authzid = decode64(encoded)
23
+ matches_from = (authzid == @stream.remote_domain)
24
+ raise SaslErrors::InvalidAuthzid unless matches_from
25
+ end
26
+ matches_from = @stream.cert_domain_matches?(@stream.remote_domain)
27
+ matches_from or raise SaslErrors::NotAuthorized
28
+ end
29
+
30
+ # Authenticate c2s streams using a username and password. Return the
31
+ # authenticated +User+ or raise a +SaslError+ if authentication failed.
32
+ def plain_auth(encoded)
33
+ jid, password = decode_credentials(encoded)
34
+ user = authenticate(jid, password)
35
+ user or raise SaslErrors::NotAuthorized
36
+ end
37
+
38
+ private
39
+
40
+ # Storage backends should not raise errors, but if an unexpected error
41
+ # occurs during authentication, convert it to a temporary-auth-failure.
42
+ # Return the authenticated +User+ or +nil+ if authentication failed.
43
+ def authenticate(jid, password)
44
+ log.info("Authenticating user: %s" % jid)
45
+ @stream.storage.authenticate(jid, password).tap do |user|
46
+ log.info("Authentication succeeded: %s" % user.jid) if user
47
+ end
48
+ rescue => e
49
+ log.error("Failed to authenticate: #{e.to_s}")
50
+ raise SaslErrors::TemporaryAuthFailure
51
+ end
52
+
53
+ # Return the +JID+ and password decoded from the base64 encoded SASL PLAIN
54
+ # credentials formatted as authzid\0authcid\0password.
55
+ # http://tools.ietf.org/html/rfc6120#section-6.3.8
56
+ # http://tools.ietf.org/html/rfc4616
57
+ def decode_credentials(encoded)
58
+ authzid, node, password = decode64(encoded).split("\x00")
59
+ raise SaslErrors::NotAuthorized if node.nil? || node.empty? || password.nil? || password.empty?
60
+ jid = JID.new(node, @stream.domain) rescue (raise SaslErrors::NotAuthorized)
61
+ validate_authzid!(authzid, jid)
62
+ [jid, password]
63
+ end
64
+
65
+ # An optional SASL authzid allows a user to authenticate with one
66
+ # user name and password and then have their connection authorized as a
67
+ # different ID (the authzid). We don't support that, so raise an error if
68
+ # the authzid is provided and different than the authcid.
69
+ #
70
+ # Most clients don't send an authzid at all because it's optional and not
71
+ # widely supported. However, Strophe and Blather send a bare JID, in
72
+ # compliance with RFC 6120, but Smack sends just the user name as the
73
+ # authzid. So, take care to handle non-compliant clients here.
74
+ # http://tools.ietf.org/html/rfc6120#section-6.3.8
75
+ def validate_authzid!(authzid, jid)
76
+ return if authzid.nil? || authzid.empty?
77
+ authzid.downcase!
78
+ smack = authzid == jid.node
79
+ compliant = authzid == jid.to_s
80
+ raise SaslErrors::InvalidAuthzid unless compliant || smack
81
+ end
82
+
83
+ # Decode the base64 encoded string, raising an error for invalid data.
84
+ # http://tools.ietf.org/html/rfc6120#section-13.9.1
85
+ def decode64(encoded)
86
+ Base64.strict_decode64(encoded)
87
+ rescue
88
+ raise SaslErrors::IncorrectEncoding
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,150 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ # Implements the XMPP protocol for server-to-server (s2s) streams. This
6
+ # serves connected streams using the jabber:server namespace. This handles
7
+ # both accepting incoming s2s streams and initiating outbound s2s streams
8
+ # to other servers.
9
+ class Server < Stream
10
+ MECHANISMS = %w[EXTERNAL].freeze
11
+
12
+ # Starts the connection to the remote server. When the stream is
13
+ # connected and ready to send stanzas it will yield to the callback
14
+ # block. The callback is run on the EventMachine reactor thread. The
15
+ # yielded stream will be nil if the remote connection failed. We need to
16
+ # use a background thread to avoid blocking the server on DNS SRV
17
+ # lookups.
18
+ def self.start(config, to, from, &callback)
19
+ op = proc do
20
+ Resolv::DNS.open do |dns|
21
+ dns.getresources("_xmpp-server._tcp.#{to}", Resolv::DNS::Resource::IN::SRV)
22
+ end.sort! {|a,b| a.priority == b.priority ? b.weight <=> a.weight : a.priority <=> b.priority }
23
+ end
24
+ cb = proc do |srv|
25
+ if srv.empty?
26
+ srv << {target: to, port: 5269}
27
+ class << srv.first
28
+ def method_missing(name); self[name]; end
29
+ end
30
+ end
31
+ Server.connect(config, to, from, srv, callback)
32
+ end
33
+ EM.defer(proc { op.call rescue [] }, cb)
34
+ end
35
+
36
+ def self.connect(config, to, from, srv, callback)
37
+ if srv.empty?
38
+ # fiber so storage calls work properly
39
+ Fiber.new { callback.call(nil) }.resume
40
+ else
41
+ begin
42
+ rr = srv.shift
43
+ opts = {to: to, from: from, srv: srv, callback: callback}
44
+ EM.connect(rr.target.to_s, rr.port, Server, config, opts)
45
+ rescue => e
46
+ connect(config, to, from, srv, callback)
47
+ end
48
+ end
49
+ end
50
+
51
+ attr_reader :domain
52
+ attr_accessor :remote_domain
53
+
54
+ def initialize(config, options={})
55
+ super(config)
56
+ @connected = false
57
+ @remote_domain = options[:to]
58
+ @domain = options[:from]
59
+ @srv = options[:srv]
60
+ @callback = options[:callback]
61
+ @outbound = @remote_domain && @domain
62
+ start = @outbound ? Outbound::Start.new(self) : Start.new(self)
63
+ advance(start)
64
+ end
65
+
66
+ def post_init
67
+ super
68
+ send_stream_header if @outbound
69
+ end
70
+
71
+ def max_stanza_size
72
+ config[:server].max_stanza_size
73
+ end
74
+
75
+ def ssl_handshake_completed
76
+ close_connection unless cert_domain_matches?(@remote_domain)
77
+ end
78
+
79
+ # Return an array of allowed authentication mechanisms advertised as
80
+ # server stream features.
81
+ def authentication_mechanisms
82
+ MECHANISMS
83
+ end
84
+
85
+ def stream_type
86
+ :server
87
+ end
88
+
89
+ def unbind
90
+ super
91
+ if @outbound && !@connected
92
+ Server.connect(config, @remote_domain, @domain, @srv, @callback)
93
+ end
94
+ end
95
+
96
+ def vhost?(domain)
97
+ config.vhost?(domain)
98
+ end
99
+
100
+ def notify_connected
101
+ @connected = true
102
+ if @callback
103
+ @callback.call(self)
104
+ @callback = nil
105
+ end
106
+ end
107
+
108
+ def ready?
109
+ state.class == Server::Ready
110
+ end
111
+
112
+ def start(node)
113
+ if @outbound then send_stream_header; return end
114
+ to, from = %w[to from].map {|a| node[a] }
115
+ @domain, @remote_domain = to, from unless @domain
116
+ send_stream_header
117
+ raise StreamErrors::NotAuthorized if domain_change?(to, from)
118
+ raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
119
+ raise StreamErrors::ImproperAddressing unless valid_address?(@domain) && valid_address?(@remote_domain)
120
+ raise StreamErrors::HostUnknown unless config.vhost?(@domain) || config.pubsub?(@domain) || config.component?(@domain)
121
+ raise StreamErrors::NotAuthorized unless config.s2s?(@remote_domain) && config.allowed?(@domain, @remote_domain)
122
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:server]
123
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
124
+ end
125
+
126
+ private
127
+
128
+ # The +to+ and +from+ domain addresses set on the initial stream header
129
+ # must not change during stream restarts. This prevents a server from
130
+ # authenticating as one domain, then sending stanzas from users in a
131
+ # different domain.
132
+ def domain_change?(to, from)
133
+ to != @domain || from != @remote_domain
134
+ end
135
+
136
+ def send_stream_header
137
+ attrs = {
138
+ 'xmlns' => NAMESPACES[:server],
139
+ 'xmlns:stream' => NAMESPACES[:stream],
140
+ 'xml:lang' => 'en',
141
+ 'id' => Kit.uuid,
142
+ 'from' => @domain,
143
+ 'to' => @remote_domain,
144
+ 'version' => '1.0'
145
+ }
146
+ write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
147
+ end
148
+ end
149
+ end
150
+ end