vines-agent 0.1.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.
- data/LICENSE +19 -0
- data/README +35 -0
- data/Rakefile +58 -0
- data/bin/vines-agent +94 -0
- data/conf/certs/README +15 -0
- data/conf/certs/ca-bundle.crt +3721 -0
- data/conf/config.rb +16 -0
- data/lib/vines/agent.rb +30 -0
- data/lib/vines/agent/agent.rb +24 -0
- data/lib/vines/agent/command/init.rb +55 -0
- data/lib/vines/agent/command/restart.rb +14 -0
- data/lib/vines/agent/command/start.rb +30 -0
- data/lib/vines/agent/command/stop.rb +20 -0
- data/lib/vines/agent/config.rb +106 -0
- data/lib/vines/agent/connection.rb +274 -0
- data/lib/vines/agent/shell.rb +166 -0
- data/lib/vines/agent/version.rb +7 -0
- data/test/config_test.rb +115 -0
- data/test/rake_test_loader.rb +17 -0
- metadata +155 -0
data/conf/config.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# This is the Vines agent configuration file. Restart the agent with
|
4
|
+
# 'vines-agent restart' after updating this file.
|
5
|
+
|
6
|
+
Vines::Agent::Config.configure do
|
7
|
+
# Set the logging level to debug, info, warn, error, or fatal. The debug
|
8
|
+
# level logs all XML sent and received by the agent.
|
9
|
+
log :info
|
10
|
+
|
11
|
+
domain 'wonderland.lit' do
|
12
|
+
upstream 'localhost', 5222
|
13
|
+
password 'secr3t'
|
14
|
+
download 'data'
|
15
|
+
end
|
16
|
+
end
|
data/lib/vines/agent.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
%w[
|
4
|
+
logger
|
5
|
+
blather
|
6
|
+
digest
|
7
|
+
etc
|
8
|
+
fiber
|
9
|
+
fileutils
|
10
|
+
json
|
11
|
+
ohai
|
12
|
+
session
|
13
|
+
slave
|
14
|
+
|
15
|
+
blather/client/client
|
16
|
+
|
17
|
+
vines/log
|
18
|
+
vines/daemon
|
19
|
+
|
20
|
+
vines/agent/version
|
21
|
+
vines/agent/agent
|
22
|
+
vines/agent/config
|
23
|
+
vines/agent/connection
|
24
|
+
vines/agent/shell
|
25
|
+
|
26
|
+
vines/agent/command/init
|
27
|
+
vines/agent/command/restart
|
28
|
+
vines/agent/command/start
|
29
|
+
vines/agent/command/stop
|
30
|
+
].each {|f| require f }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
# The main starting point for the Vines Agent process. Starts the
|
6
|
+
# EventMachine processing loop and registers the agent with the configured
|
7
|
+
# servers.
|
8
|
+
class Agent
|
9
|
+
include Vines::Log
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
log.info('Vines agent started')
|
17
|
+
at_exit { log.fatal('Vines agent stopped') }
|
18
|
+
EM.run do
|
19
|
+
@config.start
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
module Command
|
6
|
+
class Init
|
7
|
+
def run(opts)
|
8
|
+
raise 'vines-agent init <domain>' unless opts[:args].size == 1
|
9
|
+
domain = opts[:args].first.downcase
|
10
|
+
dir = File.expand_path(domain)
|
11
|
+
raise "Directory already initialized: #{domain}" if File.exists?(dir)
|
12
|
+
Dir.mkdir(dir)
|
13
|
+
|
14
|
+
FileUtils.cp_r(File.expand_path("../../../../../conf", __FILE__), dir)
|
15
|
+
|
16
|
+
data, log, pid = %w[data log pid].map do |sub|
|
17
|
+
File.join(dir, sub).tap {|subdir| Dir.mkdir(subdir) }
|
18
|
+
end
|
19
|
+
|
20
|
+
update_config(domain, File.expand_path('conf/config.rb', dir))
|
21
|
+
fix_perms(dir)
|
22
|
+
|
23
|
+
puts "Initialized agent directory: #{domain}"
|
24
|
+
puts "Run 'cd #{domain} && vines-agent start' to begin"
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# The config.rb file contains the agent's password so restrict access
|
30
|
+
# to just the agent user.
|
31
|
+
def fix_perms(dir)
|
32
|
+
File.chmod(0600, File.expand_path('conf/config.rb', dir))
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_config(domain, config)
|
36
|
+
text = File.read(config)
|
37
|
+
File.open(config, 'w') do |f|
|
38
|
+
replaced = text
|
39
|
+
.gsub('wonderland.lit', domain.downcase)
|
40
|
+
.gsub('secr3t', password)
|
41
|
+
f.write(replaced)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a large, random password with which to authenticate the
|
46
|
+
# agent bot's JID.
|
47
|
+
def password
|
48
|
+
hash = Digest::SHA512.new
|
49
|
+
1024.times { hash << rand.to_s }
|
50
|
+
hash.hexdigest
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
module Command
|
6
|
+
class Start
|
7
|
+
def run(opts)
|
8
|
+
raise 'vines-agent [--pid FILE] start' unless opts[:args].size == 0
|
9
|
+
require opts[:config]
|
10
|
+
agent = Vines::Agent::Agent.new(Config.instance)
|
11
|
+
daemonize(opts) if opts[:daemonize]
|
12
|
+
agent.start
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def daemonize(opts)
|
18
|
+
daemon = Vines::Daemon.new(:pid => opts[:pid], :stdout => opts[:log],
|
19
|
+
:stderr => opts[:log])
|
20
|
+
if daemon.running?
|
21
|
+
raise "The vines agent is running as process #{daemon.pid}"
|
22
|
+
else
|
23
|
+
puts "The vines agent has started"
|
24
|
+
daemon.start
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
module Command
|
6
|
+
class Stop
|
7
|
+
def run(opts)
|
8
|
+
raise 'vines-agent [--pid FILE] stop' unless opts[:args].size == 0
|
9
|
+
daemon = Vines::Daemon.new(:pid => opts[:pid])
|
10
|
+
if daemon.running?
|
11
|
+
daemon.stop
|
12
|
+
puts 'The vines agent has been shutdown'
|
13
|
+
else
|
14
|
+
puts 'The vines agent is not running'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
|
6
|
+
# A Config object is passed to the xmpp connections to give them access
|
7
|
+
# to configuration information like server host names, passwords, etc.
|
8
|
+
# This class provides the DSL methods used in the conf/config.rb file.
|
9
|
+
class Config
|
10
|
+
LOG_LEVELS = %w[debug info warn error fatal].freeze
|
11
|
+
|
12
|
+
@@instance = nil
|
13
|
+
def self.configure(&block)
|
14
|
+
@@instance = self.new(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.instance
|
18
|
+
@@instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(&block)
|
22
|
+
@domain = nil
|
23
|
+
instance_eval(&block)
|
24
|
+
raise "must define a domain" unless @domain
|
25
|
+
end
|
26
|
+
|
27
|
+
def log(level)
|
28
|
+
const = Logger.const_get(level.to_s.upcase) rescue nil
|
29
|
+
unless LOG_LEVELS.include?(level.to_s) && const
|
30
|
+
raise "log level must be one of: #{LOG_LEVELS.join(', ')}"
|
31
|
+
end
|
32
|
+
log = Class.new.extend(Vines::Log).log
|
33
|
+
log.progname = 'vines-agent'
|
34
|
+
log.level = const
|
35
|
+
end
|
36
|
+
|
37
|
+
def domain(name, &block)
|
38
|
+
raise 'multiple domains not allowed' if @domain
|
39
|
+
@domain = Domain.new(name, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def start
|
43
|
+
@domain.start
|
44
|
+
end
|
45
|
+
|
46
|
+
class Domain
|
47
|
+
def initialize(name, &block)
|
48
|
+
@name, @password, @upstream = name, nil, []
|
49
|
+
instance_eval(&block) if block
|
50
|
+
validate_domain(@name)
|
51
|
+
raise "password required" unless @password && !@password.strip.empty?
|
52
|
+
raise "duplicate upstream connections not allowed" if @upstream.uniq!
|
53
|
+
unless @download
|
54
|
+
@download = File.expand_path('data')
|
55
|
+
FileUtils.mkdir_p(@download)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def password(password)
|
60
|
+
@password = password
|
61
|
+
end
|
62
|
+
|
63
|
+
def download(dir)
|
64
|
+
@download = File.expand_path(dir)
|
65
|
+
begin
|
66
|
+
FileUtils.mkdir_p(@download)
|
67
|
+
rescue
|
68
|
+
raise "can't create #{@download}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def upstream(host, port)
|
73
|
+
raise 'host and port required for upstream connections' unless host && port
|
74
|
+
@upstream << {host: host, port: port}
|
75
|
+
end
|
76
|
+
|
77
|
+
def start
|
78
|
+
base = {
|
79
|
+
password: @password,
|
80
|
+
domain: @name,
|
81
|
+
download: @download
|
82
|
+
}
|
83
|
+
options = @upstream.map do |info|
|
84
|
+
base.clone.tap do |opts|
|
85
|
+
opts[:host] = info[:host]
|
86
|
+
opts[:port] = info[:port]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
# no upstream so use DNS SRV discovery for host and port
|
90
|
+
options << base if options.empty?
|
91
|
+
options.each do |args|
|
92
|
+
Vines::Agent::Connection.new(args).start
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# Prevent domains in config files that won't form valid JID's.
|
99
|
+
def validate_domain(name)
|
100
|
+
jid = Blather::JID.new(name)
|
101
|
+
raise "incorrect domain: #{name}" if jid.node || jid.resource
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Agent
|
5
|
+
|
6
|
+
# Connects the agent process to the chat server and provides service
|
7
|
+
# discovery and command execution abilities. Users are authorized against
|
8
|
+
# an access control list before being allowed to run commands.
|
9
|
+
class Connection
|
10
|
+
include Vines::Log
|
11
|
+
|
12
|
+
NS = 'http://getvines.com/protocol'.freeze
|
13
|
+
SYSTEMS = 'http://getvines.com/protocol/systems'.freeze
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
domain, password, host, port, download =
|
17
|
+
*options.values_at(:domain, :password, :host, :port, :download)
|
18
|
+
|
19
|
+
@permissions, @services, @sessions, @component = {}, {}, {}, nil
|
20
|
+
@ready = false
|
21
|
+
|
22
|
+
jid = Blather::JID.new(fqdn, domain, 'vines')
|
23
|
+
@stream = Blather::Client.setup(jid, password, host, port)
|
24
|
+
|
25
|
+
@stream.register_handler(:disconnected) do
|
26
|
+
log.info("Stream disconnected, reconnecting . . .")
|
27
|
+
EM::Timer.new(rand(16) + 5) do
|
28
|
+
self.class.new(options).start
|
29
|
+
end
|
30
|
+
true # prevent EM.stop
|
31
|
+
end
|
32
|
+
|
33
|
+
@stream.register_handler(:ready) do
|
34
|
+
# prevent handler called twice
|
35
|
+
unless @ready
|
36
|
+
log.info("Connected #{@stream.jid} agent to #{host}:#{port}")
|
37
|
+
@ready = true
|
38
|
+
startup
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@stream.register_handler(:subscription, :request?) do |node|
|
43
|
+
# ignore, rather than refuse, requests from users lacking
|
44
|
+
# permissions, so agents are invisible to them
|
45
|
+
@stream.write(node.approve!) if valid_user?(node.from)
|
46
|
+
end
|
47
|
+
|
48
|
+
@stream.register_handler(:file_transfer) do |iq|
|
49
|
+
transfer_file(iq)
|
50
|
+
end
|
51
|
+
|
52
|
+
@stream.register_handler(:disco_info, :get?) do |node|
|
53
|
+
disco_info(node)
|
54
|
+
end
|
55
|
+
|
56
|
+
@stream.register_handler(:iq, '/iq[@type="set"]/ns:query', :ns => SYSTEMS) do |node|
|
57
|
+
update_permissions(node)
|
58
|
+
end
|
59
|
+
|
60
|
+
@stream.register_handler(:iq, '/iq[@type="get"]/ns:query', :ns => 'jabber:iq:version') do |node|
|
61
|
+
version(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
@stream.register_handler(:message, :chat?, :body) do |msg|
|
65
|
+
process_message(msg)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def start
|
70
|
+
@stream.run
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# After the bot connects to the chat server, discover the component, send
|
76
|
+
# our ohai system description data, and initialize permissions.
|
77
|
+
def startup
|
78
|
+
cb = proc do |component|
|
79
|
+
if component
|
80
|
+
log.info("Found vines component at #{component}")
|
81
|
+
@component = component
|
82
|
+
send_system_info
|
83
|
+
request_permissions
|
84
|
+
else
|
85
|
+
log.info("Vines component not found, rediscovering . . .")
|
86
|
+
EM::Timer.new(10) { discover_component(&cb) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
discover_component(&cb)
|
90
|
+
end
|
91
|
+
|
92
|
+
def version(node)
|
93
|
+
return unless from_service?(node) || valid_user?(node.from)
|
94
|
+
iq = Blather::Stanza::Iq::Query.new(:result)
|
95
|
+
iq.id, iq.to = node.id, node.from
|
96
|
+
iq.query.add_child("<name>Vines Agent</name>")
|
97
|
+
iq.query.add_child("<version>#{VERSION}</version>")
|
98
|
+
@stream.write(iq)
|
99
|
+
end
|
100
|
+
|
101
|
+
def disco_info(node)
|
102
|
+
return unless from_service?(node) || valid_user?(node.from)
|
103
|
+
disco = Blather::Stanza::DiscoInfo.new(:result)
|
104
|
+
disco.id = node.id
|
105
|
+
disco.to = node.from
|
106
|
+
disco.identities = {
|
107
|
+
name: 'Vines Agent',
|
108
|
+
type: 'bot',
|
109
|
+
category: 'client'
|
110
|
+
}
|
111
|
+
disco.features = %w[
|
112
|
+
http://jabber.org/protocol/bytestreams
|
113
|
+
http://jabber.org/protocol/disco#info
|
114
|
+
http://jabber.org/protocol/si
|
115
|
+
http://jabber.org/protocol/si/profile/file-transfer
|
116
|
+
http://jabber.org/protocol/xhtml-im
|
117
|
+
jabber:iq:version
|
118
|
+
]
|
119
|
+
@stream.write(disco)
|
120
|
+
end
|
121
|
+
|
122
|
+
def transfer_file(iq)
|
123
|
+
return unless from_service?(iq) || valid_user?(iq.from)
|
124
|
+
name, size = iq.si.file['name'], iq.si.file['size'].to_i
|
125
|
+
log.info("Receiving file: #{name}")
|
126
|
+
transfer = Blather::FileTransfer.new(@stream, iq)
|
127
|
+
file = absolute_path(download, name) rescue nil
|
128
|
+
if file
|
129
|
+
transfer.accept(Blather::FileTransfer::SimpleFileReceiver, file, size)
|
130
|
+
else
|
131
|
+
transfer.decline
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def absolute_path(dir, name)
|
136
|
+
File.expand_path(name, dir).tap do |absolute|
|
137
|
+
raise 'path traversal' unless File.dirname(absolute) == dir
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Collect and send ohai system data for this machine back to the
|
142
|
+
# component.
|
143
|
+
def send_system_info
|
144
|
+
system = Ohai::System.new.tap do |sys|
|
145
|
+
sys.all_plugins
|
146
|
+
end
|
147
|
+
iq = Blather::Stanza::Iq::Query.new(:set).tap do |node|
|
148
|
+
node.to = @component
|
149
|
+
node.query.content = system.to_json
|
150
|
+
node.query.namespace = SYSTEMS
|
151
|
+
end
|
152
|
+
@stream.write(iq)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return the fully qualified domain name for this machine. This is used
|
156
|
+
# to determine the agent's JID.
|
157
|
+
def fqdn
|
158
|
+
system = Ohai::System.new
|
159
|
+
system.require_plugin('os')
|
160
|
+
system.require_plugin('hostname')
|
161
|
+
system.fqdn.downcase
|
162
|
+
end
|
163
|
+
|
164
|
+
# Use service discovery to find the JID of our Vines component. We ask the
|
165
|
+
# server for its list of components, then ask each component for it's info.
|
166
|
+
# The component broadcasting the http://getvines.com/protocol feature is our
|
167
|
+
# Vines service.
|
168
|
+
def discover_component
|
169
|
+
disco = Blather::Stanza::DiscoItems.new
|
170
|
+
disco.to = @stream.jid.domain
|
171
|
+
@stream.write_with_handler(disco) do |result|
|
172
|
+
items = result.error? ? [] : result.items
|
173
|
+
Fiber.new do
|
174
|
+
# use fiber instead of EM::Iterator until EM 1.0.0 release
|
175
|
+
found = items.find {|item| component?(item.jid) }
|
176
|
+
yield found ? found.jid : nil
|
177
|
+
end.resume
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return true if this JID is the Vines component with which we need to
|
182
|
+
# communicate. This method suspends the Fiber that calls it in order to
|
183
|
+
# turn the disco#info requests synchronous.
|
184
|
+
def component?(jid)
|
185
|
+
fiber = Fiber.current
|
186
|
+
info = Blather::Stanza::DiscoInfo.new
|
187
|
+
info.to = jid
|
188
|
+
@stream.write_with_handler(info) do |reply|
|
189
|
+
features = reply.error? ? [] : reply.features
|
190
|
+
found = !!features.find {|f| f.var == NS }
|
191
|
+
fiber.resume(found)
|
192
|
+
end
|
193
|
+
Fiber.yield
|
194
|
+
end
|
195
|
+
|
196
|
+
# Download the list of unix user accounts and the JID's that are allowed
|
197
|
+
# to use them. This is used to determine if a change user command like
|
198
|
+
# +v user root+ is allowed.
|
199
|
+
def request_permissions
|
200
|
+
iq = Blather::Stanza::Iq::Query.new(:get).tap do |node|
|
201
|
+
node.to = @component
|
202
|
+
node.query['name'] = @stream.jid.node
|
203
|
+
node.query.namespace = SYSTEMS
|
204
|
+
end
|
205
|
+
@stream.write_with_handler(iq) do |reply|
|
206
|
+
update_permissions(reply) unless reply.error?
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def update_permissions(node)
|
211
|
+
return unless node.from == @component
|
212
|
+
obj = JSON.parse(node.content) rescue {}
|
213
|
+
@permissions = obj['permissions'] || {}
|
214
|
+
@services = (obj['services'] || {}).map {|s| s['jid'] }
|
215
|
+
@sessions.values.each {|shell| shell.permissions = @permissions }
|
216
|
+
end
|
217
|
+
|
218
|
+
# Execute the incoming XMPP message as a shell command if the sender is
|
219
|
+
# allowed to execute commands on this agent as the requested user name.
|
220
|
+
def process_message(message)
|
221
|
+
bare, full = message.from.stripped.to_s, message.from.to_s
|
222
|
+
forward_to = nil
|
223
|
+
|
224
|
+
if from_service?(message)
|
225
|
+
jid = message.xpath('/message/ns:jid', 'ns' => NS).first
|
226
|
+
jid = Blather::JID.new(jid.content) rescue nil
|
227
|
+
return unless jid
|
228
|
+
bare, full = jid.stripped.to_s, jid.to_s
|
229
|
+
forward_to = full
|
230
|
+
end
|
231
|
+
|
232
|
+
return unless valid_user?(bare)
|
233
|
+
session = @sessions[full] ||= Shell.new(bare, @permissions)
|
234
|
+
session.run(message.body.strip) do |output|
|
235
|
+
@stream.write(reply(message, output, forward_to))
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Reply to the sender's message with the command's output. The component
|
240
|
+
# uses the thread attribute to pair command messages with their output
|
241
|
+
# replies.
|
242
|
+
def reply(message, body, forward_to)
|
243
|
+
Blather::Stanza::Message.new(message.from, body).tap do |node|
|
244
|
+
node << node.document.create_element('jid', forward_to, xmlns: NS) if forward_to
|
245
|
+
node.thread = message.thread if message.thread
|
246
|
+
node.xhtml = '<span style="font-family:Menlo,Courier,monospace;"></span>'
|
247
|
+
span = node.xhtml_node.elements.first
|
248
|
+
body.each_line do |line|
|
249
|
+
span.add_child(Nokogiri::XML::Text.new(line.chomp, span.document))
|
250
|
+
span.add_child(span.document.create_element('br'))
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Return true if the JID is allowed to run commands as a unix user
|
256
|
+
# account on the system. Return false if the agent doesn't have a
|
257
|
+
# permissions entry for this JID.
|
258
|
+
def valid_user?(jid)
|
259
|
+
jid = jid.stripped.to_s if jid.respond_to?(:stripped)
|
260
|
+
valid = !!@permissions.find {|unix, jids| jids.include?(jid) }
|
261
|
+
log.warn("Denied access to #{jid}") unless valid
|
262
|
+
valid
|
263
|
+
end
|
264
|
+
|
265
|
+
# Return true if the stanza was sent to the agent by a service JID to
|
266
|
+
# which the agent belongs. The agent will only reply to stanzas sent from
|
267
|
+
# users it trusts or from services of which it's a member. Stanzas
|
268
|
+
# received from untrusted JID's are ignored.
|
269
|
+
def from_service?(node)
|
270
|
+
@services.include?(node.from.stripped.to_s.downcase)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|