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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/Rakefile +23 -0
- data/bin/vines +4 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3895 -0
- data/conf/config.rb +42 -0
- data/lib/vines/cli.rb +132 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/ldap.rb +38 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config/host.rb +125 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +223 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/message.rb +40 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/presence.rb +141 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/storage/ldap.rb +71 -0
- data/lib/vines/storage/local.rb +139 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +138 -0
- data/lib/vines/storage.rb +239 -0
- data/lib/vines/store.rb +110 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/parser.rb +79 -0
- data/lib/vines/stream/sasl.rb +128 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +6 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/lib/vines.rb +203 -0
- data/test/cluster/publisher_test.rb +57 -0
- data/test/cluster/sessions_test.rb +47 -0
- data/test/cluster/subscriber_test.rb +109 -0
- data/test/config/host_test.rb +369 -0
- data/test/config/pubsub_test.rb +187 -0
- data/test/config_test.rb +732 -0
- data/test/contact_test.rb +102 -0
- data/test/error_test.rb +58 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +147 -0
- data/test/kit_test.rb +31 -0
- data/test/router_test.rb +243 -0
- data/test/stanza/iq/disco_info_test.rb +78 -0
- data/test/stanza/iq/disco_items_test.rb +49 -0
- data/test/stanza/iq/private_storage_test.rb +184 -0
- data/test/stanza/iq/roster_test.rb +229 -0
- data/test/stanza/iq/session_test.rb +25 -0
- data/test/stanza/iq/vcard_test.rb +146 -0
- data/test/stanza/iq/version_test.rb +64 -0
- data/test/stanza/iq_test.rb +70 -0
- data/test/stanza/message_test.rb +126 -0
- data/test/stanza/presence/probe_test.rb +50 -0
- data/test/stanza/presence/subscribe_test.rb +83 -0
- data/test/stanza/pubsub/create_test.rb +116 -0
- data/test/stanza/pubsub/delete_test.rb +169 -0
- data/test/stanza/pubsub/publish_test.rb +309 -0
- data/test/stanza/pubsub/subscribe_test.rb +205 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
- data/test/stanza_test.rb +85 -0
- data/test/storage/ldap_test.rb +201 -0
- data/test/storage/local_test.rb +59 -0
- data/test/storage/mock_redis.rb +97 -0
- data/test/storage/null_test.rb +29 -0
- data/test/storage/storage_tests.rb +182 -0
- data/test/storage_test.rb +85 -0
- data/test/store_test.rb +130 -0
- data/test/stream/client/auth_test.rb +137 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/client/session_test.rb +27 -0
- data/test/stream/component/handshake_test.rb +52 -0
- data/test/stream/component/ready_test.rb +103 -0
- data/test/stream/component/start_test.rb +39 -0
- data/test/stream/http/auth_test.rb +70 -0
- data/test/stream/http/ready_test.rb +86 -0
- data/test/stream/http/request_test.rb +209 -0
- data/test/stream/http/sessions_test.rb +49 -0
- data/test/stream/http/start_test.rb +50 -0
- data/test/stream/parser_test.rb +122 -0
- data/test/stream/sasl_test.rb +195 -0
- data/test/stream/server/auth_test.rb +61 -0
- data/test/stream/server/outbound/auth_test.rb +75 -0
- data/test/stream/server/ready_test.rb +98 -0
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +44 -0
- data/test/user_test.rb +96 -0
- data/vines.gemspec +30 -0
- metadata +387 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Config
|
5
|
+
class Port
|
6
|
+
include Vines::Log
|
7
|
+
|
8
|
+
attr_reader :config, :stream
|
9
|
+
|
10
|
+
%w[host port].each do |name|
|
11
|
+
define_method(name) do
|
12
|
+
@settings[name.to_sym]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(config, host, port, &block)
|
17
|
+
@config, @settings = config, {}
|
18
|
+
instance_eval(&block) if block
|
19
|
+
defaults = {:host => host, :port => port,
|
20
|
+
:max_resources_per_account => 5, :max_stanza_size => 128 * 1024}
|
21
|
+
@settings = defaults.merge(@settings)
|
22
|
+
end
|
23
|
+
|
24
|
+
def max_stanza_size(max=nil)
|
25
|
+
if max
|
26
|
+
# rfc 6120 section 13.12
|
27
|
+
@settings[:max_stanza_size] = [10000, max].max
|
28
|
+
else
|
29
|
+
@settings[:max_stanza_size]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
type = stream.name.split('::').last.downcase
|
35
|
+
log.info("Accepting #{type} connections on #{host}:#{port}")
|
36
|
+
EventMachine::start_server(host, port, stream, config)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ClientPort < Port
|
41
|
+
def initialize(config, host='0.0.0.0', port=5222, &block)
|
42
|
+
@stream = Vines::Stream::Client
|
43
|
+
super(config, host, port, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def max_resources_per_account(max=nil)
|
47
|
+
if max
|
48
|
+
@settings[:max_resources_per_account] = max
|
49
|
+
else
|
50
|
+
@settings[:max_resources_per_account]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def start
|
55
|
+
super
|
56
|
+
config.cluster.start if config.cluster?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ServerPort < Port
|
61
|
+
def initialize(config, host='0.0.0.0', port=5269, &block)
|
62
|
+
@hosts, @stream = [], Vines::Stream::Server
|
63
|
+
super(config, host, port, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def hosts(*hosts)
|
67
|
+
if hosts.any?
|
68
|
+
@hosts << hosts
|
69
|
+
@hosts.flatten!
|
70
|
+
else
|
71
|
+
@hosts
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class HttpPort < Port
|
77
|
+
def initialize(config, host='0.0.0.0', port=5280, &block)
|
78
|
+
@stream = Vines::Stream::Http
|
79
|
+
super(config, host, port, &block)
|
80
|
+
defaults = {:root => File.expand_path('web'), :bind => '/xmpp'}
|
81
|
+
@settings = defaults.merge(@settings)
|
82
|
+
end
|
83
|
+
|
84
|
+
def max_resources_per_account(max=nil)
|
85
|
+
if max
|
86
|
+
@settings[:max_resources_per_account] = max
|
87
|
+
else
|
88
|
+
@settings[:max_resources_per_account]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def root(dir=nil)
|
93
|
+
if dir
|
94
|
+
@settings[:root] = File.expand_path(dir)
|
95
|
+
else
|
96
|
+
@settings[:root]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def bind(url=nil)
|
101
|
+
if url
|
102
|
+
@settings[:bind] = url
|
103
|
+
else
|
104
|
+
@settings[:bind]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def vroute(id=nil)
|
109
|
+
if id
|
110
|
+
id = id.to_s.strip
|
111
|
+
@settings[:vroute] = id.empty? ? nil : id
|
112
|
+
else
|
113
|
+
@settings[:vroute]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def start
|
118
|
+
super
|
119
|
+
if config.cluster? && vroute.nil?
|
120
|
+
log.warn("vroute sticky session cookie not set")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class ComponentPort < Port
|
126
|
+
def initialize(config, host='0.0.0.0', port=5347, &block)
|
127
|
+
@stream = Vines::Stream::Component
|
128
|
+
super(config, host, port, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Config
|
5
|
+
# Provides the configuration DSL to conf/config.rb for pubsub subdomains and
|
6
|
+
# exposes the storage and notification systems that the pubsub stanzas need
|
7
|
+
# to process. This class hides the complexity of determining pubsub behavior
|
8
|
+
# in a standalone vs. clustered chat server environment from the stanzas.
|
9
|
+
class PubSub
|
10
|
+
def initialize(config, name)
|
11
|
+
@config, @name = config, name
|
12
|
+
@nodes = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_node(id)
|
16
|
+
if @config.cluster?
|
17
|
+
@config.cluster.add_pubsub_node(@name, id)
|
18
|
+
else
|
19
|
+
@nodes[id] ||= Set.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_node(id)
|
24
|
+
if @config.cluster?
|
25
|
+
@config.cluster.delete_pubsub_node(@name, id)
|
26
|
+
else
|
27
|
+
@nodes.delete(id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscribe(node, jid)
|
32
|
+
return unless node?(node) && @config.allowed?(jid, @name)
|
33
|
+
if @config.cluster?
|
34
|
+
@config.cluster.subscribe_pubsub(@name, node, jid)
|
35
|
+
else
|
36
|
+
@nodes[node] << JID.new(jid)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def unsubscribe(node, jid)
|
41
|
+
return unless node?(node)
|
42
|
+
if @config.cluster?
|
43
|
+
@config.cluster.unsubscribe_pubsub(@name, node, jid)
|
44
|
+
else
|
45
|
+
@nodes[node].delete(JID.new(jid))
|
46
|
+
delete_node(node) if subscribers(node).empty?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def unsubscribe_all(jid)
|
51
|
+
if @config.cluster?
|
52
|
+
@config.cluster.unsubscribe_all_pubsub(@name, jid)
|
53
|
+
else
|
54
|
+
@nodes.keys.each do |node|
|
55
|
+
unsubscribe(node, jid)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def node?(node)
|
61
|
+
if @config.cluster?
|
62
|
+
@config.cluster.pubsub_node?(@name, node)
|
63
|
+
else
|
64
|
+
@nodes.key?(node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def subscribed?(node, jid)
|
69
|
+
return false unless node?(node)
|
70
|
+
if @config.cluster?
|
71
|
+
@config.cluster.pubsub_subscribed?(@name, node, jid)
|
72
|
+
else
|
73
|
+
@nodes[node].include?(JID.new(jid))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def publish(node, stanza)
|
78
|
+
stanza['id'] = Kit.uuid
|
79
|
+
stanza['from'] = @name
|
80
|
+
|
81
|
+
local, remote = subscribers(node).partition {|jid| @config.local_jid?(jid) }
|
82
|
+
|
83
|
+
local.flat_map do |jid|
|
84
|
+
@config.router.connected_resources(jid, @name)
|
85
|
+
end.each do |recipient|
|
86
|
+
stanza['to'] = recipient.user.jid.to_s
|
87
|
+
recipient.write(stanza)
|
88
|
+
end
|
89
|
+
|
90
|
+
remote.each do |jid|
|
91
|
+
el = stanza.clone
|
92
|
+
el['to'] = jid.to_s
|
93
|
+
@config.router.route(el) rescue nil # ignore RemoteServerNotFound
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def subscribers(node)
|
100
|
+
if @config.cluster?
|
101
|
+
@config.cluster.pubsub_subscribers(@name, node)
|
102
|
+
else
|
103
|
+
@nodes[node] || []
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/vines/config.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
|
5
|
+
# A Config object is passed to the stream handlers to give them access
|
6
|
+
# to server configuration information like virtual host names, storage
|
7
|
+
# systems, etc. This class provides the DSL methods used in the
|
8
|
+
# conf/config.rb file.
|
9
|
+
class Config
|
10
|
+
LOG_LEVELS = %w[debug info warn error fatal].freeze
|
11
|
+
|
12
|
+
attr_reader :router
|
13
|
+
|
14
|
+
@@instance = nil
|
15
|
+
def self.configure(&block)
|
16
|
+
@@instance = self.new(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.instance
|
20
|
+
@@instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(&block)
|
24
|
+
@certs = File.expand_path('conf/certs')
|
25
|
+
@vhosts, @ports, @cluster = {}, {}, nil
|
26
|
+
@null = Storage::Null.new
|
27
|
+
@router = Router.new(self)
|
28
|
+
instance_eval(&block)
|
29
|
+
raise "must define at least one virtual host" if @vhosts.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def certs(dir=nil)
|
33
|
+
dir ? @certs = File.expand_path(dir) : @certs
|
34
|
+
end
|
35
|
+
|
36
|
+
def host(*names, &block)
|
37
|
+
names = names.flatten.map {|name| name.downcase }
|
38
|
+
dupes = names.uniq.size != names.size || (@vhosts.keys & names).any?
|
39
|
+
raise "one host definition per domain allowed" if dupes
|
40
|
+
names.each do |name|
|
41
|
+
if name.eql? "diaspora"
|
42
|
+
@vhosts[domain_name] = Host.new(self, domain_name, &block)
|
43
|
+
else
|
44
|
+
@vhosts[name] = Host.new(self, name, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pepper(pepper=nil)
|
50
|
+
pepper ? @pepper = pepper : @pepper = ""
|
51
|
+
end
|
52
|
+
|
53
|
+
def domain_name
|
54
|
+
AppConfig.environment.url
|
55
|
+
.gsub(/^http(s){0,1}:\/\/|\/$/, '')
|
56
|
+
.to_s rescue "localhost"
|
57
|
+
end
|
58
|
+
|
59
|
+
%w[client server http component].each do |name|
|
60
|
+
define_method(name) do |*args, &block|
|
61
|
+
port = Vines::Config.const_get("#{name.capitalize}Port")
|
62
|
+
raise "one #{name} port definition allowed" if @ports[name.to_sym]
|
63
|
+
@ports[name.to_sym] = port.new(self, *args) do
|
64
|
+
instance_eval(&block) if block
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def cluster(&block)
|
70
|
+
return @cluster unless block
|
71
|
+
raise "one cluster definition allowed" if @cluster
|
72
|
+
@cluster = Cluster.new(self, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def log(level)
|
76
|
+
const = Logger.const_get(level.to_s.upcase) rescue nil
|
77
|
+
unless LOG_LEVELS.include?(level.to_s) && const
|
78
|
+
raise "log level must be one of: #{LOG_LEVELS.join(', ')}"
|
79
|
+
end
|
80
|
+
Class.new.extend(Vines::Log).log.level = const
|
81
|
+
end
|
82
|
+
|
83
|
+
def ports
|
84
|
+
@ports.values
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return true if the domain is virtual hosted by this server.
|
88
|
+
def vhost?(domain)
|
89
|
+
!!vhost(domain)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return the Host config object for this domain if it's hosted by this
|
93
|
+
# server.
|
94
|
+
def vhost(domain)
|
95
|
+
@vhosts[domain.to_s]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the storage system for the domain or a Storage::Null instance if
|
99
|
+
# the domain is not hosted at this server.
|
100
|
+
def storage(domain)
|
101
|
+
host = vhost(domain)
|
102
|
+
host ? host.storage : @null
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the PubSub system for the domain or nil if pubsub is not enabled
|
106
|
+
# for this domain.
|
107
|
+
def pubsub(domain)
|
108
|
+
host = @vhosts.values.find {|host| host.pubsub?(domain) }
|
109
|
+
host.pubsubs[domain.to_s] if host
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return true if the domain is a pubsub service hosted at a virtual host
|
113
|
+
# at this server.
|
114
|
+
def pubsub?(domain)
|
115
|
+
@vhosts.values.any? {|host| host.pubsub?(domain) }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Return true if all JIDs belong to components hosted by this server.
|
119
|
+
def component?(*jids)
|
120
|
+
!jids.flatten.index do |jid|
|
121
|
+
!component_password(JID.new(jid).domain)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return the password for the component or nil if it's not hosted here.
|
126
|
+
def component_password(domain)
|
127
|
+
host = @vhosts.values.find {|host| host.component?(domain) }
|
128
|
+
host.password(domain) if host
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return true if all of the JIDs are hosted by this server.
|
132
|
+
def local_jid?(*jids)
|
133
|
+
!jids.flatten.index do |jid|
|
134
|
+
!vhost?(JID.new(jid).domain)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return true if private XML fragment storage is enabled for this domain.
|
139
|
+
def private_storage?(domain)
|
140
|
+
host = vhost(domain)
|
141
|
+
host.private_storage? if host
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns true if server-to-server connections are allowed with the
|
145
|
+
# given domain.
|
146
|
+
def s2s?(domain)
|
147
|
+
@ports[:server] && @ports[:server].hosts.include?(domain.to_s)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return true if the server is a member of a cluster, serving the same
|
151
|
+
# domains from different machines.
|
152
|
+
def cluster?
|
153
|
+
!!@cluster
|
154
|
+
end
|
155
|
+
|
156
|
+
# Retrieve the Port subclass with this name:
|
157
|
+
# [:client, :server, :http, :component]
|
158
|
+
def [](name)
|
159
|
+
@ports[name] or raise ArgumentError.new("no port named #{name}")
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return true if the two JIDs are allowed to send messages to each other.
|
163
|
+
# Both domains must have enabled cross_domain_messages in their config files.
|
164
|
+
def allowed?(to, from)
|
165
|
+
to, from = JID.new(to), JID.new(from)
|
166
|
+
return false if to.empty? || from.empty?
|
167
|
+
return true if to.domain == from.domain # same domain always allowed
|
168
|
+
return cross_domain?(to, from) if local_jid?(to, from) # both virtual hosted here
|
169
|
+
return check_subdomains(to, from) if subdomain?(to, from) # component/pubsub to component/pubsub
|
170
|
+
return check_subdomain(to, from) if subdomain?(to) # to component/pubsub
|
171
|
+
return check_subdomain(from, to) if subdomain?(from) # from component/pubsub
|
172
|
+
return cross_domain?(to) if local_jid?(to) # from is remote
|
173
|
+
return cross_domain?(from) if local_jid?(from) # to is remote
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# Return true if all of the JIDs are some kind of subdomain resource hosted
|
180
|
+
# here (either a component or a pubsub domain).
|
181
|
+
def subdomain?(*jids)
|
182
|
+
!jids.flatten.index do |jid|
|
183
|
+
!component?(jid) && !pubsub?(jid)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Return true if the third-level subdomain JIDs (components and pubsubs)
|
188
|
+
# are allowed to communicate with each other. For example, a
|
189
|
+
# tea.wonderland.lit component should be allowed to send messages to
|
190
|
+
# pubsub.wonderland.lit because they share the second-level wonderland.lit
|
191
|
+
# domain.
|
192
|
+
def check_subdomains(to, from)
|
193
|
+
sub1, sub2 = strip_domain(to), strip_domain(from)
|
194
|
+
(sub1 == sub2) || cross_domain?(sub1, sub2)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Return true if the third-level subdomain JID (component or pubsub) is
|
198
|
+
# allowed to communicate with the other JID. For example,
|
199
|
+
# pubsub.wonderland.lit should be allowed to send messages to
|
200
|
+
# alice@wonderland.lit because they share the second-level wonderland.lit
|
201
|
+
# domain.
|
202
|
+
def check_subdomain(subdomain, jid)
|
203
|
+
comp = strip_domain(subdomain)
|
204
|
+
return true if comp.domain == jid.domain
|
205
|
+
local_jid?(jid) ? cross_domain?(comp, jid) : cross_domain?(comp)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return the third-level JID's domain with the first subdomain stripped off
|
209
|
+
# to create a second-level domain. For example, alice@tea.wonderland.lit
|
210
|
+
# returns wonderland.lit.
|
211
|
+
def strip_domain(jid)
|
212
|
+
domain = jid.domain.split('.').drop(1).join('.')
|
213
|
+
JID.new(domain)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Return true if all JIDs are allowed to exchange cross domain messages.
|
217
|
+
def cross_domain?(*jids)
|
218
|
+
!jids.flatten.index do |jid|
|
219
|
+
!vhost(jid.domain).cross_domain_messages?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Contact
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
attr_accessor :name, :subscription, :ask, :groups
|
8
|
+
attr_reader :jid
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
@jid = JID.new(args[:jid]).bare
|
12
|
+
raise ArgumentError, 'invalid jid' if @jid.empty?
|
13
|
+
@name = args[:name]
|
14
|
+
@subscription = args[:subscription] || 'none'
|
15
|
+
@ask = args[:ask]
|
16
|
+
@groups = args[:groups] || []
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(contact)
|
20
|
+
contact.is_a?(Contact) ? self.jid.to_s <=> contact.jid.to_s : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
alias :eql? :==
|
24
|
+
|
25
|
+
def hash
|
26
|
+
jid.to_s.hash
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_from(contact)
|
30
|
+
@name = contact.name
|
31
|
+
@subscription = contact.subscription
|
32
|
+
@ask = contact.ask
|
33
|
+
@groups = contact.groups.clone
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if this contact is in a state that allows the user
|
37
|
+
# to subscribe to their presence updates.
|
38
|
+
def can_subscribe?
|
39
|
+
@ask == 'subscribe' && %w[none from].include?(@subscription)
|
40
|
+
end
|
41
|
+
|
42
|
+
def subscribe_to
|
43
|
+
@subscription = (@subscription == 'none') ? 'to' : 'both'
|
44
|
+
@ask = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def unsubscribe_to
|
48
|
+
@subscription = (@subscription == 'both') ? 'from' : 'none'
|
49
|
+
end
|
50
|
+
|
51
|
+
def subscribe_from
|
52
|
+
@subscription = (@subscription == 'none') ? 'from' : 'both'
|
53
|
+
@ask = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def unsubscribe_from
|
57
|
+
@subscription = (@subscription == 'both') ? 'to' : 'none'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns true if the user is subscribed to this contact's
|
61
|
+
# presence updates.
|
62
|
+
def subscribed_to?
|
63
|
+
%w[to both].include?(@subscription)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if the user has a presence subscription from
|
67
|
+
# this contact. The contact is subscribed to this user's presence.
|
68
|
+
def subscribed_from?
|
69
|
+
%w[from both].include?(@subscription)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a hash of this contact's attributes suitable for persisting in
|
73
|
+
# a document store.
|
74
|
+
def to_h
|
75
|
+
{
|
76
|
+
'name' => @name,
|
77
|
+
'subscription' => @subscription,
|
78
|
+
'ask' => @ask,
|
79
|
+
'groups' => @groups.sort!
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Write an iq stanza to the recipient stream representing this contact's
|
84
|
+
# current roster item state.
|
85
|
+
def send_roster_push(recipient)
|
86
|
+
doc = Nokogiri::XML::Document.new
|
87
|
+
node = doc.create_element('iq',
|
88
|
+
'id' => Kit.uuid,
|
89
|
+
'to' => recipient.user.jid.to_s,
|
90
|
+
'type' => 'set')
|
91
|
+
node << doc.create_element('query', 'xmlns' => NAMESPACES[:roster]) do |query|
|
92
|
+
query << to_roster_xml
|
93
|
+
end
|
94
|
+
recipient.write(node)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns this contact as an xmpp <item> element.
|
98
|
+
def to_roster_xml
|
99
|
+
doc = Nokogiri::XML::Document.new
|
100
|
+
doc.create_element('item') do |el|
|
101
|
+
el['ask'] = @ask unless @ask.nil? || @ask.empty?
|
102
|
+
el['jid'] = @jid.bare.to_s
|
103
|
+
el['name'] = @name unless @name.nil? || @name.empty?
|
104
|
+
el['subscription'] = @subscription
|
105
|
+
@groups.sort!.each do |group|
|
106
|
+
el << doc.create_element('group', group)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/vines/daemon.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
|
5
|
+
# Fork the current process into the background and manage pid
|
6
|
+
# files so we can kill the process later.
|
7
|
+
class Daemon
|
8
|
+
|
9
|
+
# Configure a new daemon process. Arguments hash can include the following
|
10
|
+
# keys: :pid (pid file name, required),
|
11
|
+
# :stdin, :stdout, :stderr (default to /dev/null)
|
12
|
+
def initialize(args)
|
13
|
+
@pid = args[:pid]
|
14
|
+
raise ArgumentError.new('pid file is required') unless @pid
|
15
|
+
raise ArgumentError.new('pid must be a file name') if File.directory?(@pid)
|
16
|
+
raise ArgumentError.new('pid file must be writable') unless File.writable?(File.dirname(@pid))
|
17
|
+
@stdin, @stdout, @stderr = [:stdin, :stdout, :stderr].map {|k| args[k] || '/dev/null' }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fork the current process into the background to start the
|
21
|
+
# daemon. Do nothing if the daemon is already running.
|
22
|
+
def start
|
23
|
+
daemonize unless running?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use the pid stored in the pid file created from a previous
|
27
|
+
# call to start to send a TERM signal to the process. Do nothing
|
28
|
+
# if the daemon is not running.
|
29
|
+
def stop
|
30
|
+
10.times do
|
31
|
+
break unless running?
|
32
|
+
Process.kill('TERM', pid)
|
33
|
+
sleep(0.1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true if the process is running as determined by the numeric
|
38
|
+
# pid stored in the pid file created by a previous call to start.
|
39
|
+
def running?
|
40
|
+
begin
|
41
|
+
pid && Process.kill(0, pid)
|
42
|
+
rescue Errno::ESRCH
|
43
|
+
delete_pid
|
44
|
+
false
|
45
|
+
rescue Errno::EPERM
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the numeric process ID from the pid file.
|
51
|
+
# If the pid file does not exist, returns nil.
|
52
|
+
def pid
|
53
|
+
File.read(@pid).to_i if File.exists?(@pid)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def delete_pid
|
59
|
+
File.delete(@pid) if File.exists?(@pid)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fork process into background twice to release it from
|
63
|
+
# the controlling tty. Point open file descriptors shared
|
64
|
+
# with the parent process to separate destinations (e.g. /dev/null).
|
65
|
+
def daemonize
|
66
|
+
exit if fork
|
67
|
+
Process.setsid
|
68
|
+
exit if fork
|
69
|
+
Dir.chdir('/')
|
70
|
+
$stdin.reopen(@stdin)
|
71
|
+
$stdout.reopen(@stdout, 'a').sync = true
|
72
|
+
$stderr.reopen(@stderr, 'a').sync = true
|
73
|
+
File.open(@pid, 'w') {|f| f.write(Process.pid) }
|
74
|
+
at_exit { delete_pid }
|
75
|
+
trap('TERM') { exit }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|