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
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class HttpState
7
+ class HttpRequest
8
+ attr_accessor :rid
9
+
10
+ def initialize(rid)
11
+ @rid = rid
12
+ @received = Time.now
13
+ end
14
+
15
+ def timed_out?
16
+ Time.now - @received > 55
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,139 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class HttpState
7
+ include Nokogiri::XML
8
+
9
+ attr_reader :domain
10
+ attr_accessor :last_broadcast_presence, :expiration, :domain
11
+ attr_accessor :last_activity, :queued_stanzas, :queued_requests, :user
12
+
13
+ def initialize(stream, sid, rid, domain=nil)
14
+ @stream, @sid, @domain = stream, sid, domain
15
+ @last_activity = Time.now
16
+ @state = Stream::Client::Start.new(self)
17
+ @queued_stanzas = []
18
+ @queued_requests = []
19
+ @expiration = 65
20
+ @pinged = false
21
+ create_session(rid, sid)
22
+ end
23
+
24
+ def method_missing(method, *args, &block)
25
+ @stream.send(method, *args, &block)
26
+ end
27
+
28
+ def send_response(data, sid, rid)
29
+ doc = Document.new
30
+ body = doc.create_element('body',
31
+ 'rid' => rid,
32
+ 'sid' => sid,
33
+ 'xmlns' => NAMESPACES[:http_bind]) do |node|
34
+ node.inner_html = data
35
+ end.to_s
36
+ @stream.log_node(body, :out)
37
+ @stream.send_data([create_header(body.bytesize), body].join("\r\n\r\n"))
38
+ end
39
+
40
+ def create_header(content_length)
41
+ ["HTTP/1.1 200 OK",
42
+ "Content-Type: text/xml; charset=utf-8",
43
+ "Content-Length: #{content_length}"].join("\r\n")
44
+ end
45
+
46
+ def expired?
47
+ cleanup_requests
48
+ (Time.now - @last_activity > @expiration) && @queued_requests.empty?
49
+ end
50
+
51
+ def cleanup_requests
52
+ timed_out_requests.each do |request|
53
+ log.debug("Clearing out #{request.rid}")
54
+ send_response("", @sid, request.rid)
55
+ @queued_requests.delete(request)
56
+ end
57
+ end
58
+
59
+ def ping
60
+ log.debug("Pinging #{self}. Request queue: #{@queued_requests}")
61
+ @last_activity = Time.now
62
+ write("")
63
+ @pinged = true
64
+ end
65
+
66
+ def pinged?
67
+ @pinged
68
+ end
69
+
70
+ def create_session(rid, sid)
71
+ doc = Document.new
72
+ node = doc.create_element('body',
73
+ 'accept' => 'deflate,gzip',
74
+ 'ack' => rid,
75
+ 'charsets' => 'UTF-8',
76
+ 'from' => domain,
77
+ 'hold' => '1',
78
+ 'inactivity' => '30',
79
+ 'maxpause' => '120',
80
+ 'polling' => '5',
81
+ 'requests' => '2',
82
+ 'sid' => sid,
83
+ 'ver' => '1.6',
84
+ 'wait' => '60',
85
+ 'xmlns' => 'http://jabber.org/protocol/httpbind')
86
+
87
+ node << doc.create_element('features', 'xmlns' => 'jabber:client') do |el|
88
+ el << doc.create_element('mechanisms') do |parent|
89
+ parent.default_namespace = NAMESPACES[:sasl]
90
+ mechanisms.each {|name| parent << doc.create_element('mechanism', name) }
91
+ end
92
+ end
93
+
94
+ @stream.send_data([create_header(node.to_s.bytesize), node.to_s].join("\r\n\r\n"))
95
+ end
96
+
97
+ def request(rid)
98
+ @pinged = false
99
+ @last_activity = Time.now
100
+ if @queued_stanzas.size > 0
101
+ send_response(@queued_stanzas.join(" "), @sid, rid)
102
+ @queued_stanzas.clear
103
+ else
104
+ @queued_requests << HttpRequest.new(rid)
105
+ end
106
+ end
107
+
108
+ def write(node)
109
+ request = @queued_requests.shift
110
+ unless request.nil?
111
+ send_response(node.to_s, @sid, request.rid)
112
+ else
113
+ @queued_stanzas << node.to_s
114
+ end
115
+ end
116
+
117
+ def timed_out_requests
118
+ @queued_requests.select {|request| request.timed_out? }
119
+ end
120
+
121
+ def mechanisms
122
+ ['EXTERNAL', 'PLAIN']
123
+ end
124
+
125
+ def handle_restart
126
+ doc = Document.new
127
+ node = doc.create_element('body',
128
+ 'xmlns' => NAMESPACES[:http_bind],
129
+ 'xmlns:stream' => NAMESPACES[:stream])
130
+ node << doc.create_element('stream:features') do |features|
131
+ features << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
132
+ end
133
+ @available = true
134
+ @stream.send_data([create_header(node.to_s.bytesize), node.to_s].join("\r\n\r\n"))
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class HttpStates
7
+ include Vines::Log
8
+
9
+ def start_timer
10
+ @timer ||= EventMachine::PeriodicTimer.new(5) { cleanup }
11
+ end
12
+
13
+ def initialize
14
+ @http_states = {}
15
+ start_timer
16
+ end
17
+
18
+ def cleanup
19
+ # An expired HTTP client is one that has no queued requests
20
+ # and has no activity in more than 60 seconds
21
+ expired.each do |sid, http_state|
22
+ @http_states.delete(sid)
23
+ log.debug("Removed expired HTTP client #{sid}")
24
+ end
25
+ rescue Exception => e
26
+ log.error("Failed to cleanup HTTP connections: #{e}")
27
+ end
28
+
29
+ def []=(sid, http_state)
30
+ @http_states[sid] = http_state
31
+ end
32
+
33
+ def [](sid)
34
+ @http_states[sid]
35
+ end
36
+
37
+ def connected_http_clients
38
+ @http_states
39
+ end
40
+
41
+ def delete
42
+ @http_states
43
+ end
44
+
45
+ private
46
+
47
+ def expired
48
+ @http_states.select {|sid, http_state| http_state.expired? }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Parser < Nokogiri::XML::SAX::Document
6
+ include Nokogiri::XML
7
+ STREAM_NAME = 'stream'.freeze
8
+ STREAM_URI = 'http://etherx.jabber.org/streams'.freeze
9
+ IGNORE = NAMESPACES.values_at(:client, :component, :server)
10
+
11
+ def initialize(&block)
12
+ @listeners, @node = Hash.new {|h, k| h[k] = []}, nil
13
+ @parser = Nokogiri::XML::SAX::PushParser.new(self)
14
+ instance_eval(&block) if block
15
+ end
16
+
17
+ [:stream_open, :stream_close, :stanza].each do |name|
18
+ define_method(name) do |&block|
19
+ @listeners[name] << block
20
+ end
21
+ end
22
+
23
+ def <<(data)
24
+ @parser << data
25
+ self
26
+ end
27
+
28
+ def start_element_namespace(name, attrs=[], prefix=nil, uri=nil, ns=[])
29
+ el = node(name, attrs, prefix, uri, ns)
30
+ if stream?(name, uri)
31
+ notify(:stream_open, el)
32
+ else
33
+ @node << el if @node
34
+ @node = el
35
+ end
36
+ end
37
+
38
+ def end_element_namespace(name, prefix=nil, uri=nil)
39
+ if stream?(name, uri)
40
+ notify(:stream_close)
41
+ elsif @node.parent != @node.document
42
+ @node = @node.parent
43
+ else
44
+ notify(:stanza, @node)
45
+ @node = nil
46
+ end
47
+ end
48
+
49
+ def characters(chars)
50
+ @node << Text.new(chars, @node.document) if @node
51
+ end
52
+ alias :cdata_block :characters
53
+
54
+ private
55
+
56
+ def notify(msg, node=nil)
57
+ @listeners[msg].each do |b|
58
+ (node ? b.call(node) : b.call) rescue nil
59
+ end
60
+ end
61
+
62
+ def stream?(name, uri)
63
+ name == STREAM_NAME && uri == STREAM_URI
64
+ end
65
+
66
+ def node(name, attrs=[], prefix=nil, uri=nil, ns=[])
67
+ ignore = stream?(name, uri) ? [] : IGNORE
68
+ doc = @node ? @node.document : Document.new
69
+ doc.create_element(name) do |node|
70
+ attrs.each {|attr| node[attr.localname] = attr.value }
71
+ ns.each {|prefix, uri| node.add_namespace(prefix, uri) unless ignore.include?(uri) }
72
+ node.namespace = node.add_namespace(prefix, uri) unless ignore.include?(uri)
73
+ doc << node unless @node
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,126 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+
6
+ # Implements the XMPP protocol for server-to-server (s2s) streams. This
7
+ # serves connected streams using the jabber:server namespace. This handles
8
+ # both accepting incoming s2s streams and initiating outbound s2s streams
9
+ # to other servers.
10
+ class Server < Stream
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
+ callback.call(nil)
39
+ else
40
+ begin
41
+ rr = srv.shift
42
+ opts = {:to => to, :from => from, :srv => srv, :callback => callback}
43
+ EM.connect(rr.target.to_s, rr.port, Server, config, opts)
44
+ rescue Exception => e
45
+ connect(config, to, from, srv, callback)
46
+ end
47
+ end
48
+ end
49
+
50
+ attr_reader :config, :domain
51
+ attr_accessor :remote_domain
52
+
53
+ def initialize(config, options={})
54
+ @config = config
55
+ @remote_domain = options[:to]
56
+ @domain = options[:from]
57
+ @srv = options[:srv]
58
+ @callback = options[:callback]
59
+ @outbound = @remote_domain && @domain
60
+ @state = @outbound ? Outbound::Start.new(self) : Start.new(self)
61
+ end
62
+
63
+ def post_init
64
+ super
65
+ send_stream_header if @outbound
66
+ end
67
+
68
+ def max_stanza_size
69
+ @config[:server].max_stanza_size
70
+ end
71
+
72
+ def ssl_handshake_completed
73
+ close_connection unless cert_domain_matches?(@remote_domain)
74
+ end
75
+
76
+ def unbind
77
+ super
78
+ if @outbound && @state.class != Ready
79
+ Server.connect(@config, @remote_domain, @domain, @srv, @callback)
80
+ end
81
+ end
82
+
83
+ def vhost?(domain)
84
+ @config.vhost?(domain)
85
+ end
86
+
87
+ def notify_connected
88
+ if @callback
89
+ @callback.call(self)
90
+ @callback = nil
91
+ end
92
+ end
93
+
94
+ def ready?
95
+ @state.class == Server::Ready
96
+ end
97
+
98
+ def start(node)
99
+ if @outbound then send_stream_header; return end
100
+ @domain, @remote_domain = %w[to from].map {|a| node[a] }
101
+ send_stream_header
102
+ raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
103
+ raise StreamErrors::ImproperAddressing if [@domain, @remote_domain].any? {|addr| (addr || '').strip.empty? }
104
+ raise StreamErrors::HostUnknown unless @config.vhost?(@domain)
105
+ raise StreamErrors::NotAuthorized unless @config.s2s?(@remote_domain)
106
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:server]
107
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
108
+ end
109
+
110
+ private
111
+
112
+ def send_stream_header
113
+ attrs = {
114
+ 'xmlns' => NAMESPACES[:server],
115
+ 'xmlns:stream' => NAMESPACES[:stream],
116
+ 'xml:lang' => 'en',
117
+ 'id' => Kit.uuid,
118
+ 'from' => @domain,
119
+ 'to' => @remote_domain,
120
+ 'version' => '1.0'
121
+ }
122
+ write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Auth < Client::Auth
7
+ def initialize(stream, success=FinalRestart)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class AuthRestart < Client::AuthRestart
7
+ def initialize(stream, success=Auth)
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def mechanisms
14
+ ['EXTERNAL']
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end