vines 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/README +1 -1
  2. data/Rakefile +10 -10
  3. data/conf/certs/ca-bundle.crt +112 -378
  4. data/conf/config.rb +18 -9
  5. data/lib/vines.rb +8 -1
  6. data/lib/vines/command/cert.rb +2 -1
  7. data/lib/vines/command/init.rb +11 -0
  8. data/lib/vines/command/ldap.rb +6 -3
  9. data/lib/vines/command/schema.rb +1 -1
  10. data/lib/vines/config.rb +57 -146
  11. data/lib/vines/config/host.rb +85 -0
  12. data/lib/vines/config/port.rb +111 -0
  13. data/lib/vines/contact.rb +1 -1
  14. data/lib/vines/jid.rb +26 -4
  15. data/lib/vines/kit.rb +6 -0
  16. data/lib/vines/log.rb +24 -0
  17. data/lib/vines/router.rb +70 -38
  18. data/lib/vines/stanza.rb +45 -8
  19. data/lib/vines/stanza/iq.rb +3 -3
  20. data/lib/vines/stanza/iq/disco_info.rb +5 -1
  21. data/lib/vines/stanza/iq/disco_items.rb +3 -0
  22. data/lib/vines/stanza/iq/private_storage.rb +9 -5
  23. data/lib/vines/stanza/iq/roster.rb +11 -12
  24. data/lib/vines/stanza/iq/vcard.rb +4 -4
  25. data/lib/vines/stanza/iq/version.rb +25 -0
  26. data/lib/vines/stanza/message.rb +4 -5
  27. data/lib/vines/stanza/presence.rb +20 -18
  28. data/lib/vines/stanza/presence/probe.rb +3 -4
  29. data/lib/vines/stanza/presence/subscribe.rb +4 -3
  30. data/lib/vines/stanza/presence/subscribed.rb +6 -5
  31. data/lib/vines/stanza/presence/unsubscribe.rb +4 -4
  32. data/lib/vines/stanza/presence/unsubscribed.rb +4 -3
  33. data/lib/vines/storage/couchdb.rb +3 -3
  34. data/lib/vines/storage/ldap.rb +19 -8
  35. data/lib/vines/storage/local.rb +23 -12
  36. data/lib/vines/storage/redis.rb +3 -3
  37. data/lib/vines/storage/sql.rb +5 -5
  38. data/lib/vines/stream.rb +40 -6
  39. data/lib/vines/stream/client.rb +5 -6
  40. data/lib/vines/stream/client/auth.rb +3 -2
  41. data/lib/vines/stream/client/bind.rb +2 -2
  42. data/lib/vines/stream/client/bind_restart.rb +1 -2
  43. data/lib/vines/stream/client/ready.rb +2 -0
  44. data/lib/vines/stream/client/session.rb +13 -4
  45. data/lib/vines/stream/client/tls.rb +1 -0
  46. data/lib/vines/stream/component.rb +6 -5
  47. data/lib/vines/stream/component/ready.rb +5 -6
  48. data/lib/vines/stream/http.rb +10 -4
  49. data/lib/vines/stream/http/request.rb +23 -2
  50. data/lib/vines/stream/server.rb +13 -11
  51. data/lib/vines/stream/server/outbound/auth_result.rb +1 -0
  52. data/lib/vines/stream/server/outbound/tls_result.rb +1 -0
  53. data/lib/vines/stream/server/ready.rb +2 -2
  54. data/lib/vines/user.rb +2 -1
  55. data/lib/vines/version.rb +1 -1
  56. data/test/config/host_test.rb +292 -0
  57. data/test/config_test.rb +244 -103
  58. data/test/contact_test.rb +7 -1
  59. data/test/jid_test.rb +48 -0
  60. data/test/router_test.rb +16 -47
  61. data/test/stanza/iq/disco_info_test.rb +76 -0
  62. data/test/stanza/iq/disco_items_test.rb +47 -0
  63. data/test/stanza/iq/private_storage_test.rb +33 -10
  64. data/test/stanza/iq/roster_test.rb +15 -5
  65. data/test/stanza/iq/vcard_test.rb +8 -25
  66. data/test/stanza/iq/version_test.rb +62 -0
  67. data/test/stanza/iq_test.rb +13 -10
  68. data/test/stanza/message_test.rb +16 -24
  69. data/test/stanza/presence/probe_test.rb +52 -0
  70. data/test/stanza/presence/subscribe_test.rb +1 -5
  71. data/test/stanza_test.rb +77 -0
  72. data/test/stream/client/auth_test.rb +1 -0
  73. data/test/stream/client/ready_test.rb +2 -0
  74. data/test/stream/client/session_test.rb +7 -2
  75. data/test/stream/component/ready_test.rb +19 -36
  76. data/test/stream/http/request_test.rb +22 -2
  77. data/test/stream/server/ready_test.rb +14 -21
  78. data/web/404.html +9 -3
  79. data/web/chat/index.html +2 -2
  80. data/web/chat/javascripts/app.js +1 -1
  81. data/web/chat/stylesheets/chat.css +4 -9
  82. data/web/lib/coffeescripts/layout.coffee +2 -2
  83. data/web/{chat → lib}/coffeescripts/logout.coffee +0 -0
  84. data/web/lib/coffeescripts/notification.coffee +14 -0
  85. data/web/lib/coffeescripts/session.coffee +28 -24
  86. data/web/lib/coffeescripts/transfer.coffee +37 -34
  87. data/web/lib/javascripts/base.js +8 -8
  88. data/web/lib/javascripts/icons.js +3 -0
  89. data/web/lib/javascripts/jquery.js +4 -18
  90. data/web/lib/javascripts/layout.js +2 -2
  91. data/web/{chat → lib}/javascripts/logout.js +0 -0
  92. data/web/lib/javascripts/notification.js +26 -0
  93. data/web/lib/javascripts/session.js +20 -16
  94. data/web/lib/javascripts/transfer.js +45 -55
  95. data/web/lib/stylesheets/base.css +45 -9
  96. metadata +31 -15
