vinesmod 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +57 -0
- data/bin/vines +93 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3366 -0
- data/conf/config.rb +149 -0
- data/lib/vines.rb +197 -0
- data/lib/vines/cluster.rb +246 -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/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/register.rb +27 -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/command/unregister.rb +27 -0
- data/lib/vines/config.rb +213 -0
- data/lib/vines/config/host.rb +119 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -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 +35 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/stanza/iq.rb +48 -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/register.rb +42 -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/message.rb +43 -0
- data/lib/vines/stanza/presence.rb +156 -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/pubsub.rb +22 -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/storage.rb +188 -0
- data/lib/vines/storage/local.rb +165 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +260 -0
- data/lib/vines/store.rb +94 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/stream/client.rb +84 -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/component.rb +58 -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/http.rb +157 -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/parser.rb +78 -0
- data/lib/vines/stream/sasl.rb +92 -0
- data/lib/vines/stream/server.rb +150 -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/state.rb +60 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +43 -0
- data/vines.gemspec +36 -0
- data/web/404.html +51 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +362 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/index.html +16 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/stylesheets/chat.css +144 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/button.coffee +25 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/filter.coffee +49 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +68 -0
- data/web/lib/coffeescripts/logout.coffee +5 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +229 -0
- data/web/lib/coffeescripts/transfer.coffee +106 -0
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/light-gray.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/images/white.png +0 -0
- data/web/lib/javascripts/base.js +12 -0
- data/web/lib/javascripts/icons.js +110 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +4 -0
- data/web/lib/javascripts/raphael.js +6 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +385 -0
- data/web/lib/stylesheets/login.css +68 -0
- metadata +423 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class AuthRestart < State
|
7
|
+
def initialize(stream, success=Auth)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('mechanisms') do |parent|
|
17
|
+
parent.default_namespace = NAMESPACES[:sasl]
|
18
|
+
stream.authentication_mechanisms.each do |name|
|
19
|
+
parent << doc.create_element('mechanism', name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
stream.write(features)
|
24
|
+
advance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Bind < State
|
7
|
+
NS = NAMESPACES[:bind]
|
8
|
+
MAX_ATTEMPTS = 5
|
9
|
+
|
10
|
+
def initialize(stream, success=Ready)
|
11
|
+
super
|
12
|
+
@attempts = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def node(node)
|
16
|
+
@attempts += 1
|
17
|
+
raise StreamErrors::NotAuthorized unless bind?(node)
|
18
|
+
raise StreamErrors::PolicyViolation.new('max bind attempts reached') if @attempts > MAX_ATTEMPTS
|
19
|
+
raise StanzaErrors::ResourceConstraint.new(node, 'wait') if resource_limit_reached?
|
20
|
+
|
21
|
+
stream.bind!(resource(node))
|
22
|
+
doc = Document.new
|
23
|
+
result = doc.create_element('iq', 'id' => node['id'], 'type' => 'result') do |el|
|
24
|
+
el << doc.create_element('bind') do |bind|
|
25
|
+
bind.default_namespace = NS
|
26
|
+
bind << doc.create_element('jid', stream.user.jid.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
stream.write(result)
|
30
|
+
send_empty_features
|
31
|
+
advance
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Write the final <stream:features/> element to the stream, indicating
|
37
|
+
# stream negotiation is complete and the client is cleared to send
|
38
|
+
# stanzas.
|
39
|
+
def send_empty_features
|
40
|
+
stream.write('<stream:features/>')
|
41
|
+
end
|
42
|
+
|
43
|
+
def bind?(node)
|
44
|
+
node.name == 'iq' && node['type'] == 'set' && node.xpath('ns:bind', 'ns' => NS).any?
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource(node)
|
48
|
+
el = node.xpath('ns:bind/ns:resource', 'ns' => NS).first
|
49
|
+
resource = el ? el.text.strip : ''
|
50
|
+
generate = resource.empty? || !resource_valid?(resource) || resource_used?(resource)
|
51
|
+
generate ? Kit.uuid : resource
|
52
|
+
end
|
53
|
+
|
54
|
+
def resource_limit_reached?
|
55
|
+
used = stream.connected_resources(stream.user.jid.bare).size
|
56
|
+
used >= stream.max_resources_per_account
|
57
|
+
end
|
58
|
+
|
59
|
+
def resource_used?(resource)
|
60
|
+
stream.available_resources(stream.user.jid).any? do |c|
|
61
|
+
c.user.jid.resource == resource
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def resource_valid?(resource)
|
66
|
+
jid = stream.user.jid
|
67
|
+
JID.new(jid.node, jid.domain, resource) rescue false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class BindRestart < State
|
7
|
+
def initialize(stream, success=Bind)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
|
17
|
+
end
|
18
|
+
stream.write(features)
|
19
|
+
advance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Ready < State
|
7
|
+
def node(node)
|
8
|
+
stanza = to_stanza(node)
|
9
|
+
raise StreamErrors::UnsupportedStanzaType unless stanza
|
10
|
+
stanza.validate_to
|
11
|
+
stanza.validate_from
|
12
|
+
stanza.process
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
# A Session tracks the state of a client stream over its lifetime from
|
7
|
+
# negotiation to processing stanzas to shutdown. By disconnecting the
|
8
|
+
# stream's state from the stream, we can allow multiple TCP connections
|
9
|
+
# to access one logical session (e.g. HTTP streams).
|
10
|
+
class Session
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
attr_accessor :domain, :user
|
14
|
+
attr_reader :id, :last_broadcast_presence, :state
|
15
|
+
|
16
|
+
def initialize(stream)
|
17
|
+
@stream = stream
|
18
|
+
@id = Kit.uuid
|
19
|
+
@config = stream.config
|
20
|
+
@state = Client::Start.new(stream)
|
21
|
+
@available = false
|
22
|
+
@domain = nil
|
23
|
+
@last_broadcast_presence = nil
|
24
|
+
@requested_roster = false
|
25
|
+
@unbound = false
|
26
|
+
@user = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(session)
|
30
|
+
session.is_a?(Session) ? self.id <=> session.id : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :eql? :==
|
34
|
+
|
35
|
+
def hash
|
36
|
+
@id.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def advance(state)
|
40
|
+
@state = state
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns true if this client has properly authenticated with
|
44
|
+
# the server.
|
45
|
+
def authenticated?
|
46
|
+
!@user.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Notify the session that the client has sent an initial presence
|
50
|
+
# broadcast and is now considered to be an "available" resource.
|
51
|
+
# Available resources are sent presence subscription stanzas.
|
52
|
+
def available!
|
53
|
+
@available = true
|
54
|
+
save_to_cluster
|
55
|
+
end
|
56
|
+
|
57
|
+
# An available resource has sent initial presence and can
|
58
|
+
# receive presence subscription requests.
|
59
|
+
def available?
|
60
|
+
@available && connected?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Complete resource binding with the given resource name, provided by the
|
64
|
+
# client or generated by the server. Once resource binding is completed,
|
65
|
+
# the stream is considered to be "connected" and ready for traffic.
|
66
|
+
def bind!(resource)
|
67
|
+
@user.jid.resource = resource
|
68
|
+
router << self
|
69
|
+
save_to_cluster
|
70
|
+
end
|
71
|
+
|
72
|
+
# A connected resource has authenticated and bound a resource
|
73
|
+
# identifier.
|
74
|
+
def connected?
|
75
|
+
!@unbound && authenticated? && !@user.jid.bare?
|
76
|
+
end
|
77
|
+
|
78
|
+
# An interested resource has requested its roster and can
|
79
|
+
# receive roster pushes.
|
80
|
+
def interested?
|
81
|
+
@requested_roster && connected?
|
82
|
+
end
|
83
|
+
|
84
|
+
def last_broadcast_presence=(node)
|
85
|
+
@last_broadcast_presence = node
|
86
|
+
save_to_cluster
|
87
|
+
end
|
88
|
+
|
89
|
+
def ready?
|
90
|
+
@state.class == Client::Ready
|
91
|
+
end
|
92
|
+
|
93
|
+
# Notify the session that the client has requested its roster and is now
|
94
|
+
# considered to be an "interested" resource. Interested resources are sent
|
95
|
+
# roster pushes when changes are made to their contacts.
|
96
|
+
def requested_roster!
|
97
|
+
@requested_roster = true
|
98
|
+
save_to_cluster
|
99
|
+
end
|
100
|
+
|
101
|
+
def stream_type
|
102
|
+
:client
|
103
|
+
end
|
104
|
+
|
105
|
+
def write(data)
|
106
|
+
@stream.write(data)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Called by the stream when it's disconnected from the client. The stream
|
110
|
+
# passes itself to this method in case multiple streams are accessing this
|
111
|
+
# session (e.g. BOSH/HTTP).
|
112
|
+
def unbind!(stream)
|
113
|
+
router.delete(self)
|
114
|
+
delete_from_cluster
|
115
|
+
unsubscribe_pubsub
|
116
|
+
@unbound = true
|
117
|
+
@available = false
|
118
|
+
broadcast_unavailable
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns streams for available resources to which this user
|
122
|
+
# has successfully subscribed.
|
123
|
+
def available_subscribed_to_resources
|
124
|
+
subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
|
125
|
+
router.available_resources(subscribed, @user.jid)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns streams for available resources that are subscribed
|
129
|
+
# to this user's presence updates.
|
130
|
+
def available_subscribers
|
131
|
+
subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
|
132
|
+
router.available_resources(subscribed, @user.jid)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns contacts hosted at remote servers to which this user has
|
136
|
+
# successfully subscribed.
|
137
|
+
def remote_subscribed_to_contacts
|
138
|
+
@user.subscribed_to_contacts.reject do |c|
|
139
|
+
@config.local_jid?(c.jid)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns contacts hosted at remote servers that are subscribed
|
144
|
+
# to this user's presence updates.
|
145
|
+
def remote_subscribers(to=nil)
|
146
|
+
jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
|
147
|
+
@user.subscribed_from_contacts.reject do |c|
|
148
|
+
@config.local_jid?(c.jid) || (jid && c.jid.bare != jid)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def broadcast_unavailable
|
155
|
+
return unless authenticated?
|
156
|
+
Fiber.new do
|
157
|
+
broadcast(unavailable, available_subscribers)
|
158
|
+
broadcast(unavailable, router.available_resources(@user.jid, @user.jid))
|
159
|
+
remote_subscribers.each do |contact|
|
160
|
+
node = unavailable
|
161
|
+
node['to'] = contact.jid.bare.to_s
|
162
|
+
router.route(node) rescue nil # ignore RemoteServerNotFound
|
163
|
+
end
|
164
|
+
end.resume
|
165
|
+
end
|
166
|
+
|
167
|
+
def unavailable
|
168
|
+
doc = Nokogiri::XML::Document.new
|
169
|
+
doc.create_element('presence',
|
170
|
+
'from' => @user.jid.to_s,
|
171
|
+
'type' => 'unavailable')
|
172
|
+
end
|
173
|
+
|
174
|
+
def broadcast(stanza, recipients)
|
175
|
+
recipients.each do |recipient|
|
176
|
+
stanza['to'] = recipient.user.jid.to_s
|
177
|
+
recipient.write(stanza)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def router
|
182
|
+
@config.router
|
183
|
+
end
|
184
|
+
|
185
|
+
def save_to_cluster
|
186
|
+
if @config.cluster?
|
187
|
+
@config.cluster.save_session(@user.jid, to_hash)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete_from_cluster
|
192
|
+
if connected? && @config.cluster?
|
193
|
+
@config.cluster.delete_session(@user.jid)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def unsubscribe_pubsub
|
198
|
+
if connected?
|
199
|
+
@config.vhost(@user.jid.domain).unsubscribe_pubsub(@user.jid)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def to_hash
|
204
|
+
presence = @last_broadcast_presence ? @last_broadcast_presence.to_s : nil
|
205
|
+
{available: @available, interested: @requested_roster, presence: presence.to_s}
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class Start < State
|
7
|
+
def initialize(stream, success=TLS)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
13
|
+
stream.start(node)
|
14
|
+
doc = Document.new
|
15
|
+
features = doc.create_element('stream:features') do |el|
|
16
|
+
el << doc.create_element('starttls') do |tls|
|
17
|
+
tls.default_namespace = NAMESPACES[:tls]
|
18
|
+
tls << doc.create_element('required')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
stream.write(features)
|
22
|
+
advance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Client
|
6
|
+
class TLS < State
|
7
|
+
NS = NAMESPACES[:tls]
|
8
|
+
PROCEED = %Q{<proceed xmlns="#{NS}"/>}.freeze
|
9
|
+
FAILURE = %Q{<failure xmlns="#{NS}"/>}.freeze
|
10
|
+
STARTTLS = 'starttls'.freeze
|
11
|
+
|
12
|
+
def initialize(stream, success=AuthRestart)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def node(node)
|
17
|
+
raise StreamErrors::NotAuthorized unless starttls?(node)
|
18
|
+
if stream.encrypt?
|
19
|
+
stream.write(PROCEED)
|
20
|
+
stream.encrypt
|
21
|
+
stream.reset
|
22
|
+
advance
|
23
|
+
else
|
24
|
+
stream.write(FAILURE)
|
25
|
+
stream.write('</stream:stream>')
|
26
|
+
stream.close_connection_after_writing
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def starttls?(node)
|
33
|
+
node.name == STARTTLS && namespace(node) == NS
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
|
6
|
+
# Implements the XMPP protocol for trusted, external component (XEP-0114)
|
7
|
+
# streams. This serves connected streams using the jabber:component:accept
|
8
|
+
# namespace.
|
9
|
+
class Component < Stream
|
10
|
+
attr_reader :remote_domain
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
super
|
14
|
+
@remote_domain = nil
|
15
|
+
@stream_id = Kit.uuid
|
16
|
+
advance(Start.new(self))
|
17
|
+
end
|
18
|
+
|
19
|
+
def max_stanza_size
|
20
|
+
config[:component].max_stanza_size
|
21
|
+
end
|
22
|
+
|
23
|
+
def ready?
|
24
|
+
state.class == Component::Ready
|
25
|
+
end
|
26
|
+
|
27
|
+
def stream_type
|
28
|
+
:component
|
29
|
+
end
|
30
|
+
|
31
|
+
def start(node)
|
32
|
+
@remote_domain = node['to']
|
33
|
+
send_stream_header
|
34
|
+
raise StreamErrors::ImproperAddressing unless valid_address?(@remote_domain)
|
35
|
+
raise StreamErrors::HostUnknown unless config.component?(@remote_domain)
|
36
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:component]
|
37
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
|
38
|
+
end
|
39
|
+
|
40
|
+
def secret
|
41
|
+
password = config.component_password(@remote_domain)
|
42
|
+
Digest::SHA1.hexdigest(@stream_id + password)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def send_stream_header
|
48
|
+
attrs = {
|
49
|
+
'xmlns' => NAMESPACES[:component],
|
50
|
+
'xmlns:stream' => NAMESPACES[:stream],
|
51
|
+
'id' => @stream_id,
|
52
|
+
'from' => @remote_domain
|
53
|
+
}
|
54
|
+
write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|