vinesmod 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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