vines 0.4.9 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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