data/conf/config.rb CHANGED
@@ -16,17 +16,28 @@ Vines::Config.configure do
16
16
  # command. Change the example, 'wonderland.lit', domain name to your actual
17
17
  # domain.
18
18
  #
19
+ # The private_storage attribute allows clients to store XML fragments
20
+ # on the server, using the XEP-0049 Private XML Storage feature.
21
+ #
19
22
  # Shared storage example:
20
23
  # host 'verona.lit', 'wonderland.lit' do
24
+ # private_storage false
25
+ # cross_domain_messages false
21
26
  # storage 'fs' do
22
27
  # dir 'data/users'
23
28
  # end
29
+ # components 'tea' => 'secr3t',
30
+ # 'cake' => 'passw0rd'
24
31
  # end
25
32
 
26
33
  host 'wonderland.lit' do
34
+ cross_domain_messages false
35
+ private_storage false
27
36
  storage 'fs' do
28
37
  dir 'data/users'
29
38
  end
39
+ # components 'tea' => 'secr3t',
40
+ # 'cake' => 'passw0rd'
30
41
  end
31
42
 
32
43
  # Hosts can use LDAP authentication that overrides the authentication
@@ -35,6 +46,8 @@ Vines::Config.configure do
35
46
  # information, like rosters, is still saved in the storage database.
36
47
  #
37
48
  # host 'wonderland.lit' do
49
+ # cross_domain_messages false
50
+ # private_storage false
38
51
  # storage 'fs' do
39
52
  # dir 'data/users'
40
53
  # end
@@ -42,19 +55,19 @@ Vines::Config.configure do
42
55
  # dn 'cn=Directory Manager'
43
56
  # password 'secr3t'
44
57
  # basedn 'dc=wonderland,dc=lit'
58
+ # groupdn 'cn=chatters,dc=wonderland,dc=lit' # optional
45
59
  # object_class 'person'
46
60
  # user_attr 'uid'
47
61
  # name_attr 'cn'
48
62
  # tls true
49
63
  # end
64
+ # components 'tea' => 'secr3t',
65
+ # 'cake' => 'passw0rd'
50
66
  # end
51
67
 
52
68
  # Configure the client-to-server port. The max_resources_per_account attribute
53
69
  # limits how many concurrent connections one user can have to the server.
