lygneo-vines 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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/daemon.rb +78 -0
  30. data/lib/vines/error.rb +150 -0
  31. data/lib/vines/follower.rb +111 -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/error_test.rb +58 -0
  125. data/test/ext/nokogiri.rb +14 -0
  126. data/test/follower_test.rb +102 -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,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
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+
6
+ # The base class of Stream state machines. States know how to process XML
7
+ # nodes and advance to their next valid state or fail the stream.
8
+ class State
9
+ include Nokogiri::XML
10
+ include Vines::Log
11
+
12
+ attr_accessor :stream
13
+
14
+ BODY = 'body'.freeze
15
+ STREAM = 'stream'.freeze
16
+
17
+ def initialize(stream, success=nil)
18
+ @stream, @success = stream, success
19
+ end
20
+
21
+ def node(node)
22
+ raise 'subclass must implement'
23
+ end
24
+
25
+ def ==(state)
26
+ self.class == state.class
27
+ end
28
+
29
+ def eql?(state)
30
+ state.is_a?(State) && self == state
31
+ end
32
+
33
+ def hash
34
+ self.class.hash
35
+ end
36
+
37
+ private
38
+
39
+ def advance
40
+ stream.advance(@success.new(stream))
41
+ end
42
+
43
+ def stream?(node)
44
+ node.name == STREAM && namespace(node) == NAMESPACES[:stream]
45
+ end
46
+
47
+ def body?(node)
48
+ node.name == BODY && namespace(node) == NAMESPACES[:http_bind]
49
+ end
50
+
51
+ def namespace(node)
52
+ node.namespace ? node.namespace.href : nil
53
+ end
54
+
55
+ def to_stanza(node)
56
+ Stanza.from_node(node, stream)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,247 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ # The base class for various XMPP streams (c2s, s2s, component, http),
5
+ # containing behavior common to all streams like rate limiting, stanza
6
+ # parsing, and stream error handling.
7
+ class Stream < EventMachine::Connection
8
+ include Vines::Log
9
+
10
+ ERROR = 'error'.freeze
11
+ PAD = 20
12
+
13
+ attr_reader :config, :domain
14
+ attr_accessor :user
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ def post_init
21
+ @remote_addr, @local_addr = addresses
22
+ @user, @closed, @stanza_size = nil, false, 0
23
+ @bucket = TokenBucket.new(100, 10)
24
+ @store = Store.new(@config.certs)
25
+ @nodes = EM::Queue.new
26
+ process_node_queue
27
+ create_parser
28
+ log.info { "%s %21s -> %s" %
29
+ ['Stream connected:'.ljust(PAD), @remote_addr, @local_addr] }
30
+ end
31
+
32
+ # Initialize a new XML parser for this connection. This is called when the
33
+ # stream is first connected as well as for stream restarts during
34
+ # negotiation. Subclasses can override this method to provide a different
35
+ # type of parser (e.g. HTTP).
36
+ def create_parser
37
+ @parser = Parser.new.tap do |p|
38
+ p.stream_open {|node| @nodes.push(node) }
39
+ p.stream_close { close_connection }
40
+ p.stanza {|node| @nodes.push(node) }
41
+ end
42
+ end
43
+
44
+ # Advance the state machine into the +Closed+ state so any remaining queued
45
+ # nodes are not processed while we're waiting for EM to actually close the
46
+ # connection.
47
+ def close_connection(after_writing=false)
48
+ super
49
+ @closed = true
50
+ advance(Client::Closed.new(self))
51
+ end
52
+
53
+ def receive_data(data)
54
+ return if @closed
55
+ @stanza_size += data.bytesize
56
+ if @stanza_size < max_stanza_size
57
+ @parser << data rescue error(StreamErrors::NotWellFormed.new)
58
+ else
59
+ error(StreamErrors::PolicyViolation.new('max stanza size reached'))
60
+ end
61
+ end
62
+
63
+ # Reset the connection's XML parser when a new <stream:stream> header
64
+ # is received.
65
+ def reset
66
+ create_parser
67
+ end
68
+
69
+ # Returns the storage system for the domain. If no domain is given,
70
+ # the stream's storage mechanism is returned.
71
+ def storage(domain=nil)
72
+ @config.storage(domain || self.domain)
73
+ end
74
+
75
+ # Returns the Vines::Config::Host virtual host for the stream's domain.
76
+ def vhost
77
+ @config.vhost(domain)
78
+ end
79
+
80
+ # Reload the user's information into their active connections. Call this
81
+ # after storage.save_user() to sync the new user state with their other
82
+ # connections.
83
+ def update_user_streams(user)
84
+ connected_resources(user.jid.bare).each do |stream|
85
+ stream.user.update_from(user)
86
+ end
87
+ end
88
+
89
+ def connected_resources(jid)
90
+ router.connected_resources(jid, user.jid)
91
+ end
92
+
93
+ def available_resources(*jid)
94
+ router.available_resources(*jid, user.jid)
95
+ end
96
+
97
+ def interested_resources(*jid)
98
+ router.interested_resources(*jid, user.jid)
99
+ end
100
+
101
+ def ssl_verify_peer(pem)
102
+ # EM is supposed to close the connection when this returns false,
103
+ # but it only does that for inbound connections, not when we
104
+ # make a connection to another server.
105
+ @store.trusted?(pem).tap do |trusted|
106
+ close_connection unless trusted
107
+ end
108
+ end
109
+
110
+ def cert_domain_matches?(domain)
111
+ @store.domain?(get_peer_cert, domain)
112
+ end
113
+
114
+ # Send the data over the wire to this client.
115
+ def write(data)
116
+ log_node(data, :out)
117
+ if data.respond_to?(:to_xml)
118
+ data = data.to_xml(:indent => 0)
119
+ end
120
+ send_data(data)
121
+ end
122
+
123
+ def encrypt
124
+ cert, key = @store.files_for_domain(domain)
125
+ start_tls(cert_chain_file: cert, private_key_file: key, verify_peer: true)
126
+ end
127
+
128
+ # Returns true if the TLS certificate and private key files for this domain
129
+ # exist and can be used to encrypt this stream.
130
+ def encrypt?
131
+ !@store.files_for_domain(domain).nil?
132
+ end
133
+
134
+ def unbind
135
+ router.delete(self)
136
+ log.info { "%s %21s -> %s" %
137
+ ['Stream disconnected:'.ljust(PAD), @remote_addr, @local_addr] }
138
+ log.info { "Streams connected: #{router.size}" }
139
+ end
140
+
141
+ # Advance the stream's state machine to the new state. XML nodes received
142
+ # by the stream will be passed to this state's +node+ method.
143
+ def advance(state)
144
+ @state = state
145
+ end
146
+
147
+ # Stream level errors close the stream while stanza and SASL errors are
148
+ # written to the client and leave the stream open. All exceptions should
149
+ # pass through this method for consistent handling.
150
+ def error(e)
151
+ case e
152
+ when SaslError, StanzaError
153
+ write(e.to_xml)
154
+ when StreamError
155
+ send_stream_error(e)
156
+ close_stream
157
+ else
158
+ log.error(e)
159
+ send_stream_error(StreamErrors::InternalServerError.new)
160
+ close_stream
161
+ end
162
+ end
163
+
164
+ def router
165
+ @config.router
166
+ end
167
+
168
+ private
169
+
170
+ # Return the remote and local socket addresses used by this connection.
171
+ def addresses
172
+ [get_peername, get_sockname].map do |addr|
173
+ addr ? Socket.unpack_sockaddr_in(addr)[0, 2].reverse.join(':') : 'unknown'
174
+ end
175
+ end
176
+
177
+ # Write the StreamError's xml to the stream. Subclasses can override
178
+ # this method with custom error writing behavior.
179
+ def send_stream_error(e)
180
+ write(e.to_xml)
181
+ end
182
+
183
+ # Write a closing stream tag to the stream then close the stream. Subclasses
184
+ # can override this method for custom close behavior.
185
+ def close_stream
186
+ write('</stream:stream>')
187
+ close_connection_after_writing
188
+ end
189
+
190
+ def error?(node)
191
+ ns = node.namespace ? node.namespace.href : nil
192
+ node.name == ERROR && ns == NAMESPACES[:stream]
193
+ end
194
+
195
+ # Schedule a queue pop on the EM thread to handle the next element. This
196
+ # guarantees all stanzas received on this stream are processed in order.
197
+ # http://tools.ietf.org/html/rfc6120#section-10.1
198
+ def process_node_queue
199
+ @nodes.pop do |node|
200
+ Fiber.new do
201
+ process_node(node)
202
+ process_node_queue
203
+ end.resume unless @closed
204
+ end
205
+ end
206
+
207
+ def process_node(node)
208
+ log_node(node, :in)
209
+ @stanza_size = 0
210
+ enforce_rate_limit
211
+ if error?(node)
212
+ close_stream
213
+ else
214
+ state.node(node)
215
+ end
216
+ rescue => e
217
+ error(e)
218
+ end
219
+
220
+ def enforce_rate_limit
221
+ unless @bucket.take(1)
222
+ raise StreamErrors::PolicyViolation.new('rate limit exceeded')
223
+ end
224
+ end
225
+
226
+ def log_node(node, direction)
227
+ return unless log.debug?
228
+ from, to = @remote_addr, @local_addr
229
+ from, to = to, from if direction == :out
230
+ label = (direction == :out) ? 'Sent' : 'Received'
231
+ log.debug("%s %21s -> %s\n%s\n" %
232
+ ["#{label} stanza:".ljust(PAD), from, to, node])
233
+ end
234
+
235
+ # Returns the current +State+ of the stream's state machine. Provided as a
236
+ # method so subclasses can override the behavior.
237
+ def state
238
+ @state
239
+ end
240
+
241
+ # Return +true+ if this is a valid domain-only JID that can be used in
242
+ # stream initiation stanza headers.
243
+ def valid_address?(jid)
244
+ JID.new(jid).domain? rescue false
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+
5
+ # The token bucket algorithm is useful for rate limiting.
6
+ # Before an operation can be completed, a token is taken from
7
+ # the bucket. If no tokens are available, the operation fails.
8
+ # The bucket is refilled with tokens at the maximum allowed rate
9
+ # of operations.
10
+ class TokenBucket
11
+
12
+ # Create a full bucket with `capacity` number of tokens to be filled
13
+ # at the given rate of tokens/second.
14
+ #
15
+ # capacity - The Fixnum maximum number of tokens the bucket can hold.
16
+ # rate - The Fixnum number of tokens per second at which the bucket is
17
+ # refilled.
18
+ def initialize(capacity, rate)
19
+ raise ArgumentError.new('capacity must be > 0') unless capacity > 0
20
+ raise ArgumentError.new('rate must be > 0') unless rate > 0
21
+ @capacity = capacity
22
+ @tokens = capacity
23
+ @rate = rate
24
+ @timestamp = Time.new
25
+ end
26
+
27
+ # Remove tokens from the bucket if it's full enough. There's no way, or
28
+ # need, to add tokens to the bucket. It refills over time.
29
+ #
30
+ # tokens - The Fixnum number of tokens to attempt to take from the bucket.
31
+ #
32
+ # Returns true if the bucket contains enough tokens to take, false if the
33
+ # bucket isn't full enough to satisy the request.
34
+ def take(tokens)
35
+ raise ArgumentError.new('tokens must be > 0') unless tokens > 0
36
+ tokens <= fill ? @tokens -= tokens : false
37
+ end
38
+
39
+ private
40
+
41
+ # Add tokens to the bucket at the `rate` provided in the constructor. This
42
+ # fills the bucket slowly over time.
43
+ #
44
+ # Returns the Fixnum number of tokens left in the bucket.
45
+ def fill
46
+ if @tokens < @capacity
47
+ now = Time.new
48
+ @tokens += (@rate * (now - @timestamp)).round
49
+ @tokens = @capacity if @tokens > @capacity
50
+ @timestamp = now
51
+ end
52
+ @tokens
53
+ end
54
+ end
55
+ end
data/lib/vines/user.rb ADDED
@@ -0,0 +1,123 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class User
5
+ include Comparable
6
+
7
+ attr_accessor :name, :password, :roster
8
+ attr_reader :jid
9
+
10
+ def initialize(args={})
11
+ @jid = JID.new(args[:jid])
12
+ raise ArgumentError, 'invalid jid' if @jid.empty?
13
+
14
+ @name = args[:name]
15
+ @password = args[:password]
16
+ @roster = args[:roster] || []
17
+ end
18
+
19
+ def <=>(user)
20
+ user.is_a?(User) ? self.jid.to_s <=> user.jid.to_s : nil
21
+ end
22
+
23
+ alias :eql? :==
24
+
25
+ def hash
26
+ jid.to_s.hash
27
+ end
28
+
29
+ # Update this user's information from the given user object.
30
+ def update_from(user)
31
+ @name = user.name
32
+ @password = user.password
33
+ @roster = user.roster.map {|c| c.clone }
34
+ end
35
+
36
+ # Return true if the jid is on this user's roster.
37
+ def follower?(jid)
38
+ !follower(jid).nil?
39
+ end
40
+
41
+ # Returns the follower with this jid or nil if not found.
42
+ def follower(jid)
43
+ bare = JID.new(jid).bare
44
+ @roster.find {|c| c.jid.bare == bare }
45
+ end
46
+
47
+ # Returns true if the user is subscribed to this follower's
48
+ # presence updates.
49
+ def subscribed_to?(jid)
50
+ follower = follower(jid)
51
+ follower && follower.subscribed_to?
52
+ end
53
+
54
+ # Returns true if the user has a presence subscription from this follower.
55
+ # The follower is subscribed to this user's presence.
56
+ def subscribed_from?(jid)
57
+ follower = follower(jid)
58
+ follower && follower.subscribed_from?
59
+ end
60
+
61
+ # Removes the follower with this jid from the user's roster.
62
+ def remove_follower(jid)
63
+ bare = JID.new(jid).bare
64
+ @roster.reject! {|c| c.jid.bare == bare }
65
+ end
66
+
67
+ # Returns a list of the followers to which this user has
68
+ # successfully subscribed.
69
+ def subscribed_to_followers
70
+ @roster.select {|c| c.subscribed_to? }
71
+ end
72
+
73
+ # Returns a list of the followers that are subscribed to this user's
74
+ # presence updates.
75
+ def subscribed_from_followers
76
+ @roster.select {|c| c.subscribed_from? }
77
+ end
78
+
79
+ # Update the follower's jid on this user's roster to signal that this user
80
+ # has requested the follower's permission to receive their presence updates.
81
+ def request_subscription(jid)
82
+ unless follower = follower(jid)
83
+ follower = Follower.new(:jid => jid)
84
+ @roster << follower
85
+ end
86
+ follower.ask = 'subscribe' if %w[none from].include?(follower.subscription)
87
+ end
88
+
89
+ # Add the user's jid to this follower's roster with a subscription state of
90
+ # 'from.' This signals that this follower has approved a user's subscription.
91
+ def add_subscription_from(jid)
92
+ unless follower = follower(jid)
93
+ follower = Follower.new(:jid => jid)
94
+ @roster << follower
95
+ end
96
+ follower.subscribe_from
97
+ end
98
+
99
+ def remove_subscription_to(jid)
100
+ if follower = follower(jid)
101
+ follower.unsubscribe_to
102
+ end
103
+ end
104
+
105
+ def remove_subscription_from(jid)
106
+ if follower = follower(jid)
107
+ follower.unsubscribe_from
108
+ end
109
+ end
110
+
111
+ # Returns this user's roster followers as an iq query element.
112
+ def to_roster_xml(id)
113
+ doc = Nokogiri::XML::Document.new
114
+ doc.create_element('iq', 'id' => id, 'type' => 'result') do |el|
115
+ el << doc.create_element('query', 'xmlns' => 'jabber:iq:roster') do |query|
116
+ @roster.sort!.each do |follower|
117
+ query << follower.to_roster_xml
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ # vines forked version 0.4.9
5
+ VERSION = '0.1.1'
6
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+
5
+ # The main starting point for the XMPP server process. Starts the
6
+ # EventMachine processing loop and registers the XMPP protocol handler
7
+ # with the ports defined in the server configuration file.
8
+ class XmppServer
9
+ include Vines::Log
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def start
16
+ log.info('XMPP server started')
17
+ at_exit { log.fatal('XMPP server stopped') }
18
+ EM.epoll
19
+ EM.kqueue
20
+ EM.run do
21
+ @config.ports.each {|port| port.start }
22
+ end
23
+ end
24
+ end
25
+ end