]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/webservice.rb
[webservice] response as json if asked to
[user/henk/code/ruby/rbot.git] / lib / rbot / core / webservice.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Web service for bot
5 #
6 # Author:: Matthias Hecker (apoc@geekosphere.org)
7 #
8 # HTTP(S)/json based web service for remote controlling the bot,
9 # similar to remote but much more portable.
10 #
11 # For more info/documentation:
12 # https://github.com/4poc/rbot/wiki/Web-Service
13 #
14
15 require 'webrick'
16 require 'webrick/https'
17 require 'openssl'
18 require 'cgi'
19 require 'json'
20
21 class ::WebServiceUser < Irc::User
22   def initialize(str, botuser, opts={})
23     super(str, opts)
24     @botuser = botuser
25     @response = []
26   end
27   attr_reader :botuser
28   attr_accessor :response
29 end
30
31 class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
32   def initialize(server, bot)
33     super server
34     @bot = bot
35   end
36
37   def dispatch_command(command, botuser, ip)
38     netmask = '%s!%s@%s' % [botuser.username, botuser.username, ip]
39
40     user = WebServiceUser.new(netmask, botuser)
41     message = Irc::PrivMessage.new(@bot, nil, user, @bot.myself, command)
42
43     @bot.plugins.irc_delegate('privmsg', message)
44
45     { :reply => user.response }
46   end
47
48   # Handle a dispatch request.
49   def do_POST(req, res)
50     post = CGI::parse(req.body)
51     ip = req.peeraddr[3]
52
53     username = post['username'].first
54     password = post['password'].first
55     command = post['command'].first
56
57     botuser = @bot.auth.get_botuser(username)
58     raise 'Permission Denied' if not botuser or botuser.password != password
59
60     ret = dispatch_command(command, botuser, ip)
61
62     res.status = 200
63     if req['Accept'] == 'application/json'
64       res['Content-Type'] = 'application/json'
65       res.body = JSON.dump ret
66     else
67       res['Content-Type'] = 'text/plain'
68       res.body = ret[:reply].join("\n") + "\n"
69     end
70   end
71 end
72
73 class WebServiceModule < CoreBotModule
74
75   Config.register Config::BooleanValue.new('webservice.autostart',
76     :default => false,
77     :requires_rescan => true,
78     :desc => 'Whether the web service should be started automatically')
79
80   Config.register Config::IntegerValue.new('webservice.port',
81     :default => 7260, # that's 'rbot'
82     :requires_rescan => true,
83     :desc => 'Port on which the web service will listen')
84
85   Config.register Config::StringValue.new('webservice.host',
86     :default => '127.0.0.1',
87     :requires_rescan => true,
88     :desc => 'Host the web service will bind on')
89
90   Config.register Config::BooleanValue.new('webservice.ssl',
91     :default => false,
92     :requires_rescan => true,
93     :desc => 'Whether the web server should use SSL (recommended!)')
94
95   Config.register Config::StringValue.new('webservice.ssl_key',
96     :default => '~/.rbot/wskey.pem',
97     :requires_rescan => true,
98     :desc => 'Private key file to use for SSL')
99
100   Config.register Config::StringValue.new('webservice.ssl_cert',
101     :default => '~/.rbot/wscert.pem',
102     :requires_rescan => true,
103     :desc => 'Certificate file to use for SSL')
104
105   def initialize
106     super
107     @port = @bot.config['webservice.port']
108     @host = @bot.config['webservice.host']
109     @server = nil
110     begin
111       start_service if @bot.config['webservice.autostart']
112     rescue => e
113       error "couldn't start web service provider: #{e.inspect}"
114     end
115   end
116
117   def start_service
118     raise "Remote service provider already running" if @server
119     opts = {:BindAddress => @host, :Port => @port}
120     if @bot.config['webservice.ssl']
121       opts.merge! :SSLEnable => true
122       cert = File.expand_path @bot.config['webservice.ssl_cert']
123       key = File.expand_path @bot.config['webservice.ssl_key']
124       if File.exists? cert and File.exists? key
125         debug 'using ssl certificate files'
126         opts.merge!({
127           :SSLCertificate => OpenSSL::X509::Certificate.new(File.read(cert)),
128           :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.read(key))
129         })
130       else
131         debug 'using on-the-fly generated ssl certs'
132         opts.merge! :SSLCertName => [ %w[CN localhost] ]
133         # the problem with this is that it will always use the same
134         # serial number which makes this feature pretty much useless.
135       end
136     end
137     @server = WEBrick::HTTPServer.new(opts)
138     debug 'webservice started: ' + opts.inspect
139     @server.mount('/dispatch', DispatchServlet, @bot)
140     Thread.new { @server.start }
141   end
142
143   def stop_service
144     @server.shutdown if @server
145     @server = nil
146   end
147
148   def cleanup
149     stop_service
150     super
151   end
152
153   def handle_start(m, params)
154     s = ''
155     if @server
156       s << 'web service already running'
157     else
158       begin
159         start_service
160         s << 'web service started'
161       rescue
162         s << 'unable to start web service, error: ' + $!.to_s
163       end
164     end
165     m.reply s
166   end
167
168 end
169
170 webservice = WebServiceModule.new
171
172 webservice.map 'webservice start',
173   :action => 'handle_start',
174   :auth_path => ':manage:'
175
176 webservice.map 'webservice stop',
177   :action => 'handle_stop',
178   :auth_path => ':manage:'
179
180 webservice.default_auth('*', false)
181