54
- # The private_storage attribute allows clients to store XML fragments
55
- # on the server, using the XEP-0049 Private XML Storage feature.
56
70
  client '0.0.0.0', 5222 do
57
- private_storage true
58
71
  max_stanza_size 65536
59
72
  max_resources_per_account 5
60
73
  end
@@ -75,19 +88,15 @@ Vines::Config.configure do
75
88
  # the URL to which BOSH clients must POST their XMPP stanza requests.
76
89
  http '0.0.0.0', 5280 do
77
90
  bind '/xmpp'
78
- private_storage true
79
91
  max_stanza_size 65536
80
92
  max_resources_per_account 5
81
93
  root 'web'
82
94
  end
83
95
 
84
- # Configure the XEP-0114 external component port. Add entries for each
85
- # component sub-domain allowed to connect to this server. Components must
86
- # authenticate with a password.
96
+ # Configure the XEP-0114 external component port. Component sub-domains and
97
+ # their passwords are defined with their virtual host entries above.
87
98
  component '0.0.0.0', 5347 do
88
99
  max_stanza_size 131072
89
- #components 'tea.wonderland.lit' => 'secr3t',
90
- # 'cake.wonderland.lit' => 'passw0rd'
91
100
  end
92
101
  end
93
102
 
data/lib/vines.rb CHANGED
@@ -9,6 +9,7 @@ module Vines
9
9
  :roster => 'jabber:iq:roster'.freeze,
10
10
  :non_sasl => 'jabber:iq:auth'.freeze,
11
11
  :storage => 'jabber:iq:private'.freeze,
12
+ :version => 'jabber:iq:version'.freeze,
12
13
  :sasl => 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze,
13
14
  :tls => 'urn:ietf:params:xml:ns:xmpp-tls'.freeze,
14
15
  :bind => 'urn:ietf:params:xml:ns:xmpp-bind'.freeze,
@@ -65,7 +66,9 @@ end
65
66
  uri
66
67
  yaml
67
68
 
69
+ vines/log
68
70
  vines/jid
71
+
69
72
  vines/stanza
70
73
  vines/stanza/iq
71
74
  vines/stanza/iq/query
@@ -79,6 +82,7 @@ end
79
82
  vines/stanza/iq/roster
80
83
  vines/stanza/iq/session
81
84
  vines/stanza/iq/vcard
85
+ vines/stanza/iq/version
82
86
  vines/stanza/message
83
87
  vines/stanza/presence
84
88
  vines/stanza/presence/error
@@ -96,9 +100,12 @@ end
96
100
  vines/storage/redis
97
101
  vines/storage/sql
98
102
 
103
+ vines/config
104
+ vines/config/host
105
+ vines/config/port
106
+
99
107
  vines/store
100
108
  vines/contact
101
- vines/config
102
109
  vines/daemon
103
110
  vines/error
104
111
  vines/kit
@@ -5,7 +5,7 @@ module Vines
5
5
  class Cert
6
6
  def run(opts)
7
7
  raise 'vines cert <domain>' unless opts[:args].size == 1
8
- dir = File.expand_path(File.join(opts[:config], '../certs'))
8
+ dir = File.expand_path('../certs', opts[:config])
9
9
  create_cert(opts[:args].first, dir)
10
10
  end
11
11
 
@@ -36,6 +36,7 @@ module Vines
36
36
  {'key' => key, 'crt' => cert}.each_pair do |ext, o|
37
37
  name = File.join(dir, "#{domain}.#{ext}")
38
38
  File.open(name, "w") {|f| f.write(o.to_pem) }
39
+ File.chmod(0600, name) if ext == 'key'
39
40
  end
40
41
  end
41
42
 
@@ -19,6 +19,7 @@ module Vines
19
19
 
20
20
  create_users(domain, users)
21
21
  update_config(domain, File.join(dir, 'conf', 'config.rb'))
22
+ fix_perms(dir)
22
23
  Command::Cert.new.create_cert(domain, File.join(dir, 'conf/certs'))
23
24
 
24
25
  puts "Initialized server directory: #{domain}"
@@ -27,6 +28,16 @@ module Vines
27
28
 
28
29
  private
29
30
 
