OpenSecurity/bin/opensecurity_client_restful_server.py added join to wait for netmount /umount thread finish
2 # -*- coding: utf-8 -*-
4 # ------------------------------------------------------------
5 # opensecurity_client_restful_server
7 # the OpenSecurity client RESTful server
9 # Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
11 # Copyright (C) 2013 AIT Austrian Institute of Technology
12 # AIT Austrian Institute of Technology GmbH
13 # Donau-City-Strasse 1 | 1220 Vienna | Austria
14 # http://www.ait.ac.at
16 # This program is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public License
18 # as published by the Free Software Foundation version 2.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 # Boston, MA 02110-1301, USA.
29 # ------------------------------------------------------------
32 # ------------------------------------------------------------
50 from opensecurity_util import logger, setupLogger, OpenSecurityException
51 if sys.platform == 'win32' or sys.platform == 'cygwin':
52 from cygwin import Cygwin
55 import __init__ as opensecurity
58 # ------------------------------------------------------------
62 """All the URLs we know mapping to class handler"""
64 '/credentials', 'os_credentials',
65 '/keyfile', 'os_keyfile',
67 '/notification', 'os_notification',
68 '/password', 'os_password',
69 '/netmount', 'os_netmount',
70 '/netumount', 'os_netumount',
75 # ------------------------------------------------------------
79 """The REST server object"""
83 # ------------------------------------------------------------
89 """OpenSecurity '/credentials' handler.
91 This is called on GET /credentials?text=TEXT.
92 Ideally this should pop up a user dialog to insert his
93 credentials based the given TEXT.
102 if not "text" in args:
103 raise web.badrequest('no text given')
106 remote_ip = web.ctx.environ['REMOTE_ADDR']
108 # create the process which queries the user
109 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
110 process_command = [sys.executable, dlg_image, 'credentials', args.text]
111 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
113 # run process result handling in seprate thread (not to block main one)
114 bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
117 return 'user queried for credentials'
122 """OpenSecurity '/keyfile' handler.
124 This is called on GET /keyfile?text=TEXT.
125 Ideally this should pop up a user dialog to insert his
126 password along with a keyfile.
135 if not "text" in args:
136 raise web.badrequest('no text given')
139 remote_ip = web.ctx.environ['REMOTE_ADDR']
141 # create the process which queries the user
142 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
143 process_command = [sys.executable, dlg_image, 'keyfile', args.text]
144 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
146 # run process result handling in seprate thread (not to block main one)
147 bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
150 return 'user queried for password and keyfile'
155 """OpenSecurity '/log' handler.
157 This is called on GET or POST on the log function /log
170 args['user'] = getpass.getuser()
171 args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
174 url_addr = 'http://GIMME-SERVER-TO-LOG-TO/log'
176 # by provided a 'data' we turn this into a POST statement
177 d = urllib.urlencode(args)
178 req = urllib2.Request(url_addr, d)
180 res = urllib2.urlopen(req)
182 print('failed to contact: ' + url_addr)
183 print('log data: ' + d)
189 class os_notification:
191 """OpenSecurity '/notification' handler.
193 This is called on GET /notification?msgtype=TYPE&text=TEXT.
194 This will pop up an OpenSecurity notifcation window
203 if not "msgtype" in args:
204 raise web.badrequest('no msgtype given')
206 if not args.msgtype in ['information', 'warning', 'critical']:
207 raise web.badrequest('Unknown value for msgtype')
210 if not "text" in args:
211 raise web.badrequest('no text given')
213 # invoke the user dialog as a subprocess
214 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
215 process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
216 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
223 """OpenSecurity '/password' handler.
225 This is called on GET /password?text=TEXT.
226 Ideally this should pop up a user dialog to insert his
227 password based device name.
236 if not "text" in args:
237 raise web.badrequest('no text given')
240 remote_ip = web.ctx.environ['REMOTE_ADDR']
242 # create the process which queries the user
243 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
244 process_command = [sys.executable, dlg_image, 'password', args.text]
245 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
247 # run process result handling in seprate thread (not to block main one)
248 bouncer = ProcessResultBouncer(process, remote_ip, '/password')
251 return 'user queried for password'
253 # handles netumount request
254 class MountNetworkDriveHandler(threading.Thread):
258 def __init__(self, drv, net_path):
259 threading.Thread.__init__(self)
261 self.networkPath = net_path
264 #Check for drive availability
265 if os.path.exists(self.drive):
266 logger.error("Drive letter is already in use: " + self.drive)
269 #Check for network resource availability
271 while not os.path.exists(self.networkPath):
275 logger.info("Path not accessible: " + self.networkPath + " retrying")
278 command = 'USE ' + self.drive + ' ' + self.networkPath + ' /PERSISTENT:NO'
280 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\NET', command))
281 if string.find(result[1], 'successfully',) == -1:
282 logger.error("Failed: NET " + command)
288 """OpenSecurity '/netmount' handler"""
294 # we _need_ a net_resource
295 if not "net_resource" in args:
296 raise web.badrequest('no net_resource given')
298 # we _need_ a drive_letter
299 if not "drive_letter" in args:
300 raise web.badrequest('no drive_letter given')
302 driveHandler = MountNetworkDriveHandler(args['drive_letter'], args['net_resource'])
304 driveHandler.join(None)
309 # handles netumount request
310 class UmountNetworkDriveHandler(threading.Thread):
314 def __init__(self, drv):
315 threading.Thread.__init__(self)
320 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', 'USE'))
321 mappedDrives = list()
322 for line in result[1].splitlines():
323 if 'USB' in line or 'Download' in line:
325 mappedDrives.append(parts[1])
327 logger.info(mappedDrives)
328 logger.info(self.drive)
329 if self.drive not in mappedDrives:
332 command = 'USE ' + self.drive + ' /DELETE /YES'
333 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', command))
334 if string.find(str(result[1]), 'successfully',) == -1:
335 logger.error(result[2])
341 """OpenSecurity '/netumount' handler"""
347 # we _need_ a drive_letter
348 if not "drive_letter" in args:
349 raise web.badrequest('no drive_letter given')
351 driveHandler = UmountNetworkDriveHandler(args['drive_letter'])
353 driveHandler.join(None)
359 """OpenSecurity '/' handler"""
363 res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
365 # add some sample links
371 (copy paste this into your browser's address field after the host:port)
373 /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
375 (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
376 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
379 Request a combination of user and password:
380 (copy paste this into your browser's address field after the host:port)
382 /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
384 (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
385 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
388 Request a combination of password and keyfile:
389 (copy paste this into your browser's address field after the host:port)
391 /keyfile?text=Your%20private%20RSA%20Keyfile%3A
393 (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
394 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
398 (copy paste this into your browser's address field after the host:port)
400 /application?vm=Debian+7&app=Browser
402 (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
408 class ProcessResultBouncer(threading.Thread):
410 """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
412 def __init__(self, process, remote_ip, resource):
416 threading.Thread.__init__(self)
417 self._process = process
418 self._remote_ip = remote_ip
419 self._resource = resource
432 # invoke the user dialog as a subprocess
433 result = self._process.communicate()[0]
434 if self._process.returncode != 0:
435 print 'user request has been aborted.'
438 # all ok, tell send request back appropriate destination
440 j = json.loads(result)
442 print 'error in password parsing'
445 # TODO: it would be WAY easier and secure if we just
446 # add the result json to a HTTP-POST here.
447 url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
449 # by provided a 'data' we turn this into a POST statement
450 req = urllib2.Request(url_addr, urllib.urlencode(j))
452 res = urllib2.urlopen(req)
454 print 'failed to contact: ' + url_addr
458 class RESTServerThread(threading.Thread):
460 """Thread for serving the REST API."""
462 def __init__(self, port):
465 threading.Thread.__init__(self)
481 def is_already_running(port = 8090):
483 """check if this is started twice"""
486 s = socket.create_connection(('127.0.0.1', port), 0.5)
495 """Start the REST server"""
499 # trick the web.py server
500 sys.argv = [__file__, str(port)]
501 server = web.application(opensecurity_urls, globals())
505 def serve(port = 8090, background = False):
507 """Start serving the REST Api
508 port ... port number to listen on
509 background ... cease into background (spawn thread) and return immediately"""
511 # start threaded or direct version
512 if background == True:
513 t = RESTServerThread(port)
520 """Stop serving the REST Api"""
530 if __name__ == "__main__":