vines 0.2.1 → 0.3.0

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 (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