31
+ # Limit file system database directory access so the server is the only
32
+ # process managing the data. The config.rb file contains component and
33
+ # database passwords, so restrict access to just the server user as well.
34
+ def fix_perms(dir)
35
+ %w[data data/users].each do |f|
36
+ File.chmod(0700, File.join(dir, f))
37
+ end
38
+ File.chmod(0600, File.join(dir, 'conf/config.rb'))
39
+ end
40
+
30
41
  def update_config(domain, config)
31
42
  text = File.read(config)
32
43
  File.open(config, 'w') do |f|
@@ -7,7 +7,7 @@ module Vines
7
7
  raise 'vines ldap <domain>' unless opts[:args].size == 1
8
8
  require opts[:config]
9
9
  domain = opts[:args].first
10
- unless storage = Config.instance.vhosts[domain]
10
+ unless storage = Config.instance.vhosts[domain].storage rescue nil
11
11
  raise "#{domain} virtual host not found in conf/config.rb"
12
12
  end
13
13
  unless storage.ldap?
@@ -27,8 +27,11 @@ module Vines
27
27
  rescue Exception => e
28
28
  raise "LDAP connection failed: #{e.message}"
29
29
  end
30
- raise "User not found" unless user
31
- puts "Found #{user.jid} with name: #{user.name}"
30
+
31
+ filter = storage.ldap.filter(jid)
32
+ raise "User not found with filter:\n #{filter}" unless user
33
+ name = user.name.empty? ? '<name missing>' : user.name
34
+ puts "Found user #{name} with filter:\n #{filter}"
32
35
  end
33
36
  end
34
37
  end
@@ -7,7 +7,7 @@ module Vines
7
7
  raise 'vines schema <domain>' unless opts[:args].size == 1
8
8
  require opts[:config]
9
9
  domain = opts[:args].first
10
- unless storage = Config.instance.vhosts[domain]
10
+ unless storage = Config.instance.vhosts[domain].storage rescue nil
11
11
  raise "#{domain} virtual host not found in conf/config.rb"
12
12
  end
13
13
  unless storage.respond_to?(:create_schema)
data/lib/vines/config.rb CHANGED
@@ -31,7 +31,7 @@ module Vines
31
31
  dupes = names.uniq.size != names.size || (@vhosts.keys & names).any?
32
32
  raise "one host definition per domain allowed" if dupes
33
33
  names.each do |name|
34
- @vhosts.merge! Host.new(name, &block).to_hash
34
+ @vhosts[name] = Host.new(name, &block)
35
35
  end
36
36
  end
37
37
 
@@ -57,10 +57,36 @@ module Vines
57
57
  @ports.values
58
58
  end
59
59
 
60
+ # Return true if the domain is virtual hosted by this server.
60
61
  def vhost?(domain)
61
62
  @vhosts.key?(domain)
62
63
  end
63
64
 
65
+ # Return true if all JID's belong to components hosted by this server.
66
+ def component?(*jids)
67
+ !jids.flatten.index do |jid|
68
+ !component_password(JID.new(jid).domain)
69
+ end
70
+ end
71
+
72
+ # Return the password for the component or nil if it's not hosted here.
73
+ def component_password(domain)
74
+ host = @vhosts.values.find {|host| host.component?(domain) }
75
+ host.password(domain) if host
76
+ end
77
+
78
+ # Return true if all of the JID's are hosted by this server.
79
+ def local_jid?(*jids)
80
+ !jids.flatten.index do |jid|
81
+ !vhost?(JID.new(jid).domain)
82
+ end
83
+ end
84
+
85
+ # Return true if private XML fragment storage is enabled for this domain.
86
+ def private_storage?(domain)
87
+ @vhosts[domain].private_storage?
88
+ end
89
+
64
90
  # Returns true if server-to-server connections are allowed with the
65
91
  # given domain.
66
92
  def s2s?(domain)
@@ -73,160 +99,45 @@ module Vines
73
99
  @ports[name] or raise ArgumentError.new("no port named #{name}")
74
100
  end
75
101
 
