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
@@ -23,7 +23,7 @@ module Vines
23
23
  end
24
24
 
25
25
  def find_user(jid)
26
- jid = JID.new(jid || '').bare.to_s
26
+ jid = JID.new(jid).bare.to_s
27
27
  if jid.empty? then yield; return end
28
28
  get("user:#{jid}") do |doc|
29
29
  user = if doc && doc['type'] == 'User'
@@ -61,7 +61,7 @@ module Vines
61
61
  fiber :save_user
62
62
 
63
63
  def find_vcard(jid)
64
- jid = JID.new(jid || '').bare.to_s
64
+ jid = JID.new(jid).bare.to_s
65
65
  if jid.empty? then yield; return end
66
66
  get("vcard:#{jid}") do |doc|
67
67
  card = if doc && doc['type'] == 'Vcard'
@@ -85,7 +85,7 @@ module Vines
85
85
  fiber :save_vcard
86
86
 
87
87
  def find_fragment(jid, node)
88
- jid = JID.new(jid || '').bare.to_s
88
+ jid = JID.new(jid).bare.to_s
89
89
  if jid.empty? then yield; return end
90
90
  get(fragment_id(jid, node)) do |doc|
91
91
  fragment = if doc && doc['type'] == 'Fragment'
@@ -9,8 +9,8 @@ module Vines
9
9
  # information.
10
10
  class Ldap
11
11
  @@required = [:host, :port]
12
- %w[tls dn password basedn object_class user_attr name_attr].each do |name|
13
- @@required << name.to_sym
12
+ %w[tls dn password basedn object_class user_attr name_attr groupdn].each do |name|
13
+ @@required << name.to_sym unless name == 'groupdn'
14
14
  define_method name do |*args|
15
15
  @config[name.to_sym] = args.first
16
16
  end
@@ -28,13 +28,10 @@ module Vines
28
28
  def authenticate(username, password)
29
29
  return if [username, password].any? {|arg| (arg || '').strip.empty? }
30
30
 
31
- clas = Net::LDAP::Filter.eq('objectClass', @config[:object_class])
32
- uid = Net::LDAP::Filter.eq(@config[:user_attr], username)
33
- filter = clas & uid
34
- attrs = [@config[:name_attr], 'mail']
35
-
36
31
  ldap = connect(@config[:dn], @config[:password])
37
- entries = ldap.search(:attributes => attrs, :filter => filter)
32
+ entries = ldap.search(
33
+ :attributes => [@config[:name_attr], 'mail'],
34
+ :filter => filter(username))
38
35
  return unless entries && entries.size == 1
39
36
 
40
37
  user = if connect(entries.first.dn, password).bind
@@ -44,6 +41,20 @@ module Vines
44
41
  user
45
42
  end
46
43
 
44
+ # Return an LDAP search filter for a user optionally belonging to the
45
+ # group defined by the groupdn config attribute.
46
+ def filter(username)
47
+ clas = Net::LDAP::Filter.eq('objectClass', @config[:object_class])
48
+ uid = Net::LDAP::Filter.eq(@config[:user_attr], username)
49
+ filter = clas & uid
50
+ if group = @config[:groupdn]
51
+ memberOf = Net::LDAP::Filter.eq('memberOf', group)
52
+ isMemberOf = Net::LDAP::Filter.eq('isMemberOf', group)
53
+ filter = filter & (memberOf | isMemberOf)
54
+ end
55
+ filter
56
+ end
57
+
47
58
  private
48
59
 
49
60
  def connect(dn, password)
@@ -20,8 +20,8 @@ module Vines
20
20
  end
21
21
 
22
22
  def find_user(jid)
23
- jid = JID.new(jid || '').bare.to_s
24
- file = File.join(@dir, "#{jid}.user") unless jid.empty?
23
+ jid = JID.new(jid).bare.to_s
24
+ file = absolute_path("#{jid}.user") unless jid.empty?
25
25
  record = YAML.load_file(file) rescue nil
