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,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