vines 0.4.9 → 0.4.10

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.
@@ -1,7 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Vines
4
-
5
4
  # An X509 certificate store that validates certificate trust chains.
6
5
  # This uses the conf/certs/*.crt files as the list of trusted root
7
6
  # CA certificates.
@@ -10,19 +9,25 @@ module Vines
10
9
 
11
10
  # Create a certificate store to read certificate files from the given
12
11
  # directory.
12
+ #
13
+ # dir - The String directory name (absolute or relative).
13
14
  def initialize(dir)
14
15
  @dir = File.expand_path(dir)
15
16
  @store = OpenSSL::X509::Store.new
16
- certs.each {|c| @store.add_cert(c) }
17
+ certs.each {|cert| append(cert) }
17
18
  end
18
19
 
19
20
  # Return true if the certificate is signed by a CA certificate in the
20
21
  # store. If the certificate can be trusted, it's added to the store so
21
22
  # it can be used to trust other certs.
23
+ #
24
+ # pem - The PEM encoded certificate String.
25
+ #
26
+ # Returns true if the certificate is trusted.
22
27
  def trusted?(pem)
23
28
  if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
24
29
  @store.verify(cert).tap do |trusted|
25
- @store.add_cert(cert) if trusted rescue nil
30
+ append(cert) if trusted
26
31
  end
27
32
  end
28
33
  end
@@ -30,6 +35,11 @@ module Vines
30
35
  # Return true if the domain name matches one of the names in the
31
36
  # certificate. In other words, is the certificate provided to us really
32
37
  # for the domain to which we think we're connected?
38
+ #
39
+ # pem - The PEM encoded certificate String.
40
+ # domain - The domain name String.
41
+ #
42
+ # Returns true if the certificate was issued for the domain.
33
43
  def domain?(pem, domain)
34
44
  if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
35
45
  OpenSSL::SSL.verify_certificate_identity(cert, domain) rescue false
@@ -39,8 +49,10 @@ module Vines
39
49
  # Return the trusted root CA certificates installed in conf/certs. These
40
50
  # certificates are used to start the trust chain needed to validate certs
41
51
  # we receive from clients and servers.
52
+ #
53
+ # Returns an Array of OpenSSL::X509::Certificate objects.
42
54
  def certs
43
- unless @@sources
55
+ @@sources ||= begin
44
56
  pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
45
57
  pairs = Dir[File.join(@dir, '*.crt')].map do |name|
46
58
  File.open(name, "r:UTF-8") do |f|
@@ -50,7 +62,7 @@ module Vines
50
62
  [name, certs]
51
63
  end
52
64
  end
53
- @@sources = Hash[pairs]
65
+ Hash[pairs]
54
66
  end
55
67
  @@sources.values.flatten
56
68
  end
@@ -60,26 +72,43 @@ module Vines
60
72
  # wildcard certificate files to serve several subdomains.
61
73
  #
62
74
  # Finding the certificate and private key file for a domain follows these steps:
63
- # - look for <domain>.crt and <domain>.key files in the conf/certs directory.
64
- # if found, return those file names, else
65
- # - inspect all conf/certs/*.crt files for certificates that contain the
75
+ #
76
+ # - Look for <domain>.crt and <domain>.key files in the conf/certs
77
+ # directory. If found, return those file names, otherwise . . .
78
+ #
79
+ # - Inspect all conf/certs/*.crt files for certificates that contain the
66
80
  # domain name either as the subject common name (CN) or as a DNS
67
81
  # subjectAltName. The corresponding private key must be in a file of the
68
82
  # same name as the certificate's, but with a .key extension.
69
83
  #
70
- # So in the simplest configuration, the tea.wonderland.lit encryption files would
71
- # be named conf/certs/tea.wonderland.lit.crt and conf/certs/tea.wonderland.lit.key.
84
+ # So in the simplest configuration, the tea.wonderland.lit encryption files
85
+ # would be named:
86
+ #
87
+ # - conf/certs/tea.wonderland.lit.crt
88
+ # - conf/certs/tea.wonderland.lit.key
72
89
  #
73
- # However, in the case of a wildcard certificate for *.wonderland.lit, the
74
- # files would be conf/certs/wonderland.lit.crt and conf/certs/wonderland.lit.key.
75
- # These same two files would be returned for the subdomains of tea.wonderland.lit,
76
- # crumpets.wonderland.lit, etc.
90
+ # However, in the case of a wildcard certificate for *.wonderland.lit,
91
+ # the files would be:
92
+ #
93
+ # - conf/certs/wonderland.lit.crt
94
+ # - conf/certs/wonderland.lit.key
95
+ #
96
+ # These same two files would be returned for the subdomains of:
97
+ #
98
+ # - tea.wonderland.lit
99
+ # - crumpets.wonderland.lit
100
+ # - etc.
101
+ #
102
+ # domain - The String domain name.
103
+ #
104
+ # Returns a two element String array for the certificate and private key
105
+ # file names or nil if not found.
77
106
  def files_for_domain(domain)
