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 # ------------------------------------------------------------
56 from opensecurity_util import logger, setupLogger, OpenSecurityException
57 if sys.platform == 'win32' or sys.platform == 'cygwin':
58 from cygwin import Cygwin
61 import __init__ as opensecurity
62 from environment import Environment
65 # ------------------------------------------------------------
69 """All the URLs we know mapping to class handler"""
71 '/credentials', 'os_credentials',
72 '/keyfile', 'os_keyfile',
74 '/notification', 'os_notification',
75 '/password', 'os_password',
76 '/netmount', 'os_netmount',
77 '/netumount', 'os_netumount',
82 # ------------------------------------------------------------
86 """lock for read/write log file"""
87 log_file_lock = threading.Lock()
89 """timer for the log file bouncer"""
90 log_file_bouncer = None
93 """The REST server object"""
97 # ------------------------------------------------------------
101 class os_credentials:
103 """OpenSecurity '/credentials' handler.
105 This is called on GET /credentials?text=TEXT.
106 Ideally this should pop up a user dialog to insert his
107 credentials based the given TEXT.
116 if not "text" in args:
117 raise web.badrequest('no text given')
120 remote_ip = web.ctx.environ['REMOTE_ADDR']
122 # create the process which queries the user
123 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
124 process_command = [sys.executable, dlg_image, 'credentials', args.text]
125 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
127 # run process result handling in seprate thread (not to block main one)
128 bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
131 return 'user queried for credentials'
136 """OpenSecurity '/keyfile' handler.
138 This is called on GET /keyfile?text=TEXT.
139 Ideally this should pop up a user dialog to insert his
140 password along with a keyfile.
149 if not "text" in args:
150 raise web.badrequest('no text given')
153 remote_ip = web.ctx.environ['REMOTE_ADDR']
155 # create the process which queries the user
156 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
157 process_command = [sys.executable, dlg_image, 'keyfile', args.text]
158 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
160 # run process result handling in seprate thread (not to block main one)
161 bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
164 return 'user queried for password and keyfile'
169 """OpenSecurity '/log' handler.
171 This is called on GET or POST on the log function /log
184 args['user'] = getpass.getuser()
185 args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
187 # add these to new data to log
189 log_file_name = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
190 log_file_lock.acquire()
191 pickle.dump(args, open(log_file_name, 'ab'))
192 log_file_lock.release()
197 class os_notification:
199 """OpenSecurity '/notification' handler.
201 This is called on GET /notification?msgtype=TYPE&text=TEXT.
202 This will pop up an OpenSecurity notifcation window
211 if not "msgtype" in args:
212 raise web.badrequest('no msgtype given')
214 if not args.msgtype in ['information', 'warning', 'critical']:
215 raise web.badrequest('Unknown value for msgtype')
218 if not "text" in args:
219 raise web.badrequest('no text given')
221 # invoke the user dialog as a subprocess
222 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
223 process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
224 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
231 """OpenSecurity '/password' handler.
233 This is called on GET /password?text=TEXT.
234 Ideally this should pop up a user dialog to insert his
235 password based device name.
244 if not "text" in args:
245 raise web.badrequest('no text given')
248 remote_ip = web.ctx.environ['REMOTE_ADDR']
250 # create the process which queries the user
251 dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
252 process_command = [sys.executable, dlg_image, 'password', args.text]
253 process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
255 # run process result handling in seprate thread (not to block main one)
256 bouncer = ProcessResultBouncer(process, remote_ip, '/password')
259 return 'user queried for password'
261 # handles netumount request
262 class MountNetworkDriveHandler(threading.Thread):
266 def __init__(self, drv, net_path):
267 threading.Thread.__init__(self)
269 self.networkPath = net_path
272 #Check for drive availability
273 if os.path.exists(self.drive):
274 logger.error("Drive letter is already in use: " + self.drive)
277 #Check for network resource availability
279 while not os.path.exists(self.networkPath):
283 logger.info("Path not accessible: " + self.networkPath + " retrying")
286 command = 'USE ' + self.drive + ' ' + self.networkPath + ' /PERSISTENT:NO'
288 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\NET', command))
289 if string.find(result[1], 'successfully',) == -1:
290 logger.error("Failed: NET " + command)
296 """OpenSecurity '/netmount' handler"""
302 # we _need_ a net_resource
303 if not "net_resource" in args:
304 raise web.badrequest('no net_resource given')
306 # we _need_ a drive_letter
307 if not "drive_letter" in args:
308 raise web.badrequest('no drive_letter given')
310 driveHandler = MountNetworkDriveHandler(args['drive_letter'], args['net_resource'])
312 driveHandler.join(None)
317 # handles netumount request
318 class UmountNetworkDriveHandler(threading.Thread):
322 def __init__(self, drv):
323 threading.Thread.__init__(self)
328 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', 'USE'))
329 mappedDrives = list()
330 for line in result[1].splitlines():
331 if 'USB' in line or 'Download' in line:
333 mappedDrives.append(parts[1])
335 logger.info(mappedDrives)
336 logger.info(self.drive)
337 if self.drive not in mappedDrives:
340 command = 'USE ' + self.drive + ' /DELETE /YES'
341 result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', command))
342 if string.find(str(result[1]), 'successfully',) == -1:
343 logger.error(result[2])
349 """OpenSecurity '/netumount' handler"""
355 # we _need_ a drive_letter
356 if not "drive_letter" in args:
357 raise web.badrequest('no drive_letter given')
359 driveHandler = UmountNetworkDriveHandler(args['drive_letter'])
361 driveHandler.join(None)
367 """OpenSecurity '/' handler"""
371 res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
373 # add some sample links
379 (copy paste this into your browser's address field after the host:port)
381 /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
383 (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
384 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
387 Request a combination of user and password:
388 (copy paste this into your browser's address field after the host:port)
390 /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
392 (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
393 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
396 Request a combination of password and keyfile:
397 (copy paste this into your browser's address field after the host:port)
399 /keyfile?text=Your%20private%20RSA%20Keyfile%3A
401 (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
402 NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
406 (copy paste this into your browser's address field after the host:port)
408 /application?vm=Debian+7&app=Browser
410 (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
416 class ProcessResultBouncer(threading.Thread):
418 """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
420 def __init__(self, process, remote_ip, resource):
424 threading.Thread.__init__(self)
425 self._process = process
426 self._remote_ip = remote_ip
427 self._resource = resource
440 # invoke the user dialog as a subprocess
441 result = self._process.communicate()[0]
442 if self._process.returncode != 0:
443 print 'user request has been aborted.'
446 # all ok, tell send request back appropriate destination
448 j = json.loads(result)
450 print 'error in password parsing'
453 # by provided a 'data' we turn this into a POST statement
454 url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
455 req = urllib2.Request(url_addr, urllib.urlencode(j))
457 res = urllib2.urlopen(req)
459 print 'failed to contact: ' + url_addr
463 class RESTServerThread(threading.Thread):
465 """Thread for serving the REST API."""
467 def __init__(self, port):
470 threading.Thread.__init__(self)
486 def is_already_running(port = 8090):
488 """check if this is started twice"""
491 s = socket.create_connection(('127.0.0.1', port), 0.5)
498 def _bounce_vm_logs():
500 """grab all logs from the VMs and push them to the log servers"""
504 # pick the highest current number
506 for f in glob.iglob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*')):
508 n = f.split('.')[-1:][0]
516 # first add new vm logs to our existing one: rename the log file
517 log_file_name_new = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
518 log_file_name_cur = os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.' + str(cur))
519 log_file_lock.acquire()
521 os.rename(log_file_name_new, log_file_name_cur)
522 print('new log file: ' + log_file_name_cur)
525 log_file_lock.release()
527 # now we have a list of next log files to dump
528 log_files = glob.glob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*'))
530 for log_file in log_files:
533 f = open(log_file, 'rb')
543 logger.warning('tried to delete log file (pushed to EOF) "' + log_file + '" but failed')
546 logger.warning('encountered error while pushing log file "' + log_file + '"')
549 # start bouncer again ...
550 global log_file_bouncer
551 log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
552 log_file_bouncer.start()
556 """POST a single log to log server
558 @param log the log POST param
562 key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\OpenSecurity')
563 log_server_url = str(win32api.RegQueryValueEx(key, 'LogServerURL')[0])
564 win32api.RegCloseKey(key)
566 logger.critical('Cannot open Registry HKEY_LOCAL_MACHINE\SOFTWARE\OpenSecurity and get LogServerURL value')
569 # by provided a 'data' we turn this into a POST statement
570 d = urllib.urlencode(log)
571 req = urllib2.Request(log_server_url, d)
573 logger.debug('pushed log to server: ' + str(log_server_url))
578 """Start the REST server"""
582 # start the VM-log bouncer timer
583 global log_file_bouncer
584 log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
585 log_file_bouncer.start()
587 # trick the web.py server
588 sys.argv = [__file__, str(port)]
589 server = web.application(opensecurity_urls, globals())
593 def serve(port = 8090, background = False):
595 """Start serving the REST Api
596 port ... port number to listen on
597 background ... cease into background (spawn thread) and return immediately"""
599 # start threaded or direct version
600 if background == True:
601 t = RESTServerThread(port)
608 """Stop serving the REST Api"""
614 global log_file_bouncer
615 if log_file_bouncer is not None:
616 log_file_bouncer.cancel()
621 if __name__ == "__main__":