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,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 contact?(jid)
38
+ !contact(jid).nil?
39
+ end
40
+
41
+ # Returns the contact with this jid or nil if not found.
42
+ def contact(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 contact's
48
+ # presence updates.
49
+ def subscribed_to?(jid)
50
+ contact = contact(jid)
51
+ contact && contact.subscribed_to?
52
+ end
53
+
54
+ # Returns true if the user has a presence subscription from this contact.
55
+ # The contact is subscribed to this user's presence.
56
+ def subscribed_from?(jid)
57
+ contact = contact(jid)
58
+ contact && contact.subscribed_from?
59
+ end
60
+
61
+ # Removes the contact with this jid from the user's roster.
62
+ def remove_contact(jid)
63
+ bare = JID.new(jid).bare
64
+ @roster.reject! {|c| c.jid.bare == bare }
65
+ end
66
+
67
+ # Returns a list of the contacts to which this user has
68
+ # successfully subscribed.
69
+ def subscribed_to_contacts
70
+ @roster.select {|c| c.subscribed_to? }
71
+ end
72
+
73
+ # Returns a list of the contacts that are subscribed to this user's
74
+ # presence updates.
75
+ def subscribed_from_contacts
76
+ @roster.select {|c| c.subscribed_from? }
77
+ end
78
+
79
+ # Update the contact's jid on this user's roster to signal that this user
80
+ # has requested the contact's permission to receive their presence updates.
81
+ def request_subscription(jid)
82
+ unless contact = contact(jid)
83
+ contact = Contact.new(:jid => jid)
84
+ @roster << contact
85
+ end
86
+ contact.ask = 'subscribe' if %w[none from].include?(contact.subscription)
87
+ end
88
+
89
+ # Add the user's jid to this contact's roster with a subscription state of
90
+ # 'from.' This signals that this contact has approved a user's subscription.
91
+ def add_subscription_from(jid)
92
+ unless contact = contact(jid)
93
+ contact = Contact.new(:jid => jid)
94
+ @roster << contact
95
+ end
96
+ contact.subscribe_from
97
+ end
98
+
99
+ def remove_subscription_to(jid)
100
+ if contact = contact(jid)
101
+ contact.unsubscribe_to
102
+ end
103
+ end
104
+
105
+ def remove_subscription_from(jid)
106
+ if contact = contact(jid)
107
+ contact.unsubscribe_from
108
+ end
109
+ end
110
+
111
+ # Returns this user's roster contacts 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 |contact|
117
+ query << contact.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.2'
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