26
26
  return User.new(:jid => jid).tap do |user|
27
27
  user.name, user.password = record.values_at('name', 'password')
@@ -41,44 +41,55 @@ module Vines
41
41
  user.roster.each do |contact|
42
42
  record['roster'][contact.jid.bare.to_s] = contact.to_h
43
43
  end
44
- file = File.join(@dir, "#{user.jid.bare.to_s}.user")
45
- File.open(file, 'w') do |f|
44
+ save("#{user.jid.bare.to_s}.user") do |f|
46
45
  YAML.dump(record, f)
47
46
  end
48
47
  end
49
48
 
50
49
  def find_vcard(jid)
51
- jid = JID.new(jid || '').bare.to_s
50
+ jid = JID.new(jid).bare.to_s
52
51
  return if jid.empty?
53
- file = File.join(@dir, "#{jid}.vcard")
52
+ file = absolute_path("#{jid}.vcard")
54
53
  Nokogiri::XML(File.read(file)).root rescue nil
55
54
  end
56
55
 
57
56
  def save_vcard(jid, card)
58
57
  jid = JID.new(jid).bare.to_s
59
- file = File.join(@dir, "#{jid}.vcard")
60
- File.open(file, 'w') do |f|
58
+ return if jid.empty?
59
+ save("#{jid}.vcard") do |f|
61
60
  f.write(card.to_xml)
62
61
  end
63
62
  end
64
63
 
65
64
  def find_fragment(jid, node)
66
- jid = JID.new(jid || '').bare.to_s
65
+ jid = JID.new(jid).bare.to_s
67
66
  return if jid.empty?
68
- file = File.join(@dir, fragment_id(jid, node))
67
+ file = absolute_path(fragment_id(jid, node))
69
68
  Nokogiri::XML(File.read(file)).root rescue nil
70
69
  end
71
70
 
72
71
  def save_fragment(jid, node)
73
72
  jid = JID.new(jid).bare.to_s
74
- file = File.join(@dir, fragment_id(jid, node))
75
- File.open(file, 'w') do |f|
73
+ return if jid.empty?
74
+ save(fragment_id(jid, node)) do |f|
76
75
  f.write(node.to_xml)
77
76
  end
78
77
  end
79
78
 
80
79
  private
81
80
 
81
+ def absolute_path(file)
82
+ File.expand_path(file, @dir).tap do |absolute|
83
+ raise 'path traversal' unless File.dirname(absolute) == @dir
84
+ end
85
+ end
86
+
87
+ def save(file)
88
+ file = absolute_path(file)
89
+ File.open(file, 'w') {|f| yield f }
90
+ File.chmod(0600, file)
91
+ end
92
+
82
93
  def fragment_id(jid, node)
83
94
  id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
84
95
  "#{jid}-#{id}.fragment"
@@ -22,7 +22,7 @@ module Vines
22
22
  end
23
23
 
24
24
  def find_user(jid)
25
- jid = JID.new(jid || '').bare.to_s
25
+ jid = JID.new(jid).bare.to_s
26
26
  if jid.empty? then yield; return end
27
27
  find_roster(jid) do |contacts|
28
28
  redis.get("user:#{jid}") do |response|
@@ -53,7 +53,7 @@ module Vines
53
53
  fiber :save_user
54
54
 
55
55
  def find_vcard(jid)
56
- jid = JID.new(jid || '').bare.to_s
56
+ jid = JID.new(jid).bare.to_s
57
57
  if jid.empty? then yield; return end
58
58
  redis.get("vcard:#{jid}") do |response|
59
59
  card = if response
@@ -75,7 +75,7 @@ module Vines
75
75
  fiber :save_vcard
76
76
 
77
77
  def find_fragment(jid, node)
78
- jid = JID.new(jid || '').bare.to_s
78
+ jid = JID.new(jid).bare.to_s
79
79
  if jid.empty? then yield; return end
80
80
  redis.hget("fragments:#{jid}", fragment_id(node)) do |response|
