diaspora-vines 0.1.2

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 (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/contact_test.rb +102 -0
  125. data/test/error_test.rb +58 -0
  126. data/test/ext/nokogiri.rb +14 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. metadata +387 -0
@@ -0,0 +1,150 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class XmppError < StandardError
5
+ include Nokogiri::XML
6
+
7
+ # Returns the XML element name based on the exception class name.
8
+ # For example, Vines::BadFormat becomes bad-format.
9
+ def element_name
10
+ name = self.class.name.split('::').last
11
+ name.gsub(/([A-Z])/, '-\1').downcase[1..-1]
12
+ end
13
+ end
14
+
15
+ class SaslError < XmppError
16
+ NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
17
+
18
+ def initialize(text=nil)
19
+ @text = text
20
+ end
21
+
22
+ def to_xml
23
+ doc = Document.new
24
+ doc.create_element('failure') do |node|
25
+ node.add_namespace(nil, NAMESPACE)
26
+ node << doc.create_element(element_name)
27
+ if @text
28
+ node << doc.create_element('text') do |text|
29
+ text['xml:lang'] = 'en'
30
+ text.content = @text
31
+ end
32
+ end
33
+ end.to_xml(:indent => 0).gsub(/\n/, '')
34
+ end
35
+ end
36
+
37
+ class StreamError < XmppError
38
+ NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-streams'.freeze
39
+
40
+ def initialize(text=nil)
41
+ @text = text
42
+ end
43
+
44
+ def to_xml
45
+ doc = Document.new
46
+ doc.create_element('stream:error') do |el|
47
+ el << doc.create_element(element_name, 'xmlns' => NAMESPACE)
48
+ if @text
49
+ el << doc.create_element('text', @text, 'xmlns' => NAMESPACE, 'xml:lang' => 'en')
50
+ end
51
+ end.to_xml(:indent => 0).gsub(/\n/, '')
52
+ end
53
+ end
54
+
55
+ class StanzaError < XmppError
56
+ TYPES = %w[auth cancel continue modify wait].freeze
57
+ KINDS = %w[message presence iq].freeze
58
+ NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-stanzas'.freeze
59
+
60
+ def initialize(el, type, text=nil)
61
+ raise "type must be one of: %s" % TYPES.join(', ') unless TYPES.include?(type)
62
+ raise "stanza must be one of: %s" % KINDS.join(', ') unless KINDS.include?(el.name)
63
+ @stanza_kind, @type, @text = el.name, type, text
64
+ @id, @from, @to = %w[id from to].map {|a| el[a] }
65
+ end
66
+
67
+ def to_xml
68
+ doc = Document.new
69
+ doc.create_element(@stanza_kind) do |el|
70
+ el['from'] = @to if @to
71
+ el['id'] = @id if @id
72
+ el['to'] = @from if @from
73
+ el['type'] = 'error'
74
+ el << doc.create_element('error', 'type' => @type) do |error|
75
+ error << doc.create_element(element_name, 'xmlns' => NAMESPACE)
76
+ if @text
77
+ error << doc.create_element('text', @text, 'xmlns' => NAMESPACE, 'xml:lang' => 'en')
78
+ end
79
+ end
80
+ end.to_xml(:indent => 0).gsub(/\n/, '')
81
+ end
82
+ end
83
+
84
+ module SaslErrors
85
+ class Aborted < SaslError; end
86
+ class AccountDisabled < SaslError; end
87
+ class CredentialsExpired < SaslError; end
88
+ class EncryptionRequired < SaslError; end
89
+ class IncorrectEncoding < SaslError; end
90
+ class InvalidAuthzid < SaslError; end
91
+ class InvalidMechanism < SaslError; end
92
+ class MalformedRequest < SaslError; end
93
+ class MechanismTooWeak < SaslError; end
94
+ class NotAuthorized < SaslError; end
95
+ class TemporaryAuthFailure < SaslError; end
96
+ end
97
+
98
+ module StreamErrors
99
+ class BadFormat < StreamError; end
100
+ class BadNamespacePrefix < StreamError; end
101
+ class Conflict < StreamError; end
102
+ class ConnectionTimeout < StreamError; end
103
+ class HostGone < StreamError; end
104
+ class HostUnknown < StreamError; end
105
+ class ImproperAddressing < StreamError; end
106
+ class InternalServerError < StreamError; end
107
+ class InvalidFrom < StreamError; end
108
+ class InvalidNamespace < StreamError; end
109
+ class InvalidXml < StreamError; end
110
+ class NotAuthorized < StreamError; end
111
+ class NotWellFormed < StreamError; end
112
+ class PolicyViolation < StreamError; end
113
+ class RemoteConnectionFailed < StreamError; end
114
+ class Reset < StreamError; end
115
+ class ResourceConstraint < StreamError; end
116
+ class RestrictedXml < StreamError; end
117
+ class SeeOtherHost < StreamError; end
118
+ class SystemShutdown < StreamError; end
119
+ class UndefinedCondition < StreamError; end
120
+ class UnsupportedEncoding < StreamError; end
121
+ class UnsupportedFeature < StreamError; end
122
+ class UnsupportedStanzaType < StreamError; end
123
+ class UnsupportedVersion < StreamError; end
124
+ end
125
+
126
+ module StanzaErrors
127
+ class BadRequest < StanzaError; end
128
+ class Conflict < StanzaError; end
129
+ class FeatureNotImplemented < StanzaError; end
130
+ class Forbidden < StanzaError; end
131
+ class Gone < StanzaError; end
132
+ class InternalServerError < StanzaError; end
133
+ class ItemNotFound < StanzaError; end
134
+ class JidMalformed < StanzaError; end
135
+ class NotAcceptable < StanzaError; end
136
+ class NotAllowed < StanzaError; end
137
+ class NotAuthorized < StanzaError; end
138
+ class PolicyViolation < StanzaError; end
139
+ class RecipientUnavailable < StanzaError; end
140
+ class Redirect < StanzaError; end
141
+ class RegistrationRequired < StanzaError; end
142
+ class RemoteServerNotFound < StanzaError; end
143
+ class RemoteServerTimeout < StanzaError; end
144
+ class ResourceConstraint < StanzaError; end
145
+ class ServiceUnavailable < StanzaError; end
146
+ class SubscriptionRequired < StanzaError; end
147
+ class UndefinedCondition < StanzaError; end
148
+ class UnexpectedRequest < StanzaError; end
149
+ end
150
+ end
data/lib/vines/jid.rb ADDED
@@ -0,0 +1,95 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class JID
5
+ include Comparable
6
+
7
+ PATTERN = /\A(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?\Z/.freeze
8
+
9
+ # http://tools.ietf.org/html/rfc6122#appendix-A
10
+ NODE_PREP = /[[:cntrl:] "&'\/:<>@]/.freeze
11
+
12
+ # http://tools.ietf.org/html/rfc3454#appendix-C
13
+ NAME_PREP = /[[:cntrl:] ]/.freeze
14
+
15
+ # http://tools.ietf.org/html/rfc6122#appendix-B
16
+ RESOURCE_PREP = /[[:cntrl:]]/.freeze
17
+
18
+ attr_reader :node, :domain, :resource
19
+ attr_writer :resource
20
+
21
+ def self.new(node, domain=nil, resource=nil)
22
+ node.is_a?(JID) ? node : super
23
+ end
24
+
25
+ def initialize(node, domain=nil, resource=nil)
26
+ @node, @domain, @resource = node, domain, resource
27
+
28
+ if @domain.nil? && @resource.nil?
29
+ @node, @domain, @resource = @node.to_s.scan(PATTERN).first
30
+ end
31
+ [@node, @domain].each {|part| part.downcase! if part }
32
+
33
+ validate
34
+ end
35
+
36
+ # Strip the resource part from this JID and return it as a new
37
+ # JID object. The new JID contains only the optional node part
38
+ # and the required domain part from the original. This JID remains
39
+ # unchanged.
40
+ def bare
41
+ JID.new(@node, @domain)
42
+ end
43
+
44
+ # Return true if this is a bare JID without a resource part.
45
+ def bare?
46
+ @resource.nil?
47
+ end
48
+
49
+ # Return true if this is a domain-only JID without a node or resource part.
50
+ def domain?
51
+ !empty? && to_s == @domain
52
+ end
53
+
54
+ # Return true if this JID is equal to the empty string ''. That is, it's
55
+ # missing the node, domain, and resource parts that form a valid JID. It
56
+ # makes for easier error handling to be able to create JID objects from
57
+ # strings and then check if they're empty rather than nil.
58
+ def empty?
59
+ to_s == ''
60
+ end
61
+
62
+ def <=>(jid)
63
+ self.to_s <=> jid.to_s
64
+ end
65
+
66
+ def eql?(jid)
67
+ jid.is_a?(JID) && self == jid
68
+ end
69
+
70
+ def hash
71
+ self.to_s.hash
72
+ end
73
+
74
+ def to_s
75
+ s = @domain
76
+ s = "#{@node}@#{s}" if @node
77
+ s = "#{s}/#{@resource}" if @resource
78
+ s
79
+ end
80
+
81
+ private
82
+
83
+ def validate
84
+ [@node, @domain, @resource].each do |part|
85
+ raise ArgumentError, 'jid too long' if (part || '').size > 1023
86
+ end
87
+ raise ArgumentError, 'empty node' if @node && @node.strip.empty?
88
+ raise ArgumentError, 'node contains invalid characters' if @node && @node =~ NODE_PREP
89
+ raise ArgumentError, 'empty resource' if @resource && @resource.strip.empty?
90
+ raise ArgumentError, 'resource contains invalid characters' if @resource && @resource =~ RESOURCE_PREP
91
+ raise ArgumentError, 'empty domain' if @domain == '' && (@node || @resource)
92
+ raise ArgumentError, 'domain contains invalid characters' if @domain && @domain =~ NAME_PREP
93
+ end
94
+ end
95
+ end
data/lib/vines/kit.rb ADDED
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ # A module for utility methods with no better home.
5
+ module Kit
6
+ # Create a hex-encoded, SHA-512 HMAC of the data, using the secret key.
7
+ def self.hmac(key, data)
8
+ digest = OpenSSL::Digest.new("sha512")
9
+ OpenSSL::HMAC.hexdigest(digest, key, data)
10
+ end
11
+
12
+ # Generates a random uuid per rfc 4122 that's useful for including in
13
+ # stream, iq, and other xmpp stanzas.
14
+ def self.uuid
15
+ SecureRandom.uuid
16
+ end
17
+
18
+ # Generates a random 128 character authentication token.
19
+ def self.auth_token
20
+ SecureRandom.hex(64)
21
+ end
22
+ end
23
+ end
data/lib/vines/log.rb ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Log
5
+ @@logger = nil
6
+ def log
7
+ unless @@logger
8
+ @@logger = Logger.new(STDOUT)
9
+ @@logger.level = Logger::INFO
10
+ @@logger.progname = 'vines'
11
+ @@logger.formatter = Class.new(Logger::Formatter) do
12
+ def initialize
13
+ @time = "%Y-%m-%dT%H:%M:%SZ".freeze
14
+ @fmt = "[%s] %5s -- %s: %s\n".freeze
15
+ end
16
+ def call(severity, time, program, msg)
17
+ @fmt % [time.utc.strftime(@time), severity, program, msg2str(msg)]
18
+ end
19
+ end.new
20
+ end
21
+ @@logger
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,179 @@
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 +Config#router+.
8
+ class Router
9
+ EMPTY = [].freeze
10
+
11
+ STREAM_TYPES = [:client, :server, :component].freeze
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ @clients, @servers, @components = {}, [], []
16
+ @pending = Hash.new {|h,k| h[k] = [] }
17
+ end
18
+
19
+ # Returns streams for all connected resources for this JID. A resource is
20
+ # considered connected after it has completed authentication and resource
21
+ # binding.
22
+ def connected_resources(jid, from, proxies=true)
23
+ jid, from = JID.new(jid), JID.new(from)
24
+ return [] unless @config.allowed?(jid, from)
25
+
26
+ local = @clients[jid.bare] || EMPTY
27
+ local = local.select {|stream| stream.user.jid == jid } unless jid.bare?
28
+ remote = proxies ? proxies(jid) : EMPTY
29
+ [local, remote].flatten
30
+ end
31
+
32
+ # Returns streams for all available resources for this JID. A resource is
33
+ # marked available after it sends initial presence.
34
+ def available_resources(*jids, from)
35
+ clients(jids, from) do |stream|
36
+ stream.available?
37
+ end
38
+ end
39
+
40
+ # Returns streams for all interested resources for this JID. A resource is
41
+ # marked interested after it requests the roster.
42
+ def interested_resources(*jids, from)
43
+ clients(jids, from) do |stream|
44
+ stream.interested?
45
+ end
46
+ end
47
+
48
+ # Add the connection to the routing table. The connection must return
49
+ # :client, :server, or :component from its +stream_type+ method so the
50
+ # router can properly route stanzas to the stream.
51
+ def <<(stream)
52
+ case stream_type(stream)
53
+ when :client then
54
+ return unless stream.connected?
55
+ jid = stream.user.jid.bare
56
+ @clients[jid] ||= []
57
+ @clients[jid] << stream
58
+ when :server then @servers << stream
59
+ when :component then @components << stream
60
+ end
61
+ end
62
+
63
+ # Remove the connection from the routing table.
64
+ def delete(stream)
65
+ case stream_type(stream)
66
+ when :client then
67
+ return unless stream.connected?
68
+ jid = stream.user.jid.bare
69
+ streams = @clients[jid] || []
70
+ streams.delete(stream)
71
+ @clients.delete(jid) if streams.empty?
72
+ when :server then @servers.delete(stream)
73
+ when :component then @components.delete(stream)
74
+ end
75
+ end
76
+
77
+ # Send the stanza to the appropriate remote server-to-server stream
78
+ # or an external component stream.
79
+ def route(stanza)
80
+ to, from = %w[to from].map {|attr| JID.new(stanza[attr]) }
81
+ return unless @config.allowed?(to, from)
82
+ key = [to.domain, from.domain]
83
+
84
+ if stream = connection_to(to, from)
85
+ stream.write(stanza)
86
+ elsif @pending.key?(key)
87
+ @pending[key] << stanza
88
+ elsif @config.s2s?(to.domain)
89
+ @pending[key] << stanza
90
+ Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream|
91
+ stream ? send_pending(key, stream) : return_pending(key)
92
+ @pending.delete(key)
93
+ end
94
+ else
95
+ raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel')
96
+ end
97
+ end
98
+
99
+ # Returns the total number of streams connected to the server.
100
+ def size
101
+ clients = @clients.values.inject(0) {|sum, arr| sum + arr.size }
102
+ clients + @servers.size + @components.size
103
+ end
104
+
105
+ private
106
+
107
+ # Write all pending stanzas for this domain to the stream. Called after a
108
+ # s2s stream has successfully connected and we need to dequeue all stanzas
109
+ # we received while waiting for the connection to finish.
110
+ def send_pending(key, stream)
111
+ @pending[key].each do |stanza|
112
+ stream.write(stanza)
113
+ end
114
+ end
115
+
116
+ # Return all pending stanzas to their senders as remote-server-not-found
117
+ # errors. Called after a s2s stream has failed to connect.
118
+ def return_pending(key)
119
+ @pending[key].each do |stanza|
120
+ to, from = JID.new(stanza['to']), JID.new(stanza['from'])
121
+ xml = StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel').to_xml
122
+ if @config.component?(from)
123
+ connection_to(from, to).write(xml) rescue nil
124
+ else
125
+ connected_resources(from, to).each {|c| c.write(xml) }
126
+ end
127
+ end
128
+ end
129
+
130
+ # Return the client streams to which the from address is allowed to
131
+ # contact. Apply the filter block to each stream to narrow the results
132
+ # before returning the streams.
133
+ def clients(jids, from, &filter)
134
+ jids = filter_allowed(jids, from)
135
+ local = @clients.values_at(*jids).compact.flatten.select(&filter)
136
+ proxies = proxies(*jids).select(&filter)
137
+ [local, proxies].flatten
138
+ end
139
+
140
+ # Return the bare JIDs from the list that are allowed to talk to
141
+ # the +from+ JID.
142
+ def filter_allowed(jids, from)
143
+ from = JID.new(from)
144
+ jids.flatten.map {|jid| JID.new(jid).bare }
145
+ .select {|jid| @config.allowed?(jid, from) }
146
+ end
147
+
148
+ def proxies(*jids)
149
+ return EMPTY unless @config.cluster?
150
+ @config.cluster.remote_sessions(*jids)
151
+ end
152
+
153
+ def connection_to(to, from)
154
+ component_stream(to) || server_stream(to, from)
155
+ end
156
+
157
+ def component_stream(to)
158
+ @components.select do |stream|
159
+ stream.ready? && stream.remote_domain == to.domain
160
+ end.sample
161
+ end
162
+
163
+ def server_stream(to, from)
164
+ @servers.select do |stream|
165
+ stream.ready? &&
166
+ stream.remote_domain == to.domain &&
167
+ stream.domain == from.domain
168
+ end.sample
169
+ end
170
+
171
+ def stream_type(connection)
172
+ connection.stream_type.tap do |type|
173
+ unless STREAM_TYPES.include?(type)
174
+ raise ArgumentError, "unexpected stream type: #{type}"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ 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,45 @@
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 || !allowed?
13
+ result = to_result.tap do |el|
14
+ el << el.document.create_element('query') do |query|
15
+ query.default_namespace = NS
16
+ if to_pubsub_domain?
17
+ identity(query, 'pubsub', 'service')
18
+ pubsub = [:pubsub_create, :pubsub_delete, :pubsub_instant, :pubsub_item_ids, :pubsub_publish, :pubsub_subscribe]
19
+ features(query, :disco_info, :ping, :pubsub, *pubsub)
20
+ else
21
+ identity(query, 'server', 'im')
22
+ features = [:disco_info, :disco_items, :ping, :vcard, :version]
23
+ features << :storage if stream.config.private_storage?(validate_to || stream.domain)
24
+ features(query, features)
25
+ end
26
+ end
27
+ end
28
+ stream.write(result)
29
+ end
30
+
31
+ private
32
+
33
+ def identity(query, category, type)
34
+ query << query.document.create_element('identity', 'category' => category, 'type' => type)
35
+ end
36
+
37
+ def features(query, *features)
38
+ features.flatten.each do |feature|
39
+ query << query.document.create_element('feature', 'var' => NAMESPACES[feature])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
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 || !allowed?
13
+ result = to_result.tap do |el|
14
+ el << el.document.create_element('query') do |query|
15
+ query.default_namespace = NS
16
+ unless to_pubsub_domain?
17
+ to = (validate_to || stream.domain).to_s
18
+ stream.config.vhost(to).disco_items.each do |domain|
19
+ query << el.document.create_element('item', 'jid' => domain)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ stream.write(result)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ 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 || !allowed?
11
+ stream.write(to_result)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ # Implements the Private Storage feature defined in XEP-0049. Clients are
7
+ # allowed to save arbitrary XML documents on the server, identified by
8
+ # element name and namespace.
9
+ class PrivateStorage < Query
10
+ NS = NAMESPACES[:storage]
11
+
12
+ register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
13
+
14
+ def process
15
+ validate_to_address
16
+ validate_storage_enabled
17
+ validate_children_size
18
+ validate_namespaces
19
+ get? ? retrieve_fragment : update_fragment
20
+ end
21
+
22
+ private
23
+
24
+ def retrieve_fragment
25
+ found = storage.find_fragment(stream.user.jid, elements.first.elements.first)
26
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless found
27
+
28
+ result = to_result do |node|
29
+ node << node.document.create_element('query') do |query|
30
+ query.default_namespace = NS
31
+ query << found
32
+ end
33
+ end
34
+ stream.write(result)
35
+ end
36
+
37
+ def update_fragment
38
+ elements.first.elements.each do |node|
39
+ storage.save_fragment(stream.user.jid, node)
40
+ end
41
+ stream.write(to_result)
42
+ end
43
+
44
+ private
45
+
46
+ def to_result
47
+ super.tap do |node|
48
+ node['from'] = stream.user.jid.to_s
49
+ yield node if block_given?
50
+ end
51
+ end
52
+
53
+ def validate_children_size
54
+ size = elements.first.elements.size
55
+ if (get? && size != 1) || (set? && size == 0)
56
+ raise StanzaErrors::NotAcceptable.new(self, 'modify')
57
+ end
58
+ end
59
+
60
+ def validate_to_address
61
+ to = validate_to
62
+ unless to.nil? || to == stream.user.jid.bare
63
+ raise StanzaErrors::Forbidden.new(self, 'cancel')
64
+ end
65
+ end
66
+
67
+ def validate_storage_enabled
68
+ unless stream.config.private_storage?(stream.domain)
69
+ raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
70
+ end
71
+ end
72
+
73
+ def validate_namespaces
74
+ elements.first.elements.each do |node|
75
+ if node.namespace.nil? || NAMESPACES.values.include?(node.namespace.href)
76
+ raise StanzaErrors::NotAcceptable.new(self, 'modify')
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ 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