78
107
  crt = File.expand_path("#{domain}.crt", @dir)
79
108
  key = File.expand_path("#{domain}.key", @dir)
80
109
  return [crt, key] if File.exists?(crt) && File.exists?(key)
81
110
 
82
- # might be a wildcard cert file
111
+ # Might be a wildcard cert file.
83
112
  @@sources.each do |file, certs|
84
113
  certs.each do |cert|
85
114
  if OpenSSL::SSL.verify_certificate_identity(cert, domain)
@@ -90,5 +119,19 @@ module Vines
90
119
  end
91
120
  nil
92
121
  end
122
+
123
+ private
124
+
125
+ # Add a trusted certificate to the store, suppressing any OpenSSL errors
126
+ # caused by the certificate already being stored.
127
+ #
128
+ # cert - The OpenSSL::X509::Certificate to add.
129
+ #
130
+ # Returns nothing.
131
+ def append(cert)
132
+ @store.add_cert(cert)
133
+ rescue OpenSSL::X509::StoreError
134
+ # Already added to store.
135
+ end
93
136
  end
94
137
  end
@@ -17,6 +17,11 @@ module Vines
17
17
  @config = config
18
18
  end
19
19
 
20
+ # Initialize the stream after its connection to the server has completed.
21
+ # EventMachine calls this method when an incoming connection is accepted
22
+ # into the event loop.
23
+ #
24
+ # Returns nothing.
20
25
  def post_init
21
26
  @remote_addr, @local_addr = addresses
22
27
  @user, @closed, @stanza_size = nil, false, 0
@@ -33,23 +38,34 @@ module Vines
33
38
  # stream is first connected as well as for stream restarts during
34
39
  # negotiation. Subclasses can override this method to provide a different
35
40
  # type of parser (e.g. HTTP).
41
+ #
42
+ # Returns nothing.
36
43
  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) }
44
+ @parser = Parser.new.tap do |parser|
45
+ parser.stream_open {|node| @nodes.push(node) }
46
+ parser.stream_close { close_connection }
47
+ parser.stanza {|node| @nodes.push(node) }
41
48
  end
42
49
  end
43
50
 
44
- # Advance the state machine into the +Closed+ state so any remaining queued
51
+ # Advance the state machine into the `Closed` state so any remaining queued
45
52
  # nodes are not processed while we're waiting for EM to actually close the
46
53
  # connection.
54
+ #
55
+ # Returns nothing.
47
56
  def close_connection(after_writing=false)
48
57
  super
49
58
  @closed = true
50
59
  advance(Client::Closed.new(self))
51
60
  end
52
61
 
62
+ # Read bytes off the stream and feed them into the XML parser. EventMachine
63
+ # is responsible for calling this method on its event loop as connections
64
+ # become readable.
65
+ #
66
+ # data - The byte String sent to the server from the client, hopefully XML.
67
+ #
68
+ # Returns nothing.
53
69
  def receive_data(data)
54
70
  return if @closed
55
71
  @stanza_size += data.bytesize
@@ -62,6 +78,8 @@ module Vines
62
78
 
63
79
  # Reset the connection's XML parser when a new <stream:stream> header
64
80
  # is received.
81
+ #
82
+ # Returns nothing.
65
83
  def reset
66
84
  create_parser
67
85
  end
@@ -72,7 +90,7 @@ module Vines
72
90
  @config.storage(domain || self.domain)
73
91
  end
74
92
 
75
- # Returns the Vines::Config::Host virtual host for the stream's domain.
93
+ # Returns the Config::Host virtual host for the stream's domain.
76
94
  def vhost
77
95
  @config.vhost(domain)
78
96
  end
@@ -80,6 +98,10 @@ module Vines
80
98
  # Reload the user's information into their active connections. Call this
81
99
  # after storage.save_user() to sync the new user state with their other
82
100
  # connections.
101
+ #
102
+ # user - The User whose connection info needs refreshing.
103
+ #
104
+ # Returns nothing.
83
105
  def update_user_streams(user)
84
106
  connected_resources(user.jid.bare).each do |stream|
85
107
  stream.user.update_from(user)
@@ -112,6 +134,10 @@ module Vines
112
134
  end
113
135
 
114
136
  # Send the data over the wire to this client.
137
+ #
138
+ # data - The XML String or XML::Node to write to the socket.
139
+ #
140
+ # Returns nothing.
115
141
  def write(data)
