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.
- 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
|