76
- class Host
77
- def initialize(name, &block)
78
- @name, @storage, @ldap = name, nil, nil
79
- instance_eval(&block)
80
- end
81
-
82
- def storage(name, &block)
83
- raise "one storage mechanism per host allowed" if @storage
84
- @storage = Storage.from_name(name, &block)
85
- @storage.ldap = @ldap
86
- end
87
-
88
- def ldap(host='localhost', port=636, &block)
89
- @ldap = Storage::Ldap.new(host, port, &block)
90
- @storage.ldap = @ldap if @storage
91
- end
92
-
93
- def to_hash
94
- raise "storage required for #{@name}" unless @storage
95
- {@name => @storage}
96
- end
97
- end
98
-
99
- class Port
100
- include Vines::Log
101
-
102
- attr_reader :config, :stream
103
-
104
- %w[host port].each do |name|
105
- define_method(name) do
106
- @settings[name.to_sym]
107
- end
108
- end
109
-
110
- def initialize(config, host, port, &block)
111
- @config, @settings = config, {}
112
- instance_eval(&block) if block
113
- defaults = {:host => host, :port => port,
114
- :max_resources_per_account => 5, :max_stanza_size => 128 * 1024}
115
- @settings = defaults.merge(@settings)
116
- end
117
-
118
- def max_stanza_size(max=nil)
119
- if max
120
- # rfc 6120 section 13.12
121
- @settings[:max_stanza_size] = [10000, max].max
122
- else
123
- @settings[:max_stanza_size]
124
- end
125
- end
126
-
127
- def start
128
- type = stream.name.split('::').last.downcase
129
- log.info("Accepting #{type} connections on #{host}:#{port}")
130
- EventMachine::start_server(host, port, stream, config)
131
- end
102
+ # Return true if the two JID's are allowed to send messages to each other.
103
+ # Both domains must have enabled cross_domain_messages in their config files.
104
+ def allowed?(to, from)
105
+ to, from = JID.new(to), JID.new(from)
106
+ return false if to.empty? || from.empty?
107
+ return true if to.domain == from.domain # same domain always allowed
108
+ return cross_domain?(to, from) if local_jid?(to, from) # both virtual hosted here
109
+ return check_components(to, from) if component?(to, from) # component to component
110
+ return check_component(to, from) if component?(to) # to component
111
+ return check_component(from, to) if component?(from) # from component
112
+ return cross_domain?(to) if local_jid?(to) # from is remote
113
+ return cross_domain?(from) if local_jid?(from) # to is remote
114
+ return false
132
115
  end
133
116
 
134
- class ClientPort < Port
135
- def initialize(config, host='0.0.0.0', port=5222, &block)
136
- @stream = Vines::Stream::Client
137
- super(config, host, port, &block)
138
- end
139
-
140
- def max_resources_per_account(max=nil)
141
- if max
142
- @settings[:max_resources_per_account] = max
143
- else
144
- @settings[:max_resources_per_account]
145
- end
146
- end
117
+ private
147
118
 
148
- def private_storage(enabled)
149
- @settings[:private_storage] = !!enabled
150
- end
151
-
152
- def private_storage?
153
- @settings[:private_storage]
154
- end
119
+ def check_components(to, from)
120
+ comp1, comp2 = strip_domain(to), strip_domain(from)
121
+ (comp1 == comp2) || cross_domain?(comp1, comp2)
155
122
  end
156
123
 
157
- class ServerPort < Port
158
- def initialize(config, host='0.0.0.0', port=5269, &block)
159
- @hosts, @stream = [], Vines::Stream::Server
160
- super(config, host, port, &block)
161
- end
162
-
163
- def hosts(*hosts)
164
- if hosts.any?
165
- @hosts << hosts
166
- @hosts.flatten!
167
- else
168
- @hosts
169
- end
170
- end
124
+ def check_component(component_jid, jid)
125
+ comp = strip_domain(component_jid)
126
+ return true if comp.domain == jid.domain
127
+ local_jid?(jid) ? cross_domain?(comp, jid) : cross_domain?(comp)
171
128
  end
172
129
 