116
142
  log_node(data, :out)
117
143
  if data.respond_to?(:to_xml)
@@ -139,7 +165,11 @@ module Vines
139
165
  end
140
166
 
141
167
  # 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.
168
+ # by the stream will be passed to this state's `node` method.
169
+ #
170
+ # state - The Stream::State to process the stanzas next.
171
+ #
172
+ # Returns the new Stream::State.
143
173
  def advance(state)
144
174
  @state = state
145
175
  end
@@ -147,6 +177,10 @@ module Vines
147
177
  # Stream level errors close the stream while stanza and SASL errors are
148
178
  # written to the client and leave the stream open. All exceptions should
149
179
  # pass through this method for consistent handling.
180
+ #
181
+ # e - The StandardError, usually XmppError, that occurred.
182
+ #
183
+ # Returns nothing.
150
184
  def error(e)
151
185
  case e
152
186
  when SaslError, StanzaError
@@ -167,7 +201,9 @@ module Vines
167
201
 
168
202
  private
169
203
 
170
- # Return the remote and local socket addresses used by this connection.
204
+ # Determine the remote and local socket addresses used by this connection.
205
+ #
206
+ # Returns a two-element Array of String addresses.
171
207
  def addresses
172
208
  [get_peername, get_sockname].map do |addr|
173
209
  addr ? Socket.unpack_sockaddr_in(addr)[0, 2].reverse.join(':') : 'unknown'
@@ -176,12 +212,21 @@ module Vines
176
212
 
177
213
  # Write the StreamError's xml to the stream. Subclasses can override
178
214
  # this method with custom error writing behavior.
215
+ #
216
+ # A call to `close_stream` should follow this method. Stream level errors
217
+ # are fatal to the connection.
218
+ #
219
+ # e - The StreamError that caused the connection to close.
220
+ #
221
+ # Returns nothing.
179
222
  def send_stream_error(e)
180
223
  write(e.to_xml)
181
224
  end
182
225
 
183
- # Write a closing stream tag to the stream then close the stream. Subclasses
184
- # can override this method for custom close behavior.
226
+ # Write a closing stream tag and close the connection. Subclasses can
227
+ # override this method for custom close behavior.
228
+ #
229
+ # Returns nothing.
185
230
  def close_stream
186
231
  write('</stream:stream>')
187
232
  close_connection_after_writing
@@ -194,7 +239,14 @@ module Vines
194
239
 
195
240
  # Schedule a queue pop on the EM thread to handle the next element. This
196
241
  # guarantees all stanzas received on this stream are processed in order.
197
- # http://tools.ietf.org/html/rfc6120#section-10.1
242
+ #
243
+ # http://tools.ietf.org/html/rfc6120#section-10.1
244
+ #
245
+ # Once a node is processed, this method recursively schedules itself to pop
246
+ # the next node and so on. A single call to this method effectively begins
247
+ # an asynchronous node processing loop.
248
+ #
249
+ # Returns nothing.
198
250
  def process_node_queue
199
251
  @nodes.pop do |node|
200
252
  Fiber.new do
@@ -232,14 +284,20 @@ module Vines
232
284
  ["#{label} stanza:".ljust(PAD), from, to, node])
233
285
  end
234
286
 
235
- # Returns the current +State+ of the stream's state machine. Provided as a
287
+ # Inspects the current state of the stream's state machine. Provided as a
236
288
  # method so subclasses can override the behavior.
289
+ #
290
+ # Returns the current Stream::State.
237
291
  def state
238
292
  @state
239
293
  end
240
294
 
241
- # Return +true+ if this is a valid domain-only JID that can be used in
295
+ # Determine if this is a valid domain-only JID that can be used in
242
296
  # stream initiation stanza headers.
297
+ #
298
+ # jid - The String or JID to verify (e.g. 'wonderland.lit').
299
+ #
300
+ # Return true if the jid is domain-only.
243
301
  def valid_address?(jid)
244
302
  JID.new(jid).domain? rescue false
245
303
  end
@@ -60,21 +60,25 @@ module Vines
60
60
 
61
61
  private
62
62
 
63
- # The +to+ domain address set on the initial stream header must not change
63
+ # The `to` domain address set on the initial stream header must not change
64
64
  # during stream restarts. This prevents a user from authenticating in one
65
65
  # domain, then using a stream in a different domain.
66
+ #
67
+ # to - The String domain JID to verify (e.g. 'wonderland.lit').
68
+ #
69
+ # Returns true if the client connection is misbehaving and should be closed.
66
70
  def domain_change?(to)
67
71
  to != @session.domain
68
72
  end
69
73
 
70
74
  def send_stream_header(to)
