OpenSecurity/bin/opensecurity_client_restful_server.py
author BarthaM@N3SIM1218.D03.arc.local
Mon, 19 May 2014 17:19:47 +0100
changeset 163 e7fbdaabd0bc
parent 160 c014a9db4b55
child 164 b6b9dc0ed2ac
permissions -rwxr-xr-x
OpenSecurity/bin/opensecurity_client_restful_server.py added join to wait for netmount /umount thread finish
     1 #!/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 # ------------------------------------------------------------
     5 # opensecurity_client_restful_server
     6 # 
     7 # the OpenSecurity client RESTful server
     8 #
     9 # Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    10 #
    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
    15 #
    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.
    19 # 
    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.
    24 # 
    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 # ------------------------------------------------------------
    30 
    31 
    32 # ------------------------------------------------------------
    33 # imports
    34 
    35 import getpass
    36 import json
    37 import os
    38 import os.path
    39 import platform
    40 import socket
    41 import subprocess
    42 import sys
    43 import urllib
    44 import urllib2
    45 import web
    46 import threading
    47 import time
    48 import string
    49 
    50 from opensecurity_util import logger, setupLogger, OpenSecurityException
    51 if sys.platform == 'win32' or sys.platform == 'cygwin':
    52     from cygwin import Cygwin
    53 
    54 # local
    55 import __init__ as opensecurity
    56 
    57 
    58 # ------------------------------------------------------------
    59 # const
    60 
    61 
    62 """All the URLs we know mapping to class handler"""
    63 opensecurity_urls = (
    64     '/credentials',             'os_credentials',
    65     '/keyfile',                 'os_keyfile',
    66     '/log',                     'os_log',
    67     '/notification',            'os_notification',
    68     '/password',                'os_password',
    69     '/netmount',                'os_netmount',
    70     '/netumount',               'os_netumount',
    71     '/',                        'os_root'
    72 )
    73 
    74 
    75 # ------------------------------------------------------------
    76 # vars
    77 
    78 
    79 """The REST server object"""
    80 server = None
    81 
    82 
    83 # ------------------------------------------------------------
    84 # code
    85 
    86 
    87 class os_credentials:
    88 
    89     """OpenSecurity '/credentials' handler.
    90     
    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.
    94     """
    95     
    96     def GET(self):
    97         
    98         # pick the arguments
    99         args = web.input()
   100         
   101         # we _need_ a text
   102         if not "text" in args:
   103             raise web.badrequest('no text given')
   104         
   105         # remember remote ip
   106         remote_ip = web.ctx.environ['REMOTE_ADDR']
   107 
   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)        
   112         
   113         # run process result handling in seprate thread (not to block main one)
   114         bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
   115         bouncer.start()
   116          
   117         return 'user queried for credentials'
   118 
   119 
   120 class os_keyfile:
   121 
   122     """OpenSecurity '/keyfile' handler.
   123     
   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.
   127     """
   128     
   129     def GET(self):
   130         
   131         # pick the arguments
   132         args = web.input()
   133         
   134         # we _need_ a text
   135         if not "text" in args:
   136             raise web.badrequest('no text given')
   137             
   138         # remember remote ip
   139         remote_ip = web.ctx.environ['REMOTE_ADDR']
   140         
   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)        
   145         
   146         # run process result handling in seprate thread (not to block main one)
   147         bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
   148         bouncer.start()
   149          
   150         return 'user queried for password and keyfile'
   151 
   152 
   153 class os_log:
   154 
   155     """OpenSecurity '/log' handler.
   156     
   157     This is called on GET or POST on the log function /log
   158     """
   159     
   160     def GET(self):
   161         
   162         # pick the arguments
   163         self.POST()
   164 
   165 
   166     def POST(self):
   167         
   168         # pick the arguments
   169         args = web.input()
   170         args['user'] = getpass.getuser()
   171         args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
   172 
   173         # bounce log data
   174         url_addr = 'http://GIMME-SERVER-TO-LOG-TO/log'
   175 
   176         # by provided a 'data' we turn this into a POST statement
   177         d = urllib.urlencode(args)
   178         req = urllib2.Request(url_addr, d)
   179         try:
   180             res = urllib2.urlopen(req)
   181         except:
   182             print('failed to contact: ' + url_addr)
   183             print('log data: ' + d)
   184             return "Failed"
   185          
   186         return "Ok"
   187 
   188 
   189 class os_notification:
   190 
   191     """OpenSecurity '/notification' handler.
   192     
   193     This is called on GET /notification?msgtype=TYPE&text=TEXT.
   194     This will pop up an OpenSecurity notifcation window
   195     """
   196     
   197     def GET(self):
   198         
   199         # pick the arguments
   200         args = web.input()
   201         
   202         # we _need_ a type
   203         if not "msgtype" in args:
   204             raise web.badrequest('no msgtype given')
   205             
   206         if not args.msgtype in ['information', 'warning', 'critical']:
   207             raise web.badrequest('Unknown value for msgtype')
   208             
   209         # we _need_ a text
   210         if not "text" in args:
   211             raise web.badrequest('no text given')
   212             
   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)
   217 
   218         return "Ok"
   219 
   220 
   221 class os_password:
   222 
   223     """OpenSecurity '/password' handler.
   224     
   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.
   228     """
   229     
   230     def GET(self):
   231         
   232         # pick the arguments
   233         args = web.input()
   234         
   235         # we _need_ a text
   236         if not "text" in args:
   237             raise web.badrequest('no text given')
   238             
   239         # remember remote ip
   240         remote_ip = web.ctx.environ['REMOTE_ADDR']
   241         
   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)        
   246         
   247         # run process result handling in seprate thread (not to block main one)
   248         bouncer = ProcessResultBouncer(process, remote_ip, '/password')
   249         bouncer.start()
   250         
   251         return 'user queried for password'
   252 
   253 # handles netumount request                    
   254 class MountNetworkDriveHandler(threading.Thread): 
   255     drive = None
   256     resource = None
   257     
   258     def __init__(self, drv, net_path):
   259         threading.Thread.__init__(self)
   260         self.drive = drv
   261         self.networkPath = net_path
   262     
   263     def run(self):
   264         #Check for drive availability
   265         if os.path.exists(self.drive):
   266             logger.error("Drive letter is already in use: " + self.drive)
   267             return 1
   268         
   269         #Check for network resource availability
   270         retry = 5
   271         while not os.path.exists(self.networkPath):
   272             time.sleep(1)
   273             if retry == 0:
   274                 return 1
   275             logger.info("Path not accessible: " + self.networkPath + " retrying")
   276             retry-=1
   277     
   278         command = 'USE ' + self.drive + ' ' + self.networkPath + ' /PERSISTENT:NO'
   279     
   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)
   283             return 1
   284         return 0
   285 
   286 class os_netmount:
   287     
   288     """OpenSecurity '/netmount' handler"""
   289     
   290     def GET(self):
   291         # pick the arguments
   292         args = web.input()
   293         
   294         # we _need_ a net_resource
   295         if not "net_resource" in args:
   296             raise web.badrequest('no net_resource given')
   297         
   298         # we _need_ a drive_letter
   299         if not "drive_letter" in args:
   300             raise web.badrequest('no drive_letter given')
   301 
   302         driveHandler = MountNetworkDriveHandler(args['drive_letter'], args['net_resource'])
   303         driveHandler.start()
   304         driveHandler.join(None)
   305         return 'Ok'
   306 
   307          
   308         
   309 # handles netumount request                    
   310 class UmountNetworkDriveHandler(threading.Thread): 
   311     drive = None
   312     running = True
   313     
   314     def __init__(self, drv):
   315         threading.Thread.__init__(self)
   316         self.drive = drv
   317 
   318     def run(self):
   319         while self.running:
   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:
   324                     parts = line.split()
   325                     mappedDrives.append(parts[1])
   326             
   327             logger.info(mappedDrives)
   328             logger.info(self.drive)
   329             if self.drive not in mappedDrives:
   330                 self.running = False
   331             else:
   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])
   336                     continue
   337                         
   338 
   339 class os_netumount:
   340     
   341     """OpenSecurity '/netumount' handler"""
   342     
   343     def GET(self):
   344         # pick the arguments
   345         args = web.input()
   346         
   347         # we _need_ a drive_letter
   348         if not "drive_letter" in args:
   349             raise web.badrequest('no drive_letter given')
   350         
   351         driveHandler = UmountNetworkDriveHandler(args['drive_letter'])
   352         driveHandler.start()
   353         driveHandler.join(None)
   354         return 'Ok'
   355     
   356 
   357 class os_root:
   358 
   359     """OpenSecurity '/' handler"""
   360     
   361     def GET(self):
   362     
   363         res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
   364         
   365         # add some sample links
   366         res = res + """
   367         
   368 USAGE EXAMPLES:
   369         
   370 Request a password: 
   371     (copy paste this into your browser's address field after the host:port)
   372     
   373     /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
   374     
   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.
   377     
   378     
   379 Request a combination of user and password:
   380     (copy paste this into your browser's address field after the host:port)
   381     
   382     /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
   383     
   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.
   386     
   387 
   388 Request a combination of password and keyfile:
   389     (copy paste this into your browser's address field after the host:port)
   390     
   391     /keyfile?text=Your%20private%20RSA%20Keyfile%3A
   392     
   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.
   395     
   396 
   397 Start a Browser:
   398     (copy paste this into your browser's address field after the host:port)
   399 
   400     /application?vm=Debian+7&app=Browser
   401 
   402     (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
   403         """
   404     
   405         return res
   406 
   407 
   408 class ProcessResultBouncer(threading.Thread):
   409 
   410     """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
   411 
   412     def __init__(self, process, remote_ip, resource): 
   413 
   414         """ctor"""
   415 
   416         threading.Thread.__init__(self)
   417         self._process = process
   418         self._remote_ip = remote_ip
   419         self._resource = resource
   420  
   421     
   422     def stop(self):
   423 
   424         """stop thread"""
   425         self.running = False
   426         
   427     
   428     def run(self):
   429 
   430         """run the thread"""
   431 
   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.'
   436             return
   437         
   438         # all ok, tell send request back appropriate destination
   439         try:
   440             j = json.loads(result)
   441         except:
   442             print 'error in password parsing'
   443             return
   444         
   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
   448 
   449         # by provided a 'data' we turn this into a POST statement
   450         req = urllib2.Request(url_addr, urllib.urlencode(j))
   451         try:
   452             res = urllib2.urlopen(req)
   453         except:
   454             print 'failed to contact: ' + url_addr
   455             return 
   456 
   457 
   458 class RESTServerThread(threading.Thread):
   459 
   460     """Thread for serving the REST API."""
   461 
   462     def __init__(self, port): 
   463 
   464         """ctor"""
   465         threading.Thread.__init__(self)
   466         self._port = port 
   467     
   468     def stop(self):
   469 
   470         """stop thread"""
   471         self.running = False
   472         
   473     
   474     def run(self):
   475 
   476         """run the thread"""
   477         _serve(self._port)
   478 
   479 
   480 
   481 def is_already_running(port = 8090):
   482 
   483     """check if this is started twice"""
   484 
   485     try:
   486         s = socket.create_connection(('127.0.0.1', port), 0.5)
   487     except:
   488         return False
   489 
   490     return True
   491 
   492 
   493 def _serve(port):
   494 
   495     """Start the REST server"""
   496 
   497     global server
   498 
   499     # trick the web.py server 
   500     sys.argv = [__file__, str(port)]
   501     server = web.application(opensecurity_urls, globals())
   502     server.run()
   503 
   504 
   505 def serve(port = 8090, background = False):
   506 
   507     """Start serving the REST Api
   508     port ... port number to listen on
   509     background ... cease into background (spawn thread) and return immediately"""
   510 
   511     # start threaded or direct version
   512     if background == True:
   513         t = RESTServerThread(port)
   514         t.start()
   515     else:
   516         _serve(port)
   517 
   518 def stop():
   519 
   520     """Stop serving the REST Api"""
   521 
   522     global server
   523     if server is None:
   524         return
   525 
   526     server.stop()
   527 
   528 
   529 # start
   530 if __name__ == "__main__":
   531     serve()
   532