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,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class FinalRestart < State
7
+ def initialize(stream, success=Ready)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ stream.write('<stream:features/>')
15
+ advance
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class Auth < State
8
+ NS = NAMESPACES[:sasl]
9
+
10
+ def initialize(stream, success=AuthResult)
11
+ super
12
+ end
13
+
14
+ def node(node)
15
+ raise StreamErrors::NotAuthorized unless external?(node)
16
+ authz = Base64.encode64(stream.domain).chomp
17
+ stream.write(%Q{<auth xmlns="#{NS}" mechanism="EXTERNAL">#{authz}</auth>})
18
+ advance
19
+ end
20
+
21
+ private
22
+
23
+ def external?(node)
24
+ external = node.xpath("ns:mechanisms/ns:mechanism[text()='EXTERNAL']", 'ns' => NS).any?
25
+ node.name == 'features' && namespace(node) == NAMESPACES[:stream] && external
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class AuthRestart < State
8
+ def initialize(stream, success=Auth)
9
+ super
10
+ end
11
+
12
+ def node(node)
13
+ raise StreamErrors::NotAuthorized unless stream?(node)
14
+ advance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class AuthResult < State
8
+ def initialize(stream, success=FinalRestart)
9
+ super
10
+ end
11
+
12
+ def node(node)
13
+ raise StreamErrors::NotAuthorized unless namespace(node) == NAMESPACES[:sasl]
14
+ case node.name
15
+ when 'success'
16
+ stream.start(node)
17
+ advance
18
+ when 'failure'
19
+ stream.close_connection
20
+ else
21
+ raise StreamErrors::NotAuthorized
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class FinalFeatures < State
8
+ def initialize(stream, success=Server::Ready)
9
+ super
10
+ end
11
+
12
+ def node(node)
13
+ raise StreamErrors::NotAuthorized unless empty_features?(node)
14
+ advance
15
+ stream.notify_connected
16
+ end
17
+
18
+ private
19
+
20
+ def empty_features?(node)
21
+ node.name == 'features' && namespace(node) == NAMESPACES[:stream] && node.elements.empty?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class FinalRestart < State
8
+ def initialize(stream, success=FinalFeatures)
9
+ super
10
+ end
11
+
12
+ def node(node)
13
+ raise StreamErrors::NotAuthorized unless stream?(node)
14
+ advance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class Start < State
8
+ def initialize(stream, success=TLS)
9
+ super
10
+ end
11
+
12
+ def node(node)
13
+ raise StreamErrors::NotAuthorized unless stream?(node)
14
+ advance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class TLS < State
8
+ NS = NAMESPACES[:tls]
9
+
10
+ def initialize(stream, success=TLSResult)
11
+ super
12
+ end
13
+
14
+ def node(node)
15
+ raise StreamErrors::NotAuthorized unless tls?(node)
16
+ stream.write("<starttls xmlns='#{NS}'/>")
17
+ advance
18
+ end
19
+
20
+ private
21
+
22
+ def tls?(node)
23
+ tls = node.xpath('ns:starttls', 'ns' => NS).any?
24
+ node.name == 'features' && namespace(node) == NAMESPACES[:stream] && tls
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Outbound
7
+ class TLSResult < State
8
+ NS = NAMESPACES[:tls]
9
+
10
+ def initialize(stream, success=AuthRestart)
11
+ super
12
+ end
13
+
14
+ def node(node)
15
+ raise StreamErrors::NotAuthorized unless namespace(node) == NS
16
+ case node.name
17
+ when 'proceed'
18
+ stream.encrypt
19
+ stream.start(node)
20
+ advance
21
+ when 'failure'
22
+ stream.close_connection
23
+ else
24
+ raise StreamErrors::NotAuthorized
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Ready < State
7
+ def node(node)
8
+ stanza = to_stanza(node)
9
+ raise StreamErrors::UnsupportedStanzaType unless stanza
10
+ to, from = %w[to from].map {|attr| JID.new(stanza[attr] || '') }
11
+ raise StreamErrors::ImproperAddressing if [to, from].any? {|addr| (addr.domain || '').strip.empty? }
12
+ raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
13
+ raise StreamErrors::HostUnknown unless to.domain == stream.domain
14
+ stream.user = User.new(:jid => from)
15
+ stanza.process
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class Start < Client::Start
7
+ def initialize(stream, success=TLS)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Server
6
+ class TLS < Client::TLS
7
+ def initialize(stream, success=AuthRestart)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+
6
+ # The base class of Stream state machines. States know how to process XML
7
+ # nodes and advance to their next valid state or fail the stream.
8
+ class State
9
+ include Nokogiri::XML
10
+ include Vines::Log
11
+
12
+ attr_reader :stream
13
+
14
+ STREAM = 'stream'.freeze
15
+
16
+ def initialize(stream, success=nil)
17
+ @stream, @success = stream, success
18
+ end
19
+
20
+ def node(node)
21
+ raise 'subclass must implement'
22
+ end
23
+
24
+ def ==(state)
25
+ self.class == state.class
26
+ end
27
+
28
+ def eql?(state)
29
+ state.is_a?(State) && self == state
30
+ end
31
+
32
+ def hash
33
+ self.class.hash
34
+ end
35
+
36
+ private
37
+
38
+ def advance
39
+ stream.advance(@success.new(stream))
40
+ end
41
+
42
+ def stream?(node)
43
+ node.name == STREAM && namespace(node) == NAMESPACES[:stream]
44
+ end
45
+
46
+ def namespace(node)
47
+ node.namespace ? node.namespace.href : nil
48
+ end
49
+
50
+ def to_stanza(node)
51
+ Stanza.from_node(node, stream)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+
5
+ # The token bucket algorithm is useful for rate limiting.
6
+ # Before an operation can be completed, a token is taken from
7
+ # the bucket. If no tokens are available, the operation fails.
8
+ # The bucket is refilled with tokens at the maximum allowed rate
9
+ # of operations.
10
+ class TokenBucket
11
+
12
+ # Create a full bucket with capacity number of tokens to be filled
13
+ # at the given rate of tokens/second.
14
+ def initialize(capacity, rate)
15
+ raise ArgumentError.new('capacity must be > 0') unless capacity > 0
16
+ raise ArgumentError.new('rate must be > 0') unless rate > 0
17
+ @capacity = capacity
18
+ @tokens = capacity
19
+ @rate = rate
20
+ @timestamp = Time.new
21
+ end
22
+
23
+ # Returns true if tokens can be taken from the bucket.
24
+ def take(tokens)
25
+ raise ArgumentError.new('tokens must be > 0') unless tokens > 0
26
+ if tokens <= fill
27
+ @tokens -= tokens
28
+ true
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def fill
37
+ if @tokens < @capacity
38
+ now = Time.new
39
+ delta = (@rate * (now - @timestamp)).round
40
+ @tokens = [@capacity, @tokens + delta].min
41
+ @timestamp = now
42
+ end
43
+ @tokens
44
+ end
45
+ end
46
+ end
data/lib/vines/user.rb ADDED
@@ -0,0 +1,124 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class User
5
+ include Comparable
6
+
7
+ attr_accessor :name, :password, :roster
8
+ attr_reader :jid
9
+
10
+ def initialize(args={})
11
+ @jid = JID.new(args[:jid])
12
+ raise ArgumentError, 'invalid jid' unless @jid.node && !@jid.domain.empty?
13
+ @name = args[:name]
14
+ @password = args[:password]
15
+ @roster = args[:roster] || []
16
+ end
17
+
18
+ def <=>(user)
19
+ self.jid.to_s <=> user.jid.to_s
20
+ end
21
+
22
+ def eql?(user)
23
+ user.is_a?(User) && self == user
24
+ end
25
+
26
+ def hash
27
+ jid.to_s.hash
28
+ end
29
+
30
+ # Update this user's information from the given user object.
31
+ def update_from(user)
32
+ @name = user.name
33
+ @password = user.password
34
+ @roster = user.roster.map {|c| c.clone }
35
+ end
36
+
37
+ # Return true if the jid is on this user's roster.
38
+ def contact?(jid)
39
+ !contact(jid).nil?
40
+ end
41
+
42
+ # Returns the contact with this jid or nil if not found.
43
+ def contact(jid)
44
+ bare = JID.new(jid).bare
45
+ @roster.find {|c| c.jid.bare == bare }
46
+ end
47
+
48
+ # Returns true if the user is subscribed to this contact's
49
+ # presence updates.
50
+ def subscribed_to?(jid)
51
+ contact = contact(jid)
52
+ contact && contact.subscribed_to?
53
+ end
54
+
55
+ # Returns true if the user has a presence subscription from this contact.
56
+ # The contact is subscribed to this user's presence.
57
+ def subscribed_from?(jid)
58
+ contact = contact(jid)
59
+ contact && contact.subscribed_from?
60
+ end
61
+
62
+ # Removes the contact with this jid from the user's roster.
63
+ def remove_contact(jid)
64
+ bare = JID.new(jid).bare
65
+ @roster.reject! {|c| c.jid.bare == bare }
66
+ end
67
+
68
+ # Returns a list of the contacts to which this user has
69
+ # successfully subscribed.
70
+ def subscribed_to_contacts
71
+ @roster.select {|c| c.subscribed_to? }
72
+ end
73
+
74
+ # Returns a list of the contacts that are subscribed to this user's
75
+ # presence updates.
76
+ def subscribed_from_contacts
77
+ @roster.select {|c| c.subscribed_from? }
78
+ end
79
+
80
+ # Update the contact's jid on this user's roster to signal that this user
81
+ # has requested the contact's permission to receive their presence updates.
82
+ def request_subscription(jid)
83
+ unless contact = contact(jid)
84
+ contact = Contact.new(:jid => jid)
85
+ @roster << contact
86
+ end
87
+ contact.ask = 'subscribe' if %w[none from].include?(contact.subscription)
88
+ end
89
+
90
+ # Add the user's jid to this contact's roster with a subscription state of
91
+ # 'from.' This signals that this contact has approved a user's subscription.
92
+ def add_subscription_from(jid)
93
+ unless contact = contact(jid)
94
+ contact = Contact.new(:jid => jid)
95
+ @roster << contact
96
+ end
97
+ contact.subscribe_from
98
+ end
99
+
100
+ def remove_subscription_to(jid)
101
+ if contact = contact(jid)
102
+ contact.unsubscribe_to
103
+ end
104
+ end
105
+
106
+ def remove_subscription_from(jid)
107
+ if contact = contact(jid)
108
+ contact.unsubscribe_from
109
+ end
110
+ end
111
+
112
+ # Returns this user's roster contacts as an iq query element.
113
+ def to_roster_xml(id)
114
+ doc = Nokogiri::XML::Document.new
115
+ doc.create_element('iq', 'id' => id, 'type' => 'result') do |el|
116
+ el << doc.create_element('query', 'xmlns' => 'jabber:iq:roster') do |query|
117
+ @roster.sort!.each do |contact|
118
+ query << contact.to_roster_xml
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end