vines 0.1.0

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 (119) hide show
  1. data/LICENSE +19 -0
  2. data/README +34 -0
  3. data/Rakefile +55 -0
  4. data/bin/vines +95 -0
  5. data/conf/certs/README +32 -0
  6. data/conf/certs/ca-bundle.crt +3987 -0
  7. data/conf/config.rb +114 -0
  8. data/lib/vines.rb +155 -0
  9. data/lib/vines/command/bcrypt.rb +12 -0
  10. data/lib/vines/command/cert.rb +49 -0
  11. data/lib/vines/command/init.rb +58 -0
  12. data/lib/vines/command/ldap.rb +35 -0
  13. data/lib/vines/command/restart.rb +12 -0
  14. data/lib/vines/command/schema.rb +24 -0
  15. data/lib/vines/command/start.rb +28 -0
  16. data/lib/vines/command/stop.rb +18 -0
  17. data/lib/vines/config.rb +191 -0
  18. data/lib/vines/contact.rb +99 -0
  19. data/lib/vines/daemon.rb +78 -0
  20. data/lib/vines/error.rb +150 -0
  21. data/lib/vines/jid.rb +56 -0
  22. data/lib/vines/kit.rb +23 -0
  23. data/lib/vines/router.rb +125 -0
  24. data/lib/vines/stanza.rb +55 -0
  25. data/lib/vines/stanza/iq.rb +50 -0
  26. data/lib/vines/stanza/iq/auth.rb +18 -0
  27. data/lib/vines/stanza/iq/disco_info.rb +25 -0
  28. data/lib/vines/stanza/iq/disco_items.rb +23 -0
  29. data/lib/vines/stanza/iq/error.rb +16 -0
  30. data/lib/vines/stanza/iq/ping.rb +16 -0
  31. data/lib/vines/stanza/iq/query.rb +10 -0
  32. data/lib/vines/stanza/iq/result.rb +16 -0
  33. data/lib/vines/stanza/iq/roster.rb +153 -0
  34. data/lib/vines/stanza/iq/session.rb +22 -0
  35. data/lib/vines/stanza/iq/vcard.rb +58 -0
  36. data/lib/vines/stanza/message.rb +41 -0
  37. data/lib/vines/stanza/presence.rb +119 -0
  38. data/lib/vines/stanza/presence/error.rb +23 -0
  39. data/lib/vines/stanza/presence/probe.rb +38 -0
  40. data/lib/vines/stanza/presence/subscribe.rb +66 -0
  41. data/lib/vines/stanza/presence/subscribed.rb +64 -0
  42. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  43. data/lib/vines/stanza/presence/unsubscribe.rb +57 -0
  44. data/lib/vines/stanza/presence/unsubscribed.rb +50 -0
  45. data/lib/vines/storage.rb +216 -0
  46. data/lib/vines/storage/couchdb.rb +119 -0
  47. data/lib/vines/storage/ldap.rb +59 -0
  48. data/lib/vines/storage/local.rb +66 -0
  49. data/lib/vines/storage/redis.rb +108 -0
  50. data/lib/vines/storage/sql.rb +174 -0
  51. data/lib/vines/store.rb +51 -0
  52. data/lib/vines/stream.rb +198 -0
  53. data/lib/vines/stream/client.rb +131 -0
  54. data/lib/vines/stream/client/auth.rb +94 -0
  55. data/lib/vines/stream/client/auth_restart.rb +33 -0
  56. data/lib/vines/stream/client/bind.rb +58 -0
  57. data/lib/vines/stream/client/bind_restart.rb +25 -0
  58. data/lib/vines/stream/client/closed.rb +13 -0
  59. data/lib/vines/stream/client/ready.rb +15 -0
  60. data/lib/vines/stream/client/start.rb +27 -0
  61. data/lib/vines/stream/client/tls.rb +37 -0
  62. data/lib/vines/stream/component.rb +53 -0
  63. data/lib/vines/stream/component/handshake.rb +25 -0
  64. data/lib/vines/stream/component/ready.rb +24 -0
  65. data/lib/vines/stream/component/start.rb +19 -0
  66. data/lib/vines/stream/http.rb +111 -0
  67. data/lib/vines/stream/http/http_request.rb +22 -0
  68. data/lib/vines/stream/http/http_state.rb +139 -0
  69. data/lib/vines/stream/http/http_states.rb +53 -0
  70. data/lib/vines/stream/parser.rb +78 -0
  71. data/lib/vines/stream/server.rb +126 -0
  72. data/lib/vines/stream/server/auth.rb +13 -0
  73. data/lib/vines/stream/server/auth_restart.rb +19 -0
  74. data/lib/vines/stream/server/final_restart.rb +20 -0
  75. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  76. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  77. data/lib/vines/stream/server/outbound/auth_result.rb +28 -0
  78. data/lib/vines/stream/server/outbound/final_features.rb +27 -0
  79. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  80. data/lib/vines/stream/server/outbound/start.rb +20 -0
  81. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  82. data/lib/vines/stream/server/outbound/tls_result.rb +31 -0
  83. data/lib/vines/stream/server/ready.rb +20 -0
  84. data/lib/vines/stream/server/start.rb +13 -0
  85. data/lib/vines/stream/server/tls.rb +13 -0
  86. data/lib/vines/stream/state.rb +55 -0
  87. data/lib/vines/token_bucket.rb +46 -0
  88. data/lib/vines/user.rb +124 -0
  89. data/lib/vines/version.rb +5 -0
  90. data/lib/vines/xmpp_server.rb +25 -0
  91. data/test/config_test.rb +396 -0
  92. data/test/error_test.rb +59 -0
  93. data/test/ext/nokogiri.rb +14 -0
  94. data/test/jid_test.rb +71 -0
  95. data/test/kit_test.rb +21 -0
  96. data/test/router_test.rb +60 -0
  97. data/test/stanza/iq/roster_test.rb +198 -0
  98. data/test/stanza/iq/session_test.rb +30 -0
  99. data/test/stanza/iq/vcard_test.rb +159 -0
  100. data/test/stanza/message_test.rb +124 -0
  101. data/test/stanza/presence/subscribe_test.rb +75 -0
  102. data/test/storage/couchdb_test.rb +102 -0
  103. data/test/storage/ldap_test.rb +207 -0
  104. data/test/storage/local_test.rb +54 -0
  105. data/test/storage/redis_test.rb +75 -0
  106. data/test/storage/sql_test.rb +55 -0
  107. data/test/storage/storage_tests.rb +134 -0
  108. data/test/storage_test.rb +90 -0
  109. data/test/stream/client/auth_test.rb +127 -0
  110. data/test/stream/client/ready_test.rb +47 -0
  111. data/test/stream/component/handshake_test.rb +46 -0
  112. data/test/stream/component/ready_test.rb +105 -0
  113. data/test/stream/component/start_test.rb +41 -0
  114. data/test/stream/parser_test.rb +121 -0
  115. data/test/stream/server/outbound/auth_test.rb +77 -0
  116. data/test/stream/server/ready_test.rb +100 -0
  117. data/test/token_bucket_test.rb +24 -0
  118. data/test/user_test.rb +64 -0
  119. metadata +318 -0
