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.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -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/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -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 +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/contact_test.rb +102 -0
  125. data/test/error_test.rb +58 -0
  126. data/test/ext/nokogiri.rb +14 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. 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,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class Closed < State
7
+ def node(node)
8
+ # ignore data received after close_connection
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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