81
81
  fragment = if response
@@ -40,7 +40,7 @@ module Vines
40
40
  def find_user(jid)
41
41
  ActiveRecord::Base.clear_reloadable_connections!
42
42
 
43
- jid = JID.new(jid || '').bare.to_s
43
+ jid = JID.new(jid).bare.to_s
44
44
  return if jid.empty?
45
45
  xuser = user_by_jid(jid)
46
46
  return Vines::User.new(:jid => jid).tap do |user|
@@ -99,7 +99,7 @@ module Vines
99
99
  def find_vcard(jid)
100
100
  ActiveRecord::Base.clear_reloadable_connections!
101
101
 
102
- jid = JID.new(jid || '').bare.to_s
102
+ jid = JID.new(jid).bare.to_s
103
103
  return if jid.empty?
104
104
  if xuser = user_by_jid(jid)
105
105
  Nokogiri::XML(xuser.vcard).root rescue nil
@@ -121,7 +121,7 @@ module Vines
121
121
  def find_fragment(jid, node)
122
122
  ActiveRecord::Base.clear_reloadable_connections!
123
123
 
124
- jid = JID.new(jid || '').bare.to_s
124
+ jid = JID.new(jid).bare.to_s
125
125
  return if jid.empty?
126
126
  if fragment = fragment_by_jid(jid, node)
127
127
  Nokogiri::XML(fragment.xml).root rescue nil
@@ -151,7 +151,7 @@ module Vines
151
151
 
152
152
  ActiveRecord::Schema.define do
153
153
  create_table :users, :force => args[:force] do |t|
154
- t.string :jid, :limit => 1000, :null => false
154
+ t.string :jid, :limit => 2048, :null => false
155
155
  t.string :name, :limit => 1000, :null => true
156
156
  t.string :password, :limit => 1000, :null => true
157
157
  t.text :vcard, :null => true
@@ -160,7 +160,7 @@ module Vines
160
160
 
161
161
  create_table :contacts, :force => args[:force] do |t|
162
162
  t.integer :user_id, :null => false
163
- t.string :jid, :limit => 1000, :null => false
163
+ t.string :jid, :limit => 2048, :null => false
164
164
  t.string :name, :limit => 1000, :null => true
165
165
  t.string :ask, :limit => 1000, :null => true
166
166
  t.string :subscription, :limit => 1000, :null => false
data/lib/vines/stream.rb CHANGED
@@ -10,26 +10,36 @@ module Vines
10
10
  ERROR = 'error'.freeze
11
11
  PAD = 20
12
12
 
13
- attr_reader :domain
13
+ attr_reader :config, :domain
14
14
  attr_accessor :user
15
15
 
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
16
20
  def post_init
17
21
  router << self
18
22
  @remote_addr, @local_addr = addresses
19
23
  @user, @closed, @stanza_size = nil, false, 0
20
24
  @bucket = TokenBucket.new(100, 10)
21
25
  @store = Store.new
22
-
23
26
  @nodes = EM::Queue.new
24
27
  process_node_queue
28
+ create_parser
29
+ log.info { "%s %21s -> %s" %
30
+ ['Stream connected:'.ljust(PAD), @remote_addr, @local_addr] }
31
+ end
25
32
 
33
+ # Initialize a new XML parser for this connection. This is called when the
34
+ # stream is first connected as well as for stream restarts during
35
+ # negotiation. Subclasses can override this method to provide a different
36
+ # type of parser (e.g. HTTP).
37
+ def create_parser
26
38
  @parser = Parser.new.tap do |p|
27
39
  p.stream_open {|node| @nodes.push(node) }
28
40
  p.stream_close { close_connection }
29
41
  p.stanza {|node| @nodes.push(node) }
30
42
  end
31
- log.info { "%s %21s -> %s" %
32
- ['Stream connected:'.ljust(PAD), @remote_addr, @local_addr] }
33
43
  end
34
44
 