data/lib/vines/jid.rb ADDED
@@ -0,0 +1,56 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class JID
5
+ include Comparable
6
+
7
+ PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
8
+
9
+ attr_reader :node, :domain, :resource
10
+ attr_writer :resource
11
+
12
+ def self.new(node, domain=nil, resource=nil)
13
+ node.is_a?(JID) ? node : super
14
+ end
15
+
16
+ def initialize(node, domain=nil, resource=nil)
17
+ @node, @domain, @resource = node, domain, resource
18
+
19
+ if @domain.nil? && @resource.nil?
20
+ @node, @domain, @resource = @node.to_s.scan(PATTERN).first
21
+ end
22
+ [@node, @domain].each {|piece| piece.downcase! if piece }
23
+
24
+ [@node, @domain, @resource].each do |piece|
25
+ raise ArgumentError, 'jid too long' if (piece || '').size > 1023
26
+ end
27
+ end
28
+
29
+ def bare
30
+ JID.new(@node, @domain)
31
+ end
32
+
33
+ def bare?
34
+ @resource.nil?
35
+ end
36
+
37
+ def <=>(jid)
38
+ self.to_s <=> jid.to_s
39
+ end
40
+
41
+ def eql?(jid)
42
+ jid.is_a?(JID) && self == jid
43
+ end
44
+
45
+ def hash
46
+ self.to_s.hash
47
+ end
48
+
49
+ def to_s
50
+ s = @domain
51
+ s = "#{@node}@#{s}" if @node
52
+ s = "#{s}/#{@resource}" if @resource
53
+ s
54
+ end
55
+ end
56
+ end
data/lib/vines/kit.rb ADDED
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+
5
+ # A module for utility methods with no better home.
6
+ module Kit
7
+
8
+ # Create a hex-encoded, SHA-512 HMAC of the data, using the secret key.
9
+ def self.hmac(key, data)
10
+ digest = OpenSSL::Digest::Digest.new("sha512")
11
+ OpenSSL::HMAC.hexdigest(digest, key, data)
12
+ end
13
+
14
+ # Generates a random uuid per rfc 4122 that's useful for including in
15
+ # stream, iq, and other xmpp stanzas.
16
+ def self.uuid
17
+ hex = (0...16).map { "%02x" % rand(256) }.join
18
+ hex[12] = '4'
19
+ hex[16] = %w[8 9 a b][rand(4)]
20
+ hex.scan(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/).first.join('-')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ # The router tracks all stream connections to the server for all clients,
5
+ # servers, and components. It sends stanzas to the correct stream based on
6
+ # the 'to' attribute. Router is a singleton, shared by all streams, that must
7
+ # be accessed with +Router.instance+, not +Router.new+.
8
+ class Router
9
+ ROUTABLE_STANZAS = %w[message iq presence].freeze
10
+
11
+ @@instance = nil
12
+ def self.instance
13
+ @@instance ||= Router.new
14
+ end
15
+
16
+ def initialize
17
+ @config = nil
18
+ @streams = Hash.new {|h,k| h[k] = [] }
19
+ @pending = Hash.new {|h,k| h[k] = [] }
20
+ end
21
+
22
+ %w[Client Server Component].each do |klass|
23
+ name = klass.split(/(?=[A-Z])/).join('_').downcase
24
+ define_method(name + 's') do
25
+ @streams["Vines::Stream::#{klass}"]
26
+ end
27
+ end
28
+
29
+ def http_states
30
+ @streams["Vines::Stream::Http::HttpState"]
31
+ end
32
+
33
+ # Returns streams for all connected resources for this JID. A
34
+ # resource is considered connected after it has completed authentication
35
+ # and resource binding.
36
+ def connected_resources(jid)
37
+ jid = JID.new(jid)
38
+ (clients + http_states).select do |stream|
39
+ stream.connected? && jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid)
40
+ end
41
+ end
42
+
43
+ # Returns streams for all available resources for this JID. A
44
+ # resource is marked available after it sends initial presence.
45
+ # This method accepts a single JID or a list of JIDs.
46
+ def available_resources(*jid)
47
+ ids = jid.flatten.map {|jid| JID.new(jid).bare }
48
+ (clients + http_states).select do |stream|
49
+ stream.available? && ids.include?(stream.user.jid.bare)
50
+ end
51
+ end
52
+
53
+ # Returns streams for all interested resources for this JID. A
54
+ # resource is marked interested after it requests the roster.
55
+ # This method accepts a single JID or a list of JIDs.
56
+ def interested_resources(*jid)
57
+ ids = jid.flatten.map {|jid| JID.new(jid).bare }
58
+ (clients + http_states).select do |stream|
59
+ stream.interested? && ids.include?(stream.user.jid.bare)
60
+ end
61
+ end
62
+
63
+ # Add the connection to the routing table.
64
+ def <<(connection)
65
+ @config ||= connection.config
66
+ @streams[connection.class.to_s] << connection
67
+ end
68
+
69
+ # Remove the connection from the routing table.
70
+ def delete(connection)
71
+ @streams[connection.class.to_s].delete(connection)
72
+ end
73
+
74
+ # Send the stanza to the appropriate remote server-to-server stream
75
+ # or an external component stream.
76
+ def route(stanza)
77
+ to, from = %w[to from].map {|attr| JID.new(stanza[attr]) }
78
+ if stream = connection_to(to.domain)
79
+ stream.write(stanza)
80
+ elsif @pending.key?(to.domain)
81
+ @pending[to.domain] << stanza
82
+ elsif @config.s2s?(to.domain)
83
+ @pending[to.domain] << stanza
84
+ Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream|
85
+ if stream
86
+ @pending[to.domain].each {|s| stream.write(s) }
87
+ else
88
+ @pending[to.domain].each do |s|
89
+ xml = StanzaErrors::RemoteServerNotFound.new(s, 'cancel').to_xml
90
+ connected_resources(s['from']).each {|c| c.write(xml) }
91
+ end
92
+ end
93
+ @pending.delete(to.domain)
94
+ end
95
+ else
96
+ raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel')
97
+ end
98
+ end
99
+
100
+ # Returns true if this stanza should be processed locally. Returns false
101
+ # if it's destined for a remote domain or external component.
102
+ def local?(stanza)
103
+ return true unless ROUTABLE_STANZAS.include?(stanza.name)
104
+ to = (stanza['to'] || '').strip
105
+ to.empty? || local_jid?(to)
106
+ end
107
+
108
+ def local_jid?(jid)
109
+ @config.vhost?(JID.new(jid).domain)
110
+ end
111
+
112
+ # Returns the total number of streams connected to the server.
113
+ def size
114
+ @streams.values.inject(0) {|sum, arr| sum + arr.size }
115
+ end
116
+
117
+ private
118
+
119
+ def connection_to(domain)
120
+ (components + servers).find do |stream|
121
+ stream.ready? && stream.remote_domain == domain
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ include Nokogiri::XML
6
+
7
+ attr_reader :stream
8
+ MESSAGE = 'message'.freeze
9
+ @@types = {}
10
+
11
+ def self.register(xpath, ns={})
12
+ @@types[[xpath, ns]] = self
13
+ end
14
+
15
+ def self.from_node(node, stream)
16
+ # optimize common case
17
+ return Message.new(node, stream) if node.name == MESSAGE
18
+ found = @@types.select {|pair, v| node.xpath(*pair).any? }
19
+ .sort {|a, b| b[0][0].length - a[0][0].length }.first
20
+ found ? found[1].new(node, stream) : nil
21
+ end
22
+
23
+ def initialize(node, stream)
24
+ @node, @stream = node, stream
25
+ end
26
+
27
+ def broadcast(recipients)
28
+ stream.broadcast(@node, recipients)
29
+ end
30
+
31
+ def local?
32
+ stream.router.local?(@node)
33
+ end
34
+
35
+ def route
36
+ stream.router.route(@node)
37
+ end
38
+
39
+ def router
40
+ stream.router
41
+ end
42
+
43
+ def storage(domain=stream.domain)
44
+ stream.storage(domain)
45
+ end
46
+
47
+ def process
48
+ raise 'subclass must implement'
49
+ end
50
+
51
+ def method_missing(method, *args, &block)
52
+ @node.send(method, *args, &block)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq < Stanza
6
+ register "/iq"
7
+
8
+ VALID_TYPES = %w[get set result error].freeze
9
+
10
+ VALID_TYPES.each do |type|
11
+ define_method "#{type}?" do
12
+ self['type'] == type
13
+ end
14
+ end
15
+
16
+ def process
17
+ if self['id'] && VALID_TYPES.include?(self['type'])
18
+ raise StanzaErrors::FeatureNotImplemented.new(@node, 'cancel')
19
+ else
20
+ raise StanzaErrors::BadRequest.new(@node, 'modify')
21
+ end
22
+ end
23
+
24
+ def to_result
25
+ doc = Document.new
26
+ doc.create_element('iq',
27
+ 'from' => stream.domain,
28
+ 'id' => self['id'],
29
+ 'to' => stream.user.jid.to_s,
30
+ 'type' => 'result')
31
+ end
32
+
33
+ private
34
+
35
+ def route_iq
36
+ to = (self['to'] || '').strip
37
+ return false if to.empty? || to == stream.domain
38
+ self['from'] = stream.user.jid.to_s
39
+ if local?
40
+ router.available_resources(to).each do |recipient|
41
+ recipient.write(@node)
42
+ end
43
+ else
44
+ route
45
+ end
46
+ true
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Auth < Query
7
+ register "/iq[@id and @type='get']/ns:query", 'ns' => NAMESPACES[:non_sasl]
8
+
9
+ def process
10
+ # XEP-0078 says we MUST send a service-unavailable error
11
+ # here, but Adium 1.4.1 won't login if we do that, so just
12
+ # swallow this stanza.
13
+ # raise StanzaErrors::ServiceUnavailable.new(@node, 'cancel')
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class DiscoInfo < Query
7
+ NS = NAMESPACES[:disco_info]
8
+
9
+ register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq
13
+ result = to_result.tap do |el|
14
+ el << el.document.create_element('query') do |query|
15
+ query.default_namespace = NS
16
+ query << el.document.create_element('feature', 'var' => NAMESPACES[:ping])
17
+ query << el.document.create_element('feature', 'var' => NAMESPACES[:vcard])
18
+ end
19
+ end
20
+ stream.write(result)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class DiscoItems < Query
7
+ NS = NAMESPACES[:disco_items]
8
+
9
+ register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq
13
+ result = to_result.tap do |el|
14
+ el << el.document.create_element('query') do |query|
15
+ query.default_namespace = NS
16
+ end
17
+ end
18
+ stream.write(result)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Error < Iq
7
+ register "/iq[@id and @type='error']"
8
+
9
+ def process
10
+ return if route_iq
11
+ # do nothing
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Ping < Iq
7
+ register "/iq[@id and @type='get']/ns:ping", 'ns' => NAMESPACES[:ping]
8
+
9
+ def process
10
+ return if route_iq
11
+ stream.write(to_result)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Query < Iq
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Result < Iq
7
+ register "/iq[@id and @type='result']"
8
+
9
+ def process
10
+ return if route_iq
11
+ # do nothing
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end