vinesmod 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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,26 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Cluster
5
+ # Create and cache a redis database connection.
6
+ class Connection
7
+ attr_accessor :host, :port, :database, :password
8
+
9
+ def initialize
10
+ @redis, @host, @port, @database, @password = nil, nil, nil, nil, nil
11
+ end
12
+
13
+ # Return a shared redis connection.
14
+ def connect
15
+ @redis ||= create
16
+ end
17
+
18
+ # Return a new redis connection.
19
+ def create
20
+ conn = EM::Hiredis::Client.new(@host, @port, @password, @database)
21
+ conn.connect
22
+ conn
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Cluster
5
+ # Broadcast messages to other cluster nodes via redis pubsub channels. All
6
+ # members subscribe to a channel for heartbeats, online, and offline
7
+ # messages from other nodes. This allows new nodes to be added to the
8
+ # cluster dynamically, without configuring all other nodes.
9
+ class Publisher
10
+ include Vines::Log
11
+
12
+ ALL, STANZA, USER = %w[cluster:nodes:all stanza user].map {|s| s.freeze }
13
+
14
+ def initialize(cluster)
15
+ @cluster = cluster
16
+ end
17
+
18
+ # Publish a :heartbeat, :online, or :offline message to the nodes:all
19
+ # broadcast channel.
20
+ def broadcast(type)
21
+ redis.publish(ALL, {
22
+ from: @cluster.id,
23
+ type: type,
24
+ time: Time.now.to_i
25
+ }.to_json)
26
+ end
27
+
28
+ # Send the stanza to the node hosting the user's session. The stanza is
29
+ # published to the channel to which the remote node is listening for
30
+ # messages.
31
+ def route(stanza, node)
32
+ log.debug { "Sent cluster stanza: %s -> %s\n%s\n" % [@cluster.id, node, stanza] }
33
+ redis.publish("cluster:nodes:#{node}", {
34
+ from: @cluster.id,
35
+ type: STANZA,
36
+ stanza: stanza.to_s
37
+ }.to_json)
38
+ end
39
+
40
+ # Notify the remote node that the user's roster has changed and it should
41
+ # reload the user from storage.
42
+ def update_user(jid, node)
43
+ redis.publish("cluster:nodes:#{node}", {
44
+ from: @cluster.id,
45
+ type: USER,
46
+ jid: jid.to_s
47
+ }.to_json)
48
+ end
49
+
50
+ def redis
51
+ @cluster.connection
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Cluster
5
+ # Manages the pubsub topic list and subscribers stored in redis. When a
6
+ # message is published to a topic, the receiving cluster node broadcasts
7
+ # the message to all subscribers at all other cluster nodes.
8
+ class PubSub
9
+ def initialize(cluster)
10
+ @cluster = cluster
11
+ end
12
+
13
+ # Create a pubsub topic (a.k.a. node), in the given domain, to which
14
+ # messages may be published. The domain argument will be one of the
15
+ # configured pubsub subdomains in conf/config.rb (e.g. games.wonderland.lit,
16
+ # topics.wonderland.lit, etc).
17
+ def add_node(domain, node)
18
+ redis.sadd("pubsub:#{domain}:nodes", node)
19
+ end
20
+
21
+ # Remove a pubsub topic so messages may no longer be broadcast to it.
22
+ def delete_node(domain, node)
23
+ redis.smembers("pubsub:#{domain}:subscribers_#{node}") do |subscribers|
24
+ redis.multi
25
+ subscribers.each do |jid|
26
+ redis.srem("pubsub:#{domain}:subscriptions_#{jid}", node)
27
+ end
28
+ redis.del("pubsub:#{domain}:subscribers_#{node}")
29
+ redis.srem("pubsub:#{domain}:nodes", node)
30
+ redis.exec
31
+ end
32
+ end
33
+
34
+ # Subscribe the JID to the pubsub topic so it will receive any messages
35
+ # published to it.
36
+ def subscribe(domain, node, jid)
37
+ jid = JID.new(jid)
38
+ redis.multi
39
+ redis.sadd("pubsub:#{domain}:subscribers_#{node}", jid.to_s)
40
+ redis.sadd("pubsub:#{domain}:subscriptions_#{jid}", node)
41
+ redis.exec
42
+ end
43
+
44
+ # Unsubscribe the JID from the pubsub topic, deregistering its interest
45
+ # in receiving any messages published to it.
46
+ def unsubscribe(domain, node, jid)
47
+ jid = JID.new(jid)
48
+ redis.multi
49
+ redis.srem("pubsub:#{domain}:subscribers_#{node}", jid.to_s)
50
+ redis.srem("pubsub:#{domain}:subscriptions_#{jid}", node)
51
+ redis.exec
52
+ redis.scard("pubsub:#{domain}:subscribers_#{node}") do |count|
53
+ delete_node(domain, node) if count == 0
54
+ end
55
+ end
56
+
57
+ # Unsubscribe the JID from all pubsub topics. This is useful when the
58
+ # JID's session ends by logout or disconnect.
59
+ def unsubscribe_all(domain, jid)
60
+ jid = JID.new(jid)
61
+ redis.smembers("pubsub:#{domain}:subscriptions_#{jid}") do |nodes|
62
+ nodes.each do |node|
63
+ unsubscribe(domain, node, jid)
64
+ end
65
+ end
66
+ end
67
+
68
+ # Return true if the pubsub topic exists and messages may be published to it.
69
+ def node?(domain, node)
70
+ @cluster.query(:sismember, "pubsub:#{domain}:nodes", node) == 1
71
+ end
72
+
73
+ # Return true if the JID is a registered subscriber to the pubsub topic and
74
+ # messages published to it should be routed to the JID.
75
+ def subscribed?(domain, node, jid)
76
+ jid = JID.new(jid)
77
+ @cluster.query(:sismember, "pubsub:#{domain}:subscribers_#{node}", jid.to_s) == 1
78
+ end
79
+
80
+ # Return a list of JIDs subscribed to the pubsub topic.
81
+ def subscribers(domain, node)
82
+ @cluster.query(:smembers, "pubsub:#{domain}:subscribers_#{node}")
83
+ end
84
+
85
+ private
86
+
87
+ def redis
88
+ @cluster.connection
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Cluster
5
+ # Manages the cluster node list and user session routing table stored in
6
+ # redis. All cluster nodes share this in-memory database to quickly discover
7
+ # the node hosting a particular user session. Once a session is located,
8
+ # stanzas can be routed to that node via the +Publisher+.
9
+ class Sessions
10
+ include Vines::Log
11
+
12
+ NODES = 'cluster:nodes'.freeze
13
+
14
+ def initialize(cluster)
15
+ @cluster, @nodes = cluster, {}
16
+ end
17
+
18
+ # Return the sessions for these JIDs. If a bare JID is used, all sessions
19
+ # for that user will be returned. If a full JID is used, the session for
20
+ # that single connected stream is returned.
21
+ def find(*jids)
22
+ jids.flatten.map do |jid|
23
+ jid = JID.new(jid)
24
+ jid.bare? ? user_sessions(jid) : user_session(jid)
25
+ end.compact.flatten
26
+ end
27
+
28
+ # Persist the user's session to the shared redis cache so that other cluster
29
+ # nodes can locate the node hosting this user's connection and route messages
30
+ # to them.
31
+ def save(jid, attrs)
32
+ jid = JID.new(jid)
33
+ session = {node: @cluster.id}.merge(attrs)
34
+ redis.multi
35
+ redis.hset("sessions:#{jid.bare}", jid.resource, session.to_json)
36
+ redis.sadd("cluster:nodes:#{@cluster.id}", jid.to_s)
37
+ redis.exec
38
+ end
39
+
40
+ # Remove this user from the cluster routing table so that no further stanzas
41
+ # may be routed to them. This must be called when the user's session is
42
+ # terminated, either by logout or stream disconnect.
43
+ def delete(jid)
44
+ jid = JID.new(jid)
45
+ redis.hget("sessions:#{jid.bare}", jid.resource) do |response|
46
+ if doc = JSON.parse(response) rescue nil
47
+ redis.multi
48
+ redis.hdel("sessions:#{jid.bare}", jid.resource)
49
+ redis.srem("cluster:nodes:#{doc['node']}", jid.to_s)
50
+ redis.exec
51
+ end
52
+ end
53
+ end
54
+
55
+ # Remove all user sessions from the routing table associated with the
56
+ # given node ID. Cluster nodes call this themselves during normal shutdown.
57
+ # However, if a node dies without being properly shutdown, the other nodes
58
+ # will cleanup its sessions when they detect the node is offline.
59
+ def delete_all(node)
60
+ @nodes.delete(node)
61
+ redis.smembers("cluster:nodes:#{node}") do |jids|
62
+ redis.multi
63
+ redis.del("cluster:nodes:#{node}")
64
+ redis.hdel(NODES, node)
65
+ jids.each do |jid|
66
+ jid = JID.new(jid)
67
+ redis.hdel("sessions:#{jid.bare}", jid.resource)
68
+ end
69
+ redis.exec
70
+ end
71
+ end
72
+
73
+ # Cluster nodes broadcast a heartbeat to other members every second. If we
74
+ # haven't heard from a node in five seconds, assume it's offline and cleanup
75
+ # its session cache for it. Nodes may die abrubtly, without a chance to clear
76
+ # their sessions, so other members cleanup for them.
77
+ def expire
78
+ redis.hset(NODES, @cluster.id, Time.now.to_i)
79
+ redis.hgetall(NODES) do |response|
80
+ now = Time.now
81
+ expired = Hash[*response].select do |node, active|
82
+ offset = @nodes[node] || 0
83
+ (now - offset) - Time.at(active.to_i) > 5
84
+ end.keys
85
+ expired.each {|node| delete_all(node) }
86
+ end
87
+ end
88
+
89
+ # Notify the session store that this node is still alive. The node
90
+ # broadcasts its current time, so all cluster members' clocks don't
91
+ # necessarily need to be in sync.
92
+ def poke(node, time)
93
+ offset = Time.now.to_i - time
94
+ @nodes[node] = offset
95
+ end
96
+
97
+ private
98
+
99
+ # Return all remote sessions for this user's bare JID.
100
+ def user_sessions(jid)
101
+ response = @cluster.query(:hgetall, "sessions:#{jid.bare}") || []
102
+ Hash[*response].map do |resource, json|
103
+ if session = JSON.parse(json) rescue nil
104
+ session['jid'] = JID.new(jid.node, jid.domain, resource).to_s
105
+ end
106
+ session
107
+ end.compact.reject {|session| session['node'] == @cluster.id }
108
+ end
109
+
110
+ # Return the remote session for this full JID or nil if not found.
111
+ def user_session(jid)
112
+ response = @cluster.query(:hget, "sessions:#{jid.bare}", jid.resource)
113
+ return unless response
114
+ session = JSON.parse(response) rescue nil
115
+ return if session.nil? || session['node'] == @cluster.id
116
+ session['jid'] = jid.to_s
117
+ session
118
+ end
119
+
120
+ def redis
121
+ @cluster.connection
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,108 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Cluster
5
+ # Subscribes to the redis nodes:all broadcast channel to listen for
6
+ # heartbeats from other cluster members. Also subscribes to a channel
7
+ # exclusively for this particular node, listening for stanzas routed to us
8
+ # from other nodes.
9
+ class Subscriber
10
+ include Vines::Log
11
+
12
+ ALL, FROM, HEARTBEAT, OFFLINE, ONLINE, STANZA, TIME, TO, TYPE, USER =
13
+ %w[cluster:nodes:all from heartbeat offline online stanza time to type user].map {|s| s.freeze }
14
+
15
+ def initialize(cluster)
16
+ @cluster = cluster
17
+ @channel = "cluster:nodes:#{@cluster.id}"
18
+ @messages = EM::Queue.new
19
+ process_messages
20
+ end
21
+
22
+ # Create a new redis connection and subscribe to the nodes:all broadcast
23
+ # channel as well as the channel for this cluster node. Redis connections
24
+ # in subscribe mode cannot be used for other key/value operations.
25
+ def subscribe
26
+ conn = @cluster.connect
27
+ conn.subscribe(ALL)
28
+ conn.subscribe(@channel)
29
+ conn.on(:message) do |channel, message|
30
+ @messages.push([channel, message])
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Recursively process incoming messages from the queue, guaranteeing they
37
+ # are processed in the order they are received.
38
+ def process_messages
39
+ @messages.pop do |channel, message|
40
+ Fiber.new do
41
+ on_message(channel, message)
42
+ process_messages
43
+ end.resume
44
+ end
45
+ end
46
+
47
+ # Process messages as they arrive on the pubsub channels to which we're
48
+ # subscribed.
49
+ def on_message(channel, message)
50
+ doc = JSON.parse(message)
51
+ case channel
52
+ when ALL then to_all(doc)
53
+ when @channel then to_node(doc)
54
+ end
55
+ rescue => e
56
+ log.error("Cluster subscription message failed: #{e}")
57
+ end
58
+
59
+ # Process a message sent to the nodes:all broadcast channel. In the case
60
+ # of node heartbeats, we update the last time we heard from this node so
61
+ # we can cleanup its session if it goes offline.
62
+ def to_all(message)
63
+ case message[TYPE]
64
+ when ONLINE, HEARTBEAT
65
+ @cluster.poke(message[FROM], message[TIME])
66
+ when OFFLINE
67
+ @cluster.delete_sessions(message[FROM])
68
+ end
69
+ end
70
+
71
+ # Process a message published to this node's channel. Messages sent to
72
+ # this channel are stanzas that need to be routed to connections attached
73
+ # to this node.
74
+ def to_node(message)
75
+ case message[TYPE]
76
+ when STANZA then route_stanza(message)
77
+ when USER then update_user(message)
78
+ end
79
+ end
80
+
81
+ # Send the stanza, from a remote cluster node, to locally connected
82
+ # streams for the destination user.
83
+ def route_stanza(message)
84
+ node = Nokogiri::XML(message[STANZA]).root rescue nil
85
+ return unless node
86
+ log.debug { "Received cluster stanza: %s -> %s\n%s\n" % [message[FROM], @cluster.id, node] }
87
+ if node[TO]
88
+ @cluster.connected_resources(node[TO]).each do |recipient|
89
+ recipient.write(node)
90
+ end
91
+ else
92
+ log.warn("Cluster stanza missing address:\n#{node}")
93
+ end
94
+ end
95
+
96
+ # Update the roster information, that's cached in locally connected
97
+ # streams, for this user.
98
+ def update_user(message)
99
+ jid = JID.new(message['jid']).bare
100
+ if user = @cluster.storage(jid.domain).find_user(jid)
101
+ @cluster.connected_resources(jid).each do |stream|
102
+ stream.user.update_from(user)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Bcrypt
6
+ def run(opts)
7
+ raise 'vines bcrypt <clear text>' unless opts[:args].size == 1
8
+ puts BCrypt::Password.create(opts[:args].first)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Command
5
+ class Cert
6
+ def run(opts)
7
+ raise 'vines cert <domain>' unless opts[:args].size == 1
8
+ require opts[:config]
9
+ create_cert(opts[:args].first, Config.instance.certs)
10
+ end
11
+
12
+ def create_cert(domain, dir)
13
+ domain = domain.downcase
14
+ key = OpenSSL::PKey::RSA.generate(2048)
15
+ ca = OpenSSL::X509::Name.parse("/C=US/ST=Colorado/L=Denver/O=Vines XMPP Server/CN=#{domain}")
16
+ cert = OpenSSL::X509::Certificate.new
17
+ cert.version = 2
18
+ cert.subject = ca
19
+ cert.issuer = ca
20
+ cert.serial = Time.now.to_i
21
+ cert.public_key = key.public_key
22
+ cert.not_before = Time.now - (24 * 60 * 60)
23
+ cert.not_after = Time.now + (365 * 24 * 60 * 60)
24
+
25
+ factory = OpenSSL::X509::ExtensionFactory.new
26
+ factory.subject_certificate = cert
27
+ factory.issuer_certificate = cert
28
+ cert.extensions = [
29
+ %w[basicConstraints CA:TRUE],
30
+ %w[subjectKeyIdentifier hash],
31
+ %w[subjectAltName] << [domain, hostname].map {|n| "DNS:#{n}" }.join(',')
32
+ ].map {|k, v| factory.create_ext(k, v) }
33
+
34
+ cert.sign(key, OpenSSL::Digest::SHA1.new)
35
+
36
+ {'key' => key, 'crt' => cert}.each_pair do |ext, o|
37
+ name = File.join(dir, "#{domain}.#{ext}")
38
+ File.open(name, 'w:utf-8') {|f| f.write(o.to_pem) }
39
+ File.chmod(0600, name) if ext == 'key'
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def hostname
46
+ Socket.gethostbyname(Socket.gethostname).first.downcase
47
+ end
48
+ end
49
+ end
50
+ end