35
45
  def close_connection(after_writing=false)
@@ -48,21 +58,40 @@ module Vines
48
58
  end
49
59
  end
50
60
 
61
+ # Reset the connection's XML parser when a new <stream:stream> header
62
+ # is received.
63
+ def reset
64
+ create_parser
65
+ end
66
+
51
67
  # Returns the storage system for the domain. If no domain is given,
52
68
  # the stream's storage mechanism is returned.
53
69
  def storage(domain=nil)
54
- @config.vhosts[domain || self.domain]
70
+ host = @config.vhosts[domain || self.domain]
71
+ host.storage if host
55
72
  end
56
73
 
57
74
  # Reload the user's information into their active connections. Call this
58
75
  # after storage.save_user() to sync the new user state with their other
59
76
  # connections.
60
77
  def update_user_streams(user)
61
- router.connected_resources(user.jid.bare).each do |stream|
78
+ connected_resources(user.jid.bare).each do |stream|
62
79
  stream.user.update_from(user)
63
80
  end
64
81
  end
65
82
 
83
+ def connected_resources(jid)
84
+ router.connected_resources(jid, user.jid)
85
+ end
86
+
87
+ def available_resources(*jid)
88
+ router.available_resources(*jid, user.jid)
89
+ end
90
+
91
+ def interested_resources(*jid)
92
+ router.interested_resources(*jid, user.jid)
93
+ end
94
+
66
95
  def ssl_verify_peer(pem)
67
96
  # EM is supposed to close the connection when this returns false,
68
97
  # but it only does that for inbound connections, not when we
@@ -206,5 +235,10 @@ module Vines
206
235
  def tls_files
207
236
  %w[crt key].map {|ext| File.join(VINES_ROOT, 'conf', 'certs', "#{domain}.#{ext}") }
208
237
  end
238
+
239
+ def valid_address?(jid)
240
+ jid = JID.new(jid) rescue nil
241
+ jid && !jid.empty?
242
+ end
209
243
  end
210
244
  end
@@ -6,10 +6,8 @@ module Vines
6
6
  # Implements the XMPP protocol for client-to-server (c2s) streams. This
7
7
  # serves connected streams using the jabber:client namespace.
8
8
  class Client < Stream
9
- attr_reader :config
10
-
11
9
  def initialize(config)
12
- @config = config
10
+ super
13
11
  @session = Client::Session.new(self)
14
12
  end
15
13
 
@@ -24,9 +22,9 @@ module Vines
24
22
  end
25
23
  end
26
24
 
27
- %w[max_stanza_size max_resources_per_account private_storage?].each do |name|
25
+ %w[max_stanza_size max_resources_per_account].each do |name|
28
26
  define_method name do |*args|
29
- @config[:client].send(name, *args)
27
+ config[:client].send(name, *args)
30
28
  end
31
29
  end
32
30
 
@@ -46,7 +44,8 @@ module Vines
46
44
  @session.domain = to
47
45
  send_stream_header(from)
48
46
  raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
49
- raise StreamErrors::HostUnknown unless @config.vhost?(@session.domain)
47
+ raise StreamErrors::ImproperAddressing unless valid_address?(@session.domain)
48
+ raise StreamErrors::HostUnknown unless config.vhost?(@session.domain)
50
49
  raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:client]
51
50
  raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
52
51
  end
@@ -9,7 +9,7 @@ module Vines
9
9
  SUCCESS = %Q{<success xmlns="#{NS}"/>}.freeze
10
10
  MAX_AUTH_ATTEMPTS = 3
11
11
  AUTH_MECHANISMS = {'PLAIN' => :plain_auth, 'EXTERNAL' => :external_auth}.freeze
12
-
12
+
13
13
  def initialize(stream, success=BindRestart)
14
14
  super
15
15
  @attempts, @outstanding = 0, false
@@ -49,7 +49,7 @@ module Vines
49
49
 
50
50
  # Authenticate c2s streams using a username and password. Call the
