4 # :title: Web service for bot
6 # Author:: Matthias Hecker (apoc@geekosphere.org)
8 # HTTP(S)/json based web service for remote controlling the bot,
9 # similar to remote but much more portable.
11 # For more info/documentation:
12 # https://github.com/4poc/rbot/wiki/Web-Service
16 require 'webrick/https'
21 class ::WebServiceUser < Irc::User
22 def initialize(str, botuser, opts={})
28 attr_accessor :response
31 class PingServlet < WEBrick::HTTPServlet::AbstractServlet
32 def initialize(server, bot)
38 res['Content-Type'] = 'text/plain'
43 class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
44 def initialize(server, bot)
49 def dispatch_command(command, botuser, ip)
50 netmask = '%s!%s@%s' % [botuser.username, botuser.username, ip]
52 user = WebServiceUser.new(netmask, botuser)
53 message = Irc::PrivMessage.new(@bot, nil, user, @bot.myself, command)
55 @bot.plugins.irc_delegate('privmsg', message)
57 { :reply => user.response }
60 # Handle a dispatch request.
62 post = CGI::parse(req.body)
65 username = post['username'].first
66 password = post['password'].first
67 command = post['command'].first
69 botuser = @bot.auth.get_botuser(username)
70 raise 'Permission Denied' if not botuser or botuser.password != password
73 ret = dispatch_command(command, botuser, ip)
75 debug '[webservice] error: ' + $!.to_s
80 if req['Accept'] == 'application/json'
81 res['Content-Type'] = 'application/json'
82 res.body = JSON.dump ret
84 res['Content-Type'] = 'text/plain'
85 res.body = ret[:reply].join("\n") + "\n"
90 class WebServiceModule < CoreBotModule
92 Config.register Config::BooleanValue.new('webservice.autostart',
94 :requires_rescan => true,
95 :desc => 'Whether the web service should be started automatically')
97 Config.register Config::IntegerValue.new('webservice.port',
98 :default => 7260, # that's 'rbot'
99 :requires_rescan => true,
100 :desc => 'Port on which the web service will listen')
102 Config.register Config::StringValue.new('webservice.host',
103 :default => '127.0.0.1',
104 :requires_rescan => true,
105 :desc => 'Host the web service will bind on')
107 Config.register Config::BooleanValue.new('webservice.ssl',
109 :requires_rescan => true,
110 :desc => 'Whether the web server should use SSL (recommended!)')
112 Config.register Config::StringValue.new('webservice.ssl_key',
113 :default => '~/.rbot/wskey.pem',
114 :requires_rescan => true,
115 :desc => 'Private key file to use for SSL')
117 Config.register Config::StringValue.new('webservice.ssl_cert',
118 :default => '~/.rbot/wscert.pem',
119 :requires_rescan => true,
120 :desc => 'Certificate file to use for SSL')
124 @port = @bot.config['webservice.port']
125 @host = @bot.config['webservice.host']
127 @bot.webservice = self
129 start_service if @bot.config['webservice.autostart']
131 error "couldn't start web service provider: #{e.inspect}"
136 raise "Remote service provider already running" if @server
137 opts = {:BindAddress => @host, :Port => @port}
138 if @bot.config['webservice.ssl']
139 opts.merge! :SSLEnable => true
140 cert = File.expand_path @bot.config['webservice.ssl_cert']
141 key = File.expand_path @bot.config['webservice.ssl_key']
142 if File.exists? cert and File.exists? key
143 debug 'using ssl certificate files'
145 :SSLCertificate => OpenSSL::X509::Certificate.new(File.read(cert)),
146 :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.read(key))
149 debug 'using on-the-fly generated ssl certs'
150 opts.merge! :SSLCertName => [ %w[CN localhost] ]
151 # the problem with this is that it will always use the same
152 # serial number which makes this feature pretty much useless.
155 # Logging to file in ~/.rbot
156 logfile = File.open(@bot.path('webservice.log'), 'a+')
158 :Logger => WEBrick::Log.new(logfile),
159 :AccessLog => [[logfile, WEBrick::AccessLog::COMBINED_LOG_FORMAT]]
161 @server = WEBrick::HTTPServer.new(opts)
162 debug 'webservice started: ' + opts.inspect
163 @server.mount('/dispatch', DispatchServlet, @bot)
164 @server.mount('/ping', PingServlet, @bot)
165 Thread.new { @server.start }
169 @server.shutdown if @server
178 def handle_start(m, params)
181 s << 'web service already running'
185 s << 'web service started'
187 s << 'unable to start web service, error: ' + $!.to_s
193 def register_servlet(plugin, servlet)
194 @server.mount('/plugin/%s' % plugin.name, servlet, plugin, @bot)
199 webservice = WebServiceModule.new
201 webservice.map 'webservice start',
202 :action => 'handle_start',
203 :auth_path => ':manage:'
205 webservice.map 'webservice stop',
206 :action => 'handle_stop',
207 :auth_path => ':manage:'
209 webservice.default_auth('*', false)