173
- class HttpPort < Port
174
- def initialize(config, host='0.0.0.0', port=5280, &block)
175
- @stream = Vines::Stream::Http
176
- super(config, host, port, &block)
177
- defaults = {:root => File.expand_path('web'), :bind => '/xmpp'}
178
- @settings = defaults.merge(@settings)
179
- end
180
-
181
- def max_resources_per_account(max=nil)
182
- if max
183
- @settings[:max_resources_per_account] = max
184
- else
185
- @settings[:max_resources_per_account]
186
- end
187
- end
188
-
189
- def private_storage(enabled)
190
- @settings[:private_storage] = !!enabled
191
- end
192
-
193
- def private_storage?
194
- @settings[:private_storage]
195
- end
196
-
197
- def root(dir=nil)
198
- if dir
199
- @settings[:root] = File.expand_path(dir)
200
- else
201
- @settings[:root]
202
- end
203
- end
204
-
205
- def bind(url=nil)
206
- if url
207
- @settings[:bind] = url
208
- else
209
- @settings[:bind]
210
- end
211
- end
130
+ # Return the JID's domain with the first subdomain stripped off. For example,
131
+ # alice@tea.wonderland.lit returns wonderland.lit.
132
+ def strip_domain(jid)
133
+ domain = jid.domain.split('.').drop(1).join('.')
134
+ JID.new(domain)
212
135
  end
213
136
 
214
- class ComponentPort < Port
215
- def initialize(config, host='0.0.0.0', port=5347, &block)
216
- @components, @stream = {}, Vines::Stream::Component
217
- super(config, host, port, &block)
218
- end
219
-
220
- def components(options=nil)
221
- if options
222
- @components = options
223
- else
224
- @components
225
- end
226
- end
227
-
228
- def password(component)
229
- @components[component]
137
+ # Return true if all JID's are allowed to exchange cross domain messages.
138
+ def cross_domain?(*jids)
139
+ !jids.flatten.index do |jid|
140
+ !@vhosts[jid.domain].cross_domain_messages?
230
141
  end
231
142
  end
232
143
  end
@@ -0,0 +1,85 @@
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
+ def initialize(name, &block)
11
+ @name, @storage, @ldap = name.downcase, nil, nil
12
+ @cross_domain_messages = false
13
+ @private_storage = false
14
+ @components = {}
15
+ validate_domain(@name)
16
+ instance_eval(&block)
17
+ raise "storage required for #{@name}" unless @storage
18
+ end
19
+
20
+ def storage(name=nil, &block)
21
+ if name
22
+ raise "one storage mechanism per host allowed" if @storage
23
+ @storage = Storage.from_name(name, &block)
24
+ @storage.ldap = @ldap
25
+ else
26
+ @storage
27
+ end
28
+ end
29
+
30
+ def ldap(host='localhost', port=636, &block)
31
+ @ldap = Storage::Ldap.new(host, port, &block)
32
+ @storage.ldap = @ldap if @storage
33
+ end
34
+
35
+ def cross_domain_messages(enabled)
36
+ @cross_domain_messages = !!enabled
37
+ end
38
+
39
+ def cross_domain_messages?
40
+ @cross_domain_messages
41
+ end
42
+
43
+ def components(options=nil)
44
+ return @components unless options
45
+
46
+ names = options.keys.map {|domain| "#{domain}.#{@name}".downcase }
47
+ dupes = names.uniq.size != names.size || (@components.keys & names).any?
48
+ raise "duplicate component domains not allowed" if dupes
49
+
50
+ options.each do |domain, password|
51
+ raise 'component domain required' if (domain || '').to_s.strip.empty?
52
+ raise 'component password required' if (password || '').strip.empty?
53
+ name = "#{domain}.#{@name}".downcase
54
+ raise "components must be one level below their host: #{name}" if domain.to_s.include?('.')
55
+ validate_domain(name)
56
+ @components[name] = password
57
+ end
58
+ end
59
+
60
+ def component?(domain)
61
+ !!@components[domain]
62
+ end
63
+
64
+ def password(domain)
65
+ @components[domain]
66
+ end
67
+
68
+ def private_storage(enabled)
69
+ @private_storage = !!enabled
70
+ end
71
+
72
+ def private_storage?
73
+ @private_storage
74
+ end
75
+
76
+ private
77
+
78
+ # Prevent domains in config files that won't form valid JID's.
79
+ def validate_domain(name)
80
+ jid = JID.new(name)
81
+ raise "incorrect domain: #{name}" if jid.node || jid.resource
82
+ end
83
+ end
84
+ end
85
+ end