71
75
  attrs = {
72
- 'xmlns' => NAMESPACES[:client],
76
+ 'xmlns' => NAMESPACES[:client],
73
77
  'xmlns:stream' => NAMESPACES[:stream],
74
- 'xml:lang' => 'en',
75
- 'id' => Kit.uuid,
76
- 'from' => @session.domain,
77
- 'version' => '1.0'
78
+ 'xml:lang' => 'en',
79
+ 'id' => Kit.uuid,
80
+ 'from' => @session.domain,
81
+ 'version' => '1.0'
78
82
  }
79
83
  attrs['to'] = to if to
80
84
  write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
@@ -46,10 +46,10 @@ module Vines
46
46
 
47
47
  def send_stream_header
48
48
  attrs = {
49
- 'xmlns' => NAMESPACES[:component],
49
+ 'xmlns' => NAMESPACES[:component],
50
50
  'xmlns:stream' => NAMESPACES[:stream],
51
- 'id' => @stream_id,
52
- 'from' => @remote_domain
51
+ 'id' => @stream_id,
52
+ 'from' => @remote_domain
53
53
  }
54
54
  write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
55
55
  end
@@ -10,22 +10,28 @@ module Vines
10
10
  @session = Http::Session.new(self)
11
11
  end
12
12
 
13
- # Override +Stream#create_parser+ to provide an HTTP parser rather than
13
+ # Override Stream#create_parser to provide an HTTP parser rather than
14
14
  # a Nokogiri XML parser.
15
+ #
16
+ # Returns nothing.
15
17
  def create_parser
16
- @parser = ::Http::Parser.new.tap do |p|
18
+ @parser = ::Http::Parser.new.tap do |parser|
17
19
  body = ''
18
- p.on_body = proc {|data| body << data }
19
- p.on_message_complete = proc {
20
+ parser.on_body = proc {|data| body << data }
21
+ parser.on_message_complete = proc do
20
22
  process_request(Request.new(self, @parser, body))
21
23
  body = ''
22
- }
24
+ end
23
25
  end
24
26
  end
25
27
 
26
28
  # If the session ID is valid, switch this stream's session to the new
27
29
  # ID and return true. Some clients, like Google Chrome, reuse one stream
28
30
  # for multiple sessions.
31
+ #
32
+ # sid - The String session ID.
33
+ #
34
+ # Returns true if the server previously distributed this SID to a client.
29
35
  def valid_session?(sid)
30
36
  if session = Sessions[sid]
31
37
  @session = session
@@ -62,13 +68,27 @@ module Vines
62
68
 
63
69
  # Override Stream#write to queue stanzas rather than immediately writing
64
70
  # to the stream. Stanza responses must be paired with a queued request.
71
+ #
72
+ # If a request is not waiting, the written stanzas will buffer until they
73
+ # can be sent in the next response.
74
+ #
75
+ # data - The XML String or XML::Node to write to the HTTP socket.
76
+ #
77
+ # Returns nothing.
65
78
  def write(data)
66
79
  @session.write(data)
67
80
  end
68
81
 
69
- # Return an array of Node objects inside the body element.
82
+ # Parse the one or more stanzas from a single body element. BOSH clients
83
+ # buffer stanzas sent in quick succession, and send them as a bundle, to
84
+ # save on the request/response cycle.
85
+ #
70
86
  # TODO This parses the XML again just to strip namespaces. Figure out
71
87
  # Nokogiri namespace handling instead.
88
+ #
89
+ # body - The XML::Node containing the BOSH `body` element.
90
+ #
91
+ # Returns an Array of XML::Node stanzas.
72
92
  def parse_body(body)
73
93
  body.namespace = nil
74
94
  body.elements.map do |node|
@@ -133,8 +153,12 @@ module Vines
133
153
  @session.reply(node)
134
154
  end
135
155
 
136
- # Override +Stream#send_stream_error+ to wrap the error XML in a BOSH
156
+ # Override Stream#send_stream_error to wrap the error XML in a BOSH
137
157
  # terminate body tag.
158
+ #
159
+ # e - The StreamError that caused the connection to close.
160
+ #
161
+ # Returns nothing.
138
162
  def send_stream_error(e)
139
163
  doc = Nokogiri::XML::Document.new
140
164
  node = doc.create_element('body',
@@ -146,12 +170,14 @@ module Vines
146
170
  @session.reply(node)
147
171
  end
148
172
 
149
- # Override +Stream#close_stream+ to simply close the connection without
173
+ # Override Stream#close_stream to simply close the connection without
150
174
  # writing a closing stream tag.
175
+ #
176
+ # Returns nothing.
151
177
  def close_stream
152
178
  close_connection_after_writing
153
179
  @session.close
154
180
  end
155
181
  end
156
182
  end
157
- end
183
+ end