vinesmod 0.4.5

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 (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -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/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -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 +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
@@ -0,0 +1,68 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Init
6
+ def run(opts)
7
+ raise 'vines init <domain>' unless opts[:args].size == 1
8
+ domain = opts[:args].first.downcase
9
+ dir = File.expand_path(domain)
10
+ raise "Directory already initialized: #{domain}" if File.exists?(dir)
11
+ Dir.mkdir(dir)
12
+
13
+ create_directories(dir)
14
+ create_users(domain, dir)
15
+ update_config(domain, dir)
16
+ Command::Cert.new.create_cert(domain, File.join(dir, 'conf/certs'))
17
+
18
+ puts "Initialized server directory: #{domain}"
19
+ puts "Run 'cd #{domain} && vines start' to begin"
20
+ end
21
+
22
+ private
23
+
24
+ # Limit file system database directory access so the server is the only
25
+ # process managing the data. The config.rb file contains component and
26
+ # database passwords, so restrict access to just the server user as well.
27
+ def create_directories(dir)
28
+ %w[conf web].each do |sub|
29
+ FileUtils.cp_r(File.expand_path("../../../../#{sub}", __FILE__), dir)
30
+ end
31
+ %w[data log pid].each do |sub|
32
+ Dir.mkdir(File.join(dir, sub), 0700)
33
+ end
34
+ File.chmod(0600, File.join(dir, 'conf/config.rb'))
35
+ end
36
+
37
+ def update_config(domain, dir)
38
+ config = File.expand_path('conf/config.rb', dir)
39
+ text = File.read(config, encoding: 'utf-8')
40
+ File.open(config, 'w:utf-8') do |f|
41
+ f.write(text.gsub('wonderland.lit', domain))
42
+ end
43
+ end
44
+
45
+ def create_users(domain, dir)
46
+ password = 'secr3t'
47
+ alice, arthur = %w[alice arthur].map do |jid|
48
+ User.new(jid: [jid, domain].join('@'),
49
+ password: BCrypt::Password.create(password).to_s)
50
+ end
51
+
52
+ [[alice, arthur], [arthur, alice]].each do |user, contact|
53
+ user.roster << Contact.new(
54
+ jid: contact.jid,
55
+ name: contact.jid.node.capitalize,
56
+ subscription: 'both',
57
+ groups: %w[Buddies])
58
+ end
59
+
60
+ storage = Storage::Local.new { dir(File.join(dir, 'data')) }
61
+ [alice, arthur].each do |user|
62
+ storage.save_user(user)
63
+ puts "Created example user #{user.jid} with password #{password}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Register
6
+ def run(opts)
7
+ raise 'vines register <login> <password> <domain>' unless opts[:args].size == 3
8
+ require opts[:config]
9
+
10
+ login, password, domain = opts[:args]
11
+
12
+ create_user(domain, login, password)
13
+ end
14
+
15
+ def create_user(domain, jid, password)
16
+ user = User.new(jid: [jid, domain].join('@'),
17
+ password: BCrypt::Password.create(password).to_s)
18
+
19
+ storage = Storage::Local.new { dir(Config.instance.storage(domain).dir) }
20
+ storage.save_user(user)
21
+
22
+ puts "Created user #{user.jid} with password #{password}"
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Restart
6
+ def run(opts)
7
+ Stop.new.run(opts)
8
+ Start.new.run(opts)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Schema
6
+ def run(opts)
7
+ raise 'vines schema <domain>' unless opts[:args].size == 1
8
+ require opts[:config]
9
+ domain = opts[:args].first
10
+ unless storage = Config.instance.vhost(domain).storage rescue nil
11
+ raise "#{domain} virtual host not found in conf/config.rb"
12
+ end
13
+ unless storage.respond_to?(:create_schema)
14
+ raise "SQL storage not configured for #{domain} virtual host"
15
+ end
16
+ begin
17
+ storage.create_schema
18
+ rescue => e
19
+ raise "Schema creation failed: #{e.message}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Start
6
+ def run(opts)
7
+ raise 'vines [--pid FILE] start' unless opts[:args].size == 0
8
+ require opts[:config]
9
+ server = XmppServer.new(Config.instance)
10
+ daemonize(opts) if opts[:daemonize]
11
+ server.start
12
+ end
13
+
14
+ private
15
+
16
+ def daemonize(opts)
17
+ daemon = Daemon.new(:pid => opts[:pid], :stdout => opts[:log],
18
+ :stderr => opts[:log])
19
+ if daemon.running?
20
+ raise "Vines is running as process #{daemon.pid}"
21
+ else
22
+ puts "Vines has started"
23
+ daemon.start
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Stop
6
+ def run(opts)
7
+ raise 'vines [--pid FILE] stop' unless opts[:args].size == 0
8
+ daemon = Daemon.new(:pid => opts[:pid])
9
+ if daemon.running?
10
+ daemon.stop
11
+ puts 'Vines has been shutdown'
12
+ else
13
+ puts 'Vines is not running'
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Unregister
6
+ def run(opts)
7
+ raise 'vines unregister <login> <password> <domain>' unless opts[:args].size == 3
8
+ require opts[:config]
9
+
10
+ login, password, domain = opts[:args]
11
+
12
+ delete_user(domain, login, password)
13
+ end
14
+
15
+ def delete_user(domain, jid, password)
16
+ user = User.new(jid: [jid, domain].join('@'),
17
+ password: BCrypt::Password.create(password).to_s)
18
+
19
+ storage = Storage::Local.new { dir(Config.instance.storage(domain).dir) }
20
+ storage.save_user(user)
21
+
22
+ puts "Created user #{user.jid} with password #{password}"
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,213 @@
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
+
31
+ unless @certs && File.directory?(@certs) && File.readable?(@certs)
32
+ raise 'Must provide a readable certs directory'
33
+ end
34
+ end
35
+
36
+ def certs(dir=nil)
37
+ dir ? @certs = File.expand_path(dir) : @certs
38
+ end
39
+
40
+ def host(*names, &block)
41
+ names = names.flatten.map {|name| name.downcase }
42
+ dupes = names.uniq.size != names.size || (@vhosts.keys & names).any?
43
+ raise "one host definition per domain allowed" if dupes
44
+ names.each do |name|
45
+ @vhosts[name] = Host.new(self, name, &block)
46
+ end
47
+ end
48
+
49
+ %w[client server http component].each do |name|
50
+ define_method(name) do |*args, &block|
51
+ port = Vines::Config.const_get("#{name.capitalize}Port")
52
+ raise "one #{name} port definition allowed" if @ports[name.to_sym]
53
+ @ports[name.to_sym] = port.new(self, *args) do
54
+ instance_eval(&block) if block
55
+ end
56
+ end
57
+ end
58
+
59
+ def cluster(&block)
60
+ return @cluster unless block
61
+ raise "one cluster definition allowed" if @cluster
62
+ @cluster = Cluster.new(self, &block)
63
+ end
64
+
65
+ def log(level)
66
+ const = Logger.const_get(level.to_s.upcase) rescue nil
67
+ unless LOG_LEVELS.include?(level.to_s) && const
68
+ raise "log level must be one of: #{LOG_LEVELS.join(', ')}"
69
+ end
70
+ Class.new.extend(Vines::Log).log.level = const
71
+ end
72
+
73
+ def ports
74
+ @ports.values
75
+ end
76
+
77
+ # Return true if the domain is virtual hosted by this server.
78
+ def vhost?(domain)
79
+ !!vhost(domain)
80
+ end
81
+
82
+ # Return the Host config object for this domain if it's hosted by this
83
+ # server.
84
+ def vhost(domain)
85
+ @vhosts[domain.to_s]
86
+ end
87
+
88
+ # Returns the storage system for the domain or a Storage::Null instance if
89
+ # the domain is not hosted at this server.
90
+ def storage(domain)
91
+ host = vhost(domain)
92
+ host ? host.storage : @null
93
+ end
94
+
95
+ # Returns the PubSub system for the domain or nil if pubsub is not enabled
96
+ # for this domain.
97
+ def pubsub(domain)
98
+ host = @vhosts.values.find {|host| host.pubsub?(domain) }
99
+ host.pubsubs[domain.to_s] if host
100
+ end
101
+
102
+ # Return true if the domain is a pubsub service hosted at a virtual host
103
+ # at this server.
104
+ def pubsub?(domain)
105
+ @vhosts.values.any? {|host| host.pubsub?(domain) }
106
+ end
107
+
108
+ # Return true if all JIDs belong to components hosted by this server.
109
+ def component?(*jids)
110
+ !jids.flatten.index do |jid|
111
+ !component_password(JID.new(jid).domain)
112
+ end
113
+ end
114
+
115
+ # Return the password for the component or nil if it's not hosted here.
116
+ def component_password(domain)
117
+ host = @vhosts.values.find {|host| host.component?(domain) }
118
+ host.password(domain) if host
119
+ end
120
+
121
+ # Return true if all of the JIDs are hosted by this server.
122
+ def local_jid?(*jids)
123
+ !jids.flatten.index do |jid|
124
+ !vhost?(JID.new(jid).domain)
125
+ end
126
+ end
127
+
128
+ # Return true if private XML fragment storage is enabled for this domain.
129
+ def private_storage?(domain)
130
+ host = vhost(domain)
131
+ host.private_storage? if host
132
+ end
133
+
134
+ # Returns true if server-to-server connections are allowed with the
135
+ # given domain.
136
+ def s2s?(domain)
137
+ @ports[:server] && @ports[:server].hosts.include?(domain.to_s)
138
+ end
139
+
140
+ # Return true if the server is a member of a cluster, serving the same
141
+ # domains from different machines.
142
+ def cluster?
143
+ !!@cluster
144
+ end
145
+
146
+ # Retrieve the Port subclass with this name:
147
+ # [:client, :server, :http, :component]
148
+ def [](name)
149
+ @ports[name] or raise ArgumentError.new("no port named #{name}")
150
+ end
151
+
152
+ # Return true if the two JIDs are allowed to send messages to each other.
153
+ # Both domains must have enabled cross_domain_messages in their config files.
154
+ def allowed?(to, from)
155
+ to, from = JID.new(to), JID.new(from)
156
+ return false if to.empty? || from.empty?
157
+ return true if to.domain == from.domain # same domain always allowed
158
+ return cross_domain?(to, from) if local_jid?(to, from) # both virtual hosted here
159
+ return check_subdomains(to, from) if subdomain?(to, from) # component/pubsub to component/pubsub
160
+ return check_subdomain(to, from) if subdomain?(to) # to component/pubsub
161
+ return check_subdomain(from, to) if subdomain?(from) # from component/pubsub
162
+ return cross_domain?(to) if local_jid?(to) # from is remote
163
+ return cross_domain?(from) if local_jid?(from) # to is remote
164
+ return false
165
+ end
166
+
167
+ private
168
+
169
+ # Return true if all of the JIDs are some kind of subdomain resource hosted
170
+ # here (either a component or a pubsub domain).
171
+ def subdomain?(*jids)
172
+ !jids.flatten.index do |jid|
173
+ !component?(jid) && !pubsub?(jid)
174
+ end
175
+ end
176
+
177
+ # Return true if the third-level subdomain JIDs (components and pubsubs)
178
+ # are allowed to communicate with each other. For example, a
179
+ # tea.wonderland.lit component should be allowed to send messages to
180
+ # pubsub.wonderland.lit because they share the second-level wonderland.lit
181
+ # domain.
182
+ def check_subdomains(to, from)
183
+ sub1, sub2 = strip_domain(to), strip_domain(from)
184
+ (sub1 == sub2) || cross_domain?(sub1, sub2)
185
+ end
186
+
187
+ # Return true if the third-level subdomain JID (component or pubsub) is
188
+ # allowed to communicate with the other JID. For example,
189
+ # pubsub.wonderland.lit should be allowed to send messages to
190
+ # alice@wonderland.lit because they share the second-level wonderland.lit
191
+ # domain.
192
+ def check_subdomain(subdomain, jid)
193
+ comp = strip_domain(subdomain)
194
+ return true if comp.domain == jid.domain
195
+ local_jid?(jid) ? cross_domain?(comp, jid) : cross_domain?(comp)
196
+ end
197
+
198
+ # Return the third-level JID's domain with the first subdomain stripped off
199
+ # to create a second-level domain. For example, alice@tea.wonderland.lit
200
+ # returns wonderland.lit.
201
+ def strip_domain(jid)
202
+ domain = jid.domain.split('.').drop(1).join('.')
203
+ JID.new(domain)
204
+ end
205
+
206
+ # Return true if all JIDs are allowed to exchange cross domain messages.
207
+ def cross_domain?(*jids)
208
+ !jids.flatten.index do |jid|
209
+ !vhost(jid.domain).cross_domain_messages?
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,119 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Config
5
+
6
+ # Provides the DSL methods for the virtual host definitions in the
7
+ # conf/config.rb file. Host instances can be accessed at runtime through
8
+ # the +Config#vhosts+ method.
9
+ class Host
10
+ attr_reader :pubsubs
11
+
12
+ def initialize(config, name, &block)
13
+ @config, @name = config, name.downcase
14
+ @storage = nil
15
+ @cross_domain_messages = false
16
+ @private_storage = false
17
+ @components, @pubsubs = {}, {}
18
+ validate_domain(@name)
19
+ instance_eval(&block)
20
+ raise "storage required for #{@name}" unless @storage
21
+ end
22
+
23
+ def storage(name=nil, &block)
24
+ if name
25
+ raise "one storage mechanism per host allowed" if @storage
26
+ @storage = Storage.from_name(name, &block)
27
+ else
28
+ @storage
29
+ end
30
+ end
31
+
32
+ def cross_domain_messages(enabled)
33
+ @cross_domain_messages = !!enabled
34
+ end
35
+
36
+ def cross_domain_messages?
37
+ @cross_domain_messages
38
+ end
39
+
40
+ def components(options=nil)
41
+ return @components unless options
42
+
43
+ names = options.keys.map {|domain| "#{domain}.#{@name}".downcase }
44
+ raise "duplicate component domains not allowed" if dupes?(names, @components.keys)
45
+ raise "pubsub domains overlap component domains" if dupes?(names, @pubsubs.keys)
46
+
47
+ options.each do |domain, password|
48
+ raise 'component domain required' if (domain || '').to_s.strip.empty?
49
+ raise 'component password required' if (password || '').strip.empty?
50
+ name = "#{domain}.#{@name}".downcase
51
+ raise "components must be one level below their host: #{name}" if domain.to_s.include?('.')
52
+ validate_domain(name)
53
+ @components[name] = password
54
+ end
55
+ end
56
+
57
+ def component?(domain)
58
+ !!@components[domain.to_s]
59
+ end
60
+
61
+ def password(domain)
62
+ @components[domain.to_s]
63
+ end
64
+
65
+ def pubsub(*domains)
66
+ domains.flatten!
67
+ raise 'define at least one pubsub domain' if domains.empty?
68
+ names = domains.map {|domain| "#{domain}.#{@name}".downcase }
69
+ raise "duplicate pubsub domains not allowed" if dupes?(names, @pubsubs.keys)
70
+ raise "pubsub domains overlap component domains" if dupes?(names, @components.keys)
71
+ domains.each do |domain|
72
+ raise 'pubsub domain required' if (domain || '').to_s.strip.empty?
73
+ name = "#{domain}.#{@name}".downcase
74
+ raise "pubsub domains must be one level below their host: #{name}" if domain.to_s.include?('.')
75
+ validate_domain(name)
76
+ @pubsubs[name] = PubSub.new(@config, name)
77
+ end
78
+ end
79
+
80
+ def pubsub?(domain)
81
+ @pubsubs.key?(domain.to_s)
82
+ end
83
+
84
+ # Unsubscribe this JID from all pubsub topics hosted at this virtual host.
85
+ # This should be called when the user's session ends via logout or
86
+ # disconnect.
87
+ def unsubscribe_pubsub(jid)
88
+ @pubsubs.values.each do |pubsub|
89
+ pubsub.unsubscribe_all(jid)
90
+ end
91
+ end
92
+
93
+ def disco_items
94
+ [@components.keys, @pubsubs.keys].flatten.sort
95
+ end
96
+
97
+ def private_storage(enabled)
98
+ @private_storage = !!enabled
99
+ end
100
+
101
+ def private_storage?
102
+ @private_storage
103
+ end
104
+
105
+ private
106
+
107
+ # Return true if the arrays contain any duplicate items.
108
+ def dupes?(a, b)
109
+ a.uniq.size != a.size || b.uniq.size != b.size || (a & b).any?
110
+ end
111
+
112
+ # Prevent domains in config files that won't form valid JIDs.
113
+ def validate_domain(name)
114
+ jid = JID.new(name)
115
+ raise "incorrect domain: #{name}" if jid.node || jid.resource
116
+ end
117
+ end
118
+ end
119
+ end