51
51
  # authentication module in a separate thread to avoid blocking stanza
52
- # processing for other users.
52
+ # processing for other users.
53
53
  def plain_auth(stanza)
54
54
  jid, node, password = Base64.decode64(stanza.text).split("\000")
55
55
  jid = [node, stream.domain].join('@') if jid.nil? || jid.empty?
@@ -77,6 +77,7 @@ module Vines
77
77
 
78
78
  def send_auth_success
79
79
  stream.write(SUCCESS)
80
+ stream.reset
80
81
  advance
81
82
  end
82
83
 
@@ -51,12 +51,12 @@ module Vines
51
51
  end
52
52
 
53
53
  def resource_limit_reached?
54
- used = stream.router.connected_resources(stream.user.jid.bare).size
54
+ used = stream.connected_resources(stream.user.jid.bare).size
55
55
  used >= stream.max_resources_per_account
56
56
  end
57
57
 
58
58
  def resource_used?(resource)
59
- stream.router.available_resources(stream.user.jid).any? do |c|
59
+ stream.available_resources(stream.user.jid).any? do |c|
60
60
  c.user.jid.resource == resource
61
61
  end
62
62
  end
@@ -14,11 +14,10 @@ module Vines
14
14
  doc = Document.new
15
15
  features = doc.create_element('stream:features') do |el|
16
16
  el << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
17
- el << doc.create_element('session', 'xmlns' => NAMESPACES[:session])
18
17
  end
19
18
  stream.write(features)
20
19
  advance
21
- end
20
+ end
22
21
  end
23
22
  end
24
23
  end
@@ -7,6 +7,8 @@ module Vines
7
7
  def node(node)
8
8
  stanza = to_stanza(node)
9
9
  raise StreamErrors::UnsupportedStanzaType unless stanza
10
+ stanza.validate_to
11
+ stanza.validate_from
10
12
  stanza.process
11
13
  end
12
14
  end
@@ -15,6 +15,7 @@ module Vines
15
15
 
16
16
  def initialize(stream)
17
17
  @id = Kit.uuid
18
+ @config = stream.config
18
19
  @state = Client::Start.new(stream)
19
20
  @available = false
20
21
  @domain = nil
@@ -91,14 +92,22 @@ module Vines
91
92
  # has successfully subscribed.
92
93
  def available_subscribed_to_resources
93
94
  subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
94
- router.available_resources(subscribed)
95
+ router.available_resources(subscribed, @user.jid)
95
96
  end
96
97
 
97
98
  # Returns streams for available resources that are subscribed
98
99
  # to this user's presence updates.
99
100
  def available_subscribers
100
101
  subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
101
- router.available_resources(subscribed)
102
+ router.available_resources(subscribed, @user.jid)
103
+ end
104
+
105
+ # Returns contacts hosted at remote servers to which this user has
106
+ # successfully subscribed.
107
+ def remote_subscribed_to_contacts
108
+ @user.subscribed_to_contacts.reject do |c|
109
+ @config.local_jid?(c.jid)
110
+ end
102
111
  end
103
112
 
104
113
  # Returns contacts hosted at remote servers that are subscribed
@@ -106,7 +115,7 @@ module Vines
106
115
  def remote_subscribers(to=nil)
107
116
  jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
108
117
  @user.subscribed_from_contacts.reject do |c|
109
- router.local_jid?(c.jid) || (jid && c.jid.bare != jid)
118
+ @config.local_jid?(c.jid) || (jid && c.jid.bare != jid)
110
119
  end
111
120
  end
112
121
 
@@ -121,7 +130,7 @@ module Vines
121
130
  'type' => 'unavailable')
122
131
 
123
132
  broadcast(el, available_subscribers)
124
- broadcast(el, router.available_resources(@user.jid))
133
+ broadcast(el, router.available_resources(@user.jid, @user.jid))
125
134
 
126
135
  remote_subscribers.each do |contact|
127
136
  node = el.clone