vines 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class AuthRestart < State
7
+ def initialize(stream, success=Auth)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ doc = Document.new
15
+ features = doc.create_element('stream:features') do |el|
16
+ el << doc.create_element('mechanisms') do |parent|
17
+ parent.default_namespace = NAMESPACES[:sasl]
18
+ mechanisms.each {|name| parent << doc.create_element('mechanism', name) }
19
+ end
20
+ end
21
+ stream.write(features)
22
+ advance
23
+ end
24
+
25
+ private
26
+
27
+ def mechanisms
28
+ ['EXTERNAL', 'PLAIN']
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class Bind < State
7
+ NS = NAMESPACES[:bind]
8
+ MAX_ATTEMPTS = 5
9
+
10
+ def initialize(stream, success=Ready)
11
+ super
12
+ @attempts = 0
13
+ end
14
+
15
+ def node(node)
16
+ @attempts += 1
17
+ raise StreamErrors::NotAuthorized unless bind?(node)
18
+ raise StreamErrors::PolicyViolation.new('max bind attempts reached') if @attempts > MAX_ATTEMPTS
19
+ raise StanzaErrors::ResourceConstraint.new(node, 'wait') if resource_limit_reached?
20
+ stream.user.jid.resource = resource(node)
21
+ doc = Document.new
22
+ result = doc.create_element('iq', 'id' => node['id'], 'type' => 'result') do |el|
23
+ el << doc.create_element('bind') do |bind|
24
+ bind.default_namespace = NS
25
+ bind << doc.create_element('jid', stream.user.jid.to_s)
26
+ end
27
+ end
28
+ stream.write(result)
29
+ stream.write('<stream:features/>')
30
+ advance
31
+ end
32
+
33
+ private
34
+
35
+ def bind?(node)
36
+ node.name == 'iq' && node['type'] == 'set' && node.xpath('ns:bind', 'ns' => NS).any?
37
+ end
38
+
39
+ def resource(node)
40
+ el = node.xpath('ns:bind/ns:resource', 'ns' => NS).first
41
+ resource = el ? el.text.strip : ''
42
+ resource.empty? || resource_used?(resource) ? Kit.uuid : resource
43
+ end
44
+
45
+ def resource_limit_reached?
46
+ used = stream.router.connected_resources(stream.user.jid.bare).size
47
+ used >= stream.max_resources_per_account
48
+ end
49
+
50
+ def resource_used?(resource)
51
+ stream.router.available_resources(stream.user.jid).any? do |c|
52
+ c.user.jid.resource == resource
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class BindRestart < State
7
+ def initialize(stream, success=Bind)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ doc = Document.new
15
+ features = doc.create_element('stream:features') do |el|
16
+ el << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
17
+ el << doc.create_element('session', 'xmlns' => NAMESPACES[:session])
18
+ end
19
+ stream.write(features)
20
+ advance
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class Closed < State
7
+ def node(node)
8
+ # ignore data received after close_connection
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class Ready < State
7
+ def node(node)
8
+ stanza = to_stanza(node)
9
+ raise StreamErrors::UnsupportedStanzaType unless stanza
10
+ stanza.process
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class Start < State
7
+ def initialize(stream, success=TLS)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ doc = Document.new
15
+ features = doc.create_element('stream:features') do |el|
16
+ el << doc.create_element('starttls') do |tls|
17
+ tls.default_namespace = NAMESPACES[:tls]
18
+ tls << doc.create_element('required')
19
+ end
20
+ end
21
+ stream.write(features)
22
+ advance
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ class TLS < State
7
+ NS = NAMESPACES[:tls]
8
+ PROCEED = %Q{<proceed xmlns="#{NS}"/>}.freeze
9
+ FAILURE = %Q{<failure xmlns="#{NS}"/>}.freeze
10
+ STARTTLS = 'starttls'.freeze
11
+
12
+ def initialize(stream, success=AuthRestart)
13
+ super
14
+ end
15
+
16
+ def node(node)
17
+ raise StreamErrors::NotAuthorized unless starttls?(node)
18
+ if stream.encrypt?
19
+ stream.write(PROCEED)
20
+ stream.encrypt
21
+ advance
22
+ else
23
+ stream.write(FAILURE)
24
+ stream.write('</stream:stream>')
25
+ stream.close_connection_after_writing
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def starttls?(node)
32
+ node.name == STARTTLS && namespace(node) == NS
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+
6
+ # Implements the XMPP protocol for trusted, external component (XEP-0114)
7
+ # streams. This serves connected streams using the jabber:component:accept
8
+ # namespace.
9
+ class Component < Stream
10
+ attr_reader :config, :remote_domain
11
+
12
+ def initialize(config)
13
+ @config = config
14
+ @remote_domain = nil
15
+ @stream_id = Kit.uuid
16
+ @state = Start.new(self)
17
+ end
18
+
19
+ def max_stanza_size
20
+ @config[:component].max_stanza_size
21
+ end
22
+
23
+ def ready?
24
+ @state.class == Component::Ready
25
+ end
26
+
27
+ def start(node)
28
+ @remote_domain = node['to']
29
+ send_stream_header
30
+ raise StreamErrors::HostUnknown unless @config[:component].password(@remote_domain)
31
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:component]
32
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
33
+ end
34
+
35
+ def secret
36
+ password = @config[:component].password(@remote_domain)
37
+ Digest::SHA1.hexdigest(@stream_id + password)
38
+ end
39
+
40
+ private
41
+
42
+ def send_stream_header
43
+ attrs = {
44
+ 'xmlns' => NAMESPACES[:component],
45
+ 'xmlns:stream' => NAMESPACES[:stream],
46
+ 'id' => @stream_id,
47
+ 'from' => @remote_domain
48
+ }
49
+ write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Handshake < State
7
+ def initialize(stream, success=Ready)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless handshake?(node)
13
+ stream.write('<handshake/>')
14
+ advance
15
+ end
16
+
17
+ private
18
+
19
+ def handshake?(node)
20
+ node.name == 'handshake' && node.text == stream.secret
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Ready < State
7
+ def node(node)
8
+ stanza = to_stanza(node)
9
+ raise StreamErrors::UnsupportedStanzaType unless stanza
10
+ to = (node['to'] || '').strip
11
+ from = JID.new(node['from'] || '')
12
+ raise StreamErrors::ImproperAddressing if to.empty? || from.domain != stream.remote_domain
13
+ if stanza.local?
14
+ stream.router.connected_resources(to).each do |recipient|
15
+ recipient.write(node)
16
+ end
17
+ else
18
+ stanza.route
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Start < State
7
+ def initialize(stream, success=Handshake)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ advance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,111 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http < Client
6
+ include Thin
7
+ include Vines::Log
8
+
9
+ attr_accessor :last_broadcast_presence, :last_activity
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ @domain = nil
14
+ @requested_roster = false
15
+ @available = false
16
+ @unbound = false
17
+ @last_broadcast_presence = nil
18
+ @request = Thin::Request.new
19
+ @@http_states ||= HttpStates.new
20
+ @state = Auth.new(self)
21
+ end
22
+
23
+ def user
24
+ @http_state.user
25
+ end
26
+
27
+ def user=(user)
28
+ @http_state.user = user
29
+ end
30
+
31
+ def receive_data(data)
32
+ #TODO: make sure we add max stanza size enforcement
33
+ if @request.parse(data)
34
+ process_http_request(@request)
35
+ @request = Thin::Request.new
36
+ end
37
+ rescue InvalidRequest => e
38
+ error(StreamErrors::NotWellFormed.new)
39
+ end
40
+
41
+ def write(data)
42
+ @http_state.write(data)
43
+ end
44
+
45
+ def setup_new_client(rid, domain)
46
+ sid = Kit.uuid
47
+ log.info("Setting up a new client SID: #{sid} for RID: #{rid}.")
48
+ @http_state = HttpState.new(self, sid, rid, domain)
49
+ @@http_states[sid] = @http_state
50
+ end
51
+
52
+ def unbind
53
+ #router.delete(@http_state)
54
+ log.info("HTTP Stream disconnected:\tfrom=#{@remote_addr}\tto=#{@local_addr}")
55
+ log.info("Streams connected: #{router.size}")
56
+ end
57
+
58
+ def process_http_request(request)
59
+ if request.body.string.empty?
60
+ #Respond to proxy servers' status pings
61
+ log.info("A status request has been received.")
62
+ send_data("Online")
63
+ close_connection_after_writing
64
+ return
65
+ end
66
+ body = Nokogiri::XML(request.body.string).root
67
+ body.namespace = nil
68
+ #TODO: Confirm this is a valid body stanza.
69
+ # If it isn't a body, return proxy ping result
70
+
71
+ if body['sid']
72
+ @http_state = @@http_states[body['sid']]
73
+ unless @http_state
74
+ log.info("Client was not found #{body['sid']}")
75
+ send_bosh_error
76
+ return
77
+ end
78
+ @domain = @http_state.domain
79
+ @user = @http_state.user
80
+ @http_state.request(body['rid'])
81
+ if body['restart']
82
+ @http_state.handle_restart
83
+ router << @http_state
84
+ @state = Bind.new(self)
85
+ end
86
+
87
+ body.elements.each do |node|
88
+ @nodes.push(Nokogiri::XML(node.to_s.sub(' xmlns="jabber:client"', '')).root)
89
+ end
90
+ else
91
+ self.setup_new_client(body['rid'], body['to'])
92
+ end
93
+ end
94
+
95
+ def send_bosh_error
96
+ body = "<body type='terminate' condition='remote-connection-failed' xmlns='http://jabber.org/protocol/httpbind'/>"
97
+ header = [
98
+ "HTTP/1.1 404 OK",
99
+ "Content-Type: text/xml; charset=utf-8",
100
+ "Content-Length: #{body.bytesize}"
101
+ ].join("\r\n")
102
+
103
+ send_data([header, body].join("\r\n\r\n"))
104
+ end
105
+
106
+ def domain
107
+ @http_state.domain
108
+ end
109
+ end
110
+ end
111
+ end