OpenSecurity/bin/opensecurity_client_restful_server.py
changeset 167 1e1811fa44bc
parent 164 b6b9dc0ed2ac
child 168 76267df09d71
     1.1 --- a/OpenSecurity/bin/opensecurity_client_restful_server.py	Tue May 20 11:21:48 2014 +0200
     1.2 +++ b/OpenSecurity/bin/opensecurity_client_restful_server.py	Thu May 22 11:00:33 2014 +0200
     1.3 @@ -1,621 +1,623 @@
     1.4 -#!/bin/env python
     1.5 -# -*- coding: utf-8 -*-
     1.6 -
     1.7 -# ------------------------------------------------------------
     1.8 -# opensecurity_client_restful_server
     1.9 -# 
    1.10 -# the OpenSecurity client RESTful server
    1.11 -#
    1.12 -# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    1.13 -#
    1.14 -# Copyright (C) 2013 AIT Austrian Institute of Technology
    1.15 -# AIT Austrian Institute of Technology GmbH
    1.16 -# Donau-City-Strasse 1 | 1220 Vienna | Austria
    1.17 -# http://www.ait.ac.at
    1.18 -#
    1.19 -# This program is free software; you can redistribute it and/or
    1.20 -# modify it under the terms of the GNU General Public License
    1.21 -# as published by the Free Software Foundation version 2.
    1.22 -# 
    1.23 -# This program is distributed in the hope that it will be useful,
    1.24 -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.25 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.26 -# GNU General Public License for more details.
    1.27 -# 
    1.28 -# You should have received a copy of the GNU General Public License
    1.29 -# along with this program; if not, write to the Free Software
    1.30 -# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    1.31 -# Boston, MA  02110-1301, USA.
    1.32 -# ------------------------------------------------------------
    1.33 -
    1.34 -
    1.35 -# ------------------------------------------------------------
    1.36 -# imports
    1.37 -
    1.38 -import getpass
    1.39 -import glob
    1.40 -import json
    1.41 -import os
    1.42 -import os.path
    1.43 -import pickle
    1.44 -import platform
    1.45 -import socket
    1.46 -import subprocess
    1.47 -import sys
    1.48 -import threading
    1.49 -import time
    1.50 -import urllib
    1.51 -import urllib2
    1.52 -import web
    1.53 -import threading
    1.54 -import time
    1.55 -import string
    1.56 -import win32api
    1.57 -import win32con
    1.58 -
    1.59 -from opensecurity_util import logger, setupLogger, OpenSecurityException
    1.60 -if sys.platform == 'win32' or sys.platform == 'cygwin':
    1.61 -    from cygwin import Cygwin
    1.62 -
    1.63 -# local
    1.64 -import __init__ as opensecurity
    1.65 -from environment import Environment
    1.66 -
    1.67 -
    1.68 -# ------------------------------------------------------------
    1.69 -# const
    1.70 -
    1.71 -
    1.72 -"""All the URLs we know mapping to class handler"""
    1.73 -opensecurity_urls = (
    1.74 -    '/credentials',             'os_credentials',
    1.75 -    '/keyfile',                 'os_keyfile',
    1.76 -    '/log',                     'os_log',
    1.77 -    '/notification',            'os_notification',
    1.78 -    '/password',                'os_password',
    1.79 -    '/netmount',                'os_netmount',
    1.80 -    '/netumount',               'os_netumount',
    1.81 -    '/',                        'os_root'
    1.82 -)
    1.83 -
    1.84 -
    1.85 -# ------------------------------------------------------------
    1.86 -# vars
    1.87 -
    1.88 -
    1.89 -"""lock for read/write log file"""
    1.90 -log_file_lock = threading.Lock()
    1.91 -
    1.92 -"""timer for the log file bouncer"""
    1.93 -log_file_bouncer = None
    1.94 -
    1.95 -
    1.96 -"""The REST server object"""
    1.97 -server = None
    1.98 -
    1.99 -
   1.100 -# ------------------------------------------------------------
   1.101 -# code
   1.102 -
   1.103 -
   1.104 -class os_credentials:
   1.105 -
   1.106 -    """OpenSecurity '/credentials' handler.
   1.107 -    
   1.108 -    This is called on GET /credentials?text=TEXT.
   1.109 -    Ideally this should pop up a user dialog to insert his
   1.110 -    credentials based the given TEXT.
   1.111 -    """
   1.112 -    
   1.113 -    def GET(self):
   1.114 -        
   1.115 -        # pick the arguments
   1.116 -        args = web.input()
   1.117 -        
   1.118 -        # we _need_ a text
   1.119 -        if not "text" in args:
   1.120 -            raise web.badrequest('no text given')
   1.121 -        
   1.122 -        # remember remote ip
   1.123 -        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.124 -
   1.125 -        # create the process which queries the user
   1.126 -        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.127 -        process_command = [sys.executable, dlg_image, 'credentials', args.text]
   1.128 -        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.129 -        
   1.130 -        # run process result handling in seprate thread (not to block main one)
   1.131 -        bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
   1.132 -        bouncer.start()
   1.133 -         
   1.134 -        return 'user queried for credentials'
   1.135 -
   1.136 -
   1.137 -class os_keyfile:
   1.138 -
   1.139 -    """OpenSecurity '/keyfile' handler.
   1.140 -    
   1.141 -    This is called on GET /keyfile?text=TEXT.
   1.142 -    Ideally this should pop up a user dialog to insert his
   1.143 -    password along with a keyfile.
   1.144 -    """
   1.145 -    
   1.146 -    def GET(self):
   1.147 -        
   1.148 -        # pick the arguments
   1.149 -        args = web.input()
   1.150 -        
   1.151 -        # we _need_ a text
   1.152 -        if not "text" in args:
   1.153 -            raise web.badrequest('no text given')
   1.154 -            
   1.155 -        # remember remote ip
   1.156 -        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.157 -        
   1.158 -        # create the process which queries the user
   1.159 -        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.160 -        process_command = [sys.executable, dlg_image, 'keyfile', args.text]
   1.161 -        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.162 -        
   1.163 -        # run process result handling in seprate thread (not to block main one)
   1.164 -        bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
   1.165 -        bouncer.start()
   1.166 -         
   1.167 -        return 'user queried for password and keyfile'
   1.168 -
   1.169 -
   1.170 -class os_log:
   1.171 -
   1.172 -    """OpenSecurity '/log' handler.
   1.173 -    
   1.174 -    This is called on GET or POST on the log function /log
   1.175 -    """
   1.176 -    
   1.177 -    def GET(self):
   1.178 -        
   1.179 -        # pick the arguments
   1.180 -        self.POST()
   1.181 -
   1.182 -
   1.183 -    def POST(self):
   1.184 -        
   1.185 -        # pick the arguments
   1.186 -        args = web.input()
   1.187 -        args['user'] = getpass.getuser()
   1.188 -        args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
   1.189 -
   1.190 -        # add these to new data to log
   1.191 -        global log_file_lock
   1.192 -        log_file_name = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
   1.193 -        log_file_lock.acquire()
   1.194 -        pickle.dump(args,  open(log_file_name, 'ab'))
   1.195 -        log_file_lock.release()
   1.196 -
   1.197 -        return "Ok"
   1.198 -
   1.199 -
   1.200 -class os_notification:
   1.201 -
   1.202 -    """OpenSecurity '/notification' handler.
   1.203 -    
   1.204 -    This is called on GET /notification?msgtype=TYPE&text=TEXT.
   1.205 -    This will pop up an OpenSecurity notifcation window
   1.206 -    """
   1.207 -    
   1.208 -    def GET(self):
   1.209 -        
   1.210 -        # pick the arguments
   1.211 -        args = web.input()
   1.212 -        
   1.213 -        # we _need_ a type
   1.214 -        if not "msgtype" in args:
   1.215 -            raise web.badrequest('no msgtype given')
   1.216 -            
   1.217 -        if not args.msgtype in ['information', 'warning', 'critical']:
   1.218 -            raise web.badrequest('Unknown value for msgtype')
   1.219 -            
   1.220 -        # we _need_ a text
   1.221 -        if not "text" in args:
   1.222 -            raise web.badrequest('no text given')
   1.223 -            
   1.224 -        # invoke the user dialog as a subprocess
   1.225 -        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   1.226 -        process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
   1.227 -        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   1.228 -
   1.229 -        return "Ok"
   1.230 -
   1.231 -
   1.232 -class os_password:
   1.233 -
   1.234 -    """OpenSecurity '/password' handler.
   1.235 -    
   1.236 -    This is called on GET /password?text=TEXT.
   1.237 -    Ideally this should pop up a user dialog to insert his
   1.238 -    password based device name.
   1.239 -    """
   1.240 -    
   1.241 -    def GET(self):
   1.242 -        
   1.243 -        # pick the arguments
   1.244 -        args = web.input()
   1.245 -        
   1.246 -        # we _need_ a text
   1.247 -        if not "text" in args:
   1.248 -            raise web.badrequest('no text given')
   1.249 -            
   1.250 -        # remember remote ip
   1.251 -        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.252 -        
   1.253 -        # create the process which queries the user
   1.254 -        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.255 -        process_command = [sys.executable, dlg_image, 'password', args.text]
   1.256 -        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.257 -        
   1.258 -        # run process result handling in seprate thread (not to block main one)
   1.259 -        bouncer = ProcessResultBouncer(process, remote_ip, '/password')
   1.260 -        bouncer.start()
   1.261 -        
   1.262 -        return 'user queried for password'
   1.263 -
   1.264 -# handles netumount request                    
   1.265 -class MountNetworkDriveHandler(threading.Thread): 
   1.266 -    drive = None
   1.267 -    resource = None
   1.268 -    
   1.269 -    def __init__(self, drv, net_path):
   1.270 -        threading.Thread.__init__(self)
   1.271 -        self.drive = drv
   1.272 -        self.networkPath = net_path
   1.273 -    
   1.274 -    def run(self):
   1.275 -        #Check for drive availability
   1.276 -        if os.path.exists(self.drive):
   1.277 -            logger.error("Drive letter is already in use: " + self.drive)
   1.278 -            return 1
   1.279 -        
   1.280 -        #Check for network resource availability
   1.281 -        retry = 5
   1.282 -        while not os.path.exists(self.networkPath):
   1.283 -            time.sleep(1)
   1.284 -            if retry == 0:
   1.285 -                return 1
   1.286 -            logger.info("Path not accessible: " + self.networkPath + " retrying")
   1.287 -            retry-=1
   1.288 -    
   1.289 -        command = 'USE ' + self.drive + ' ' + self.networkPath + ' /PERSISTENT:NO'
   1.290 -    
   1.291 -        result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\NET', command))
   1.292 -        if string.find(result[1], 'successfully',) == -1:
   1.293 -            logger.error("Failed: NET " + command)
   1.294 -            return 1
   1.295 -        return 0
   1.296 -
   1.297 -class os_netmount:
   1.298 -    
   1.299 -    """OpenSecurity '/netmount' handler"""
   1.300 -    
   1.301 -    def GET(self):
   1.302 -        # pick the arguments
   1.303 -        args = web.input()
   1.304 -        
   1.305 -        # we _need_ a net_resource
   1.306 -        if not "net_resource" in args:
   1.307 -            raise web.badrequest('no net_resource given')
   1.308 -        
   1.309 -        # we _need_ a drive_letter
   1.310 -        if not "drive_letter" in args:
   1.311 -            raise web.badrequest('no drive_letter given')
   1.312 -
   1.313 -        driveHandler = MountNetworkDriveHandler(args['drive_letter'], args['net_resource'])
   1.314 -        driveHandler.start()
   1.315 -        return 'Ok'
   1.316 -
   1.317 -         
   1.318 -        
   1.319 -# handles netumount request                    
   1.320 -class UmountNetworkDriveHandler(threading.Thread): 
   1.321 -    drive = None
   1.322 -    running = True
   1.323 -    
   1.324 -    def __init__(self, drv):
   1.325 -        threading.Thread.__init__(self)
   1.326 -        self.drive = drv
   1.327 -
   1.328 -    def run(self):
   1.329 -        while self.running:
   1.330 -            result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', 'USE'))
   1.331 -            mappedDrives = list()
   1.332 -            for line in result[1].splitlines():
   1.333 -                if 'USB' in line or 'Download' in line:
   1.334 -                    parts = line.split()
   1.335 -                    mappedDrives.append(parts[1])
   1.336 -            
   1.337 -            logger.info(mappedDrives)
   1.338 -            logger.info(self.drive)
   1.339 -            if self.drive not in mappedDrives:
   1.340 -                self.running = False
   1.341 -            else:
   1.342 -                command = 'USE ' + self.drive + ' /DELETE /YES' 
   1.343 -                result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', command)) 
   1.344 -                if string.find(str(result[1]), 'successfully',) == -1:
   1.345 -                    logger.error(result[2])
   1.346 -                    continue
   1.347 -                        
   1.348 -
   1.349 -class os_netumount:
   1.350 -    
   1.351 -    """OpenSecurity '/netumount' handler"""
   1.352 -    
   1.353 -    def GET(self):
   1.354 -        # pick the arguments
   1.355 -        args = web.input()
   1.356 -        
   1.357 -        # we _need_ a drive_letter
   1.358 -        if not "drive_letter" in args:
   1.359 -            raise web.badrequest('no drive_letter given')
   1.360 -        
   1.361 -        driveHandler = UmountNetworkDriveHandler(args['drive_letter'])
   1.362 -        driveHandler.start()
   1.363 -        return 'Ok'
   1.364 -    
   1.365 -
   1.366 -class os_root:
   1.367 -
   1.368 -    """OpenSecurity '/' handler"""
   1.369 -    
   1.370 -    def GET(self):
   1.371 -    
   1.372 -        res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
   1.373 -        
   1.374 -        # add some sample links
   1.375 -        res = res + """
   1.376 -        
   1.377 -USAGE EXAMPLES:
   1.378 -        
   1.379 -Request a password: 
   1.380 -    (copy paste this into your browser's address field after the host:port)
   1.381 -    
   1.382 -    /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
   1.383 -    
   1.384 -    (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
   1.385 -    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   1.386 -    
   1.387 -    
   1.388 -Request a combination of user and password:
   1.389 -    (copy paste this into your browser's address field after the host:port)
   1.390 -    
   1.391 -    /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
   1.392 -    
   1.393 -    (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
   1.394 -    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   1.395 -    
   1.396 -
   1.397 -Request a combination of password and keyfile:
   1.398 -    (copy paste this into your browser's address field after the host:port)
   1.399 -    
   1.400 -    /keyfile?text=Your%20private%20RSA%20Keyfile%3A
   1.401 -    
   1.402 -    (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
   1.403 -    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   1.404 -    
   1.405 -
   1.406 -Start a Browser:
   1.407 -    (copy paste this into your browser's address field after the host:port)
   1.408 -
   1.409 -    /application?vm=Debian+7&app=Browser
   1.410 -
   1.411 -    (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
   1.412 -        """
   1.413 -    
   1.414 -        return res
   1.415 -
   1.416 -
   1.417 -class ProcessResultBouncer(threading.Thread):
   1.418 -
   1.419 -    """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
   1.420 -
   1.421 -    def __init__(self, process, remote_ip, resource): 
   1.422 -
   1.423 -        """ctor"""
   1.424 -
   1.425 -        threading.Thread.__init__(self)
   1.426 -        self._process = process
   1.427 -        self._remote_ip = remote_ip
   1.428 -        self._resource = resource
   1.429 - 
   1.430 -    
   1.431 -    def stop(self):
   1.432 -
   1.433 -        """stop thread"""
   1.434 -        self.running = False
   1.435 -        
   1.436 -    
   1.437 -    def run(self):
   1.438 -
   1.439 -        """run the thread"""
   1.440 -
   1.441 -        # invoke the user dialog as a subprocess
   1.442 -        result = self._process.communicate()[0]
   1.443 -        if self._process.returncode != 0:
   1.444 -            print 'user request has been aborted.'
   1.445 -            return
   1.446 -        
   1.447 -        # all ok, tell send request back appropriate destination
   1.448 -        try:
   1.449 -            j = json.loads(result)
   1.450 -        except:
   1.451 -            print 'error in password parsing'
   1.452 -            return
   1.453 -        
   1.454 -        # by provided a 'data' we turn this into a POST statement
   1.455 -        url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
   1.456 -        req = urllib2.Request(url_addr, urllib.urlencode(j))
   1.457 -        try:
   1.458 -            res = urllib2.urlopen(req)
   1.459 -        except:
   1.460 -            print 'failed to contact: ' + url_addr
   1.461 -            return 
   1.462 -
   1.463 -
   1.464 -class RESTServerThread(threading.Thread):
   1.465 -
   1.466 -    """Thread for serving the REST API."""
   1.467 -
   1.468 -    def __init__(self, port): 
   1.469 -
   1.470 -        """ctor"""
   1.471 -        threading.Thread.__init__(self)
   1.472 -        self._port = port 
   1.473 -    
   1.474 -    def stop(self):
   1.475 -
   1.476 -        """stop thread"""
   1.477 -        self.running = False
   1.478 -        
   1.479 -    
   1.480 -    def run(self):
   1.481 -
   1.482 -        """run the thread"""
   1.483 -        _serve(self._port)
   1.484 -
   1.485 -
   1.486 -
   1.487 -def is_already_running(port = 8090):
   1.488 -
   1.489 -    """check if this is started twice"""
   1.490 -
   1.491 -    try:
   1.492 -        s = socket.create_connection(('127.0.0.1', port), 0.5)
   1.493 -    except:
   1.494 -        return False
   1.495 -
   1.496 -    return True
   1.497 -
   1.498 -
   1.499 -def _bounce_vm_logs():
   1.500 -
   1.501 -    """grab all logs from the VMs and push them to the log servers"""
   1.502 -
   1.503 -    global log_file_lock
   1.504 -
   1.505 -    # pick the highest current number
   1.506 -    cur = 0
   1.507 -    for f in glob.iglob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*')):
   1.508 -        try:
   1.509 -            n = f.split('.')[-1:][0]
   1.510 -            if cur < int(n):
   1.511 -                cur = int(n)
   1.512 -        except:
   1.513 -            pass
   1.514 -
   1.515 -    cur = cur + 1
   1.516 -
   1.517 -    # first add new vm logs to our existing one: rename the log file
   1.518 -    log_file_name_new = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
   1.519 -    log_file_name_cur = os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.' + str(cur))
   1.520 -    log_file_lock.acquire()
   1.521 -    try:
   1.522 -        os.rename(log_file_name_new, log_file_name_cur)
   1.523 -        print('new log file: ' + log_file_name_cur)
   1.524 -    except:
   1.525 -        pass
   1.526 -    log_file_lock.release()
   1.527 -
   1.528 -    # now we have a list of next log files to dump
   1.529 -    log_files = glob.glob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*'))
   1.530 -    log_files.sort()
   1.531 -    for log_file in log_files:
   1.532 -
   1.533 -        try:
   1.534 -            f = open(log_file, 'rb')
   1.535 -            while True:
   1.536 -                l = pickle.load(f)
   1.537 -                _push_log(l)
   1.538 -
   1.539 -        except EOFError:
   1.540 -
   1.541 -            try:
   1.542 -                os.remove(log_file)
   1.543 -            except:
   1.544 -                logger.warning('tried to delete log file (pushed to EOF) "' + log_file + '" but failed')
   1.545 -
   1.546 -        except:
   1.547 -            logger.warning('encountered error while pushing log file "' + log_file + '"')
   1.548 -            break
   1.549 -
   1.550 -    # start bouncer again ...
   1.551 -    global log_file_bouncer
   1.552 -    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
   1.553 -    log_file_bouncer.start()
   1.554 -
   1.555 -
   1.556 -def _push_log(log):
   1.557 -    """POST a single log to log server
   1.558 -
   1.559 -    @param  log     the log POST param
   1.560 -    """
   1.561 -
   1.562 -    try:
   1.563 -        key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\OpenSecurity')
   1.564 -        log_server_url = str(win32api.RegQueryValueEx(key, 'LogServerURL')[0])
   1.565 -        win32api.RegCloseKey(key)
   1.566 -    except:
   1.567 -        logger.critical('Cannot open Registry HKEY_LOCAL_MACHINE\SOFTWARE\OpenSecurity and get LogServerURL value')
   1.568 -        raise
   1.569 -
   1.570 -    # by provided a 'data' we turn this into a POST statement
   1.571 -    d = urllib.urlencode(log)
   1.572 -    req = urllib2.Request(log_server_url, d)
   1.573 -    urllib2.urlopen(req)
   1.574 -    logger.debug('pushed log to server: ' + str(log_server_url))
   1.575 -
   1.576 -
   1.577 -def _serve(port):
   1.578 -
   1.579 -    """Start the REST server"""
   1.580 -
   1.581 -    global server
   1.582 -
   1.583 -    # start the VM-log bouncer timer
   1.584 -    global log_file_bouncer
   1.585 -    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
   1.586 -    log_file_bouncer.start()
   1.587 -
   1.588 -    # trick the web.py server 
   1.589 -    sys.argv = [__file__, str(port)]
   1.590 -    server = web.application(opensecurity_urls, globals())
   1.591 -    server.run()
   1.592 -
   1.593 -
   1.594 -def serve(port = 8090, background = False):
   1.595 -
   1.596 -    """Start serving the REST Api
   1.597 -    port ... port number to listen on
   1.598 -    background ... cease into background (spawn thread) and return immediately"""
   1.599 -
   1.600 -    # start threaded or direct version
   1.601 -    if background == True:
   1.602 -        t = RESTServerThread(port)
   1.603 -        t.start()
   1.604 -    else:
   1.605 -        _serve(port)
   1.606 -
   1.607 -def stop():
   1.608 -
   1.609 -    """Stop serving the REST Api"""
   1.610 -
   1.611 -    global server
   1.612 -    if server is None:
   1.613 -        return
   1.614 -
   1.615 -    global log_file_bouncer
   1.616 -    if log_file_bouncer is not None:
   1.617 -        log_file_bouncer.cancel()
   1.618 -
   1.619 -    server.stop()
   1.620 -
   1.621 -# start
   1.622 -if __name__ == "__main__":
   1.623 -    serve()
   1.624 -
   1.625 +#!/bin/env python
   1.626 +# -*- coding: utf-8 -*-
   1.627 +
   1.628 +# ------------------------------------------------------------
   1.629 +# opensecurity_client_restful_server
   1.630 +# 
   1.631 +# the OpenSecurity client RESTful server
   1.632 +#
   1.633 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   1.634 +#
   1.635 +# Copyright (C) 2013 AIT Austrian Institute of Technology
   1.636 +# AIT Austrian Institute of Technology GmbH
   1.637 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
   1.638 +# http://www.ait.ac.at
   1.639 +#
   1.640 +# This program is free software; you can redistribute it and/or
   1.641 +# modify it under the terms of the GNU General Public License
   1.642 +# as published by the Free Software Foundation version 2.
   1.643 +# 
   1.644 +# This program is distributed in the hope that it will be useful,
   1.645 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   1.646 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   1.647 +# GNU General Public License for more details.
   1.648 +# 
   1.649 +# You should have received a copy of the GNU General Public License
   1.650 +# along with this program; if not, write to the Free Software
   1.651 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
   1.652 +# Boston, MA  02110-1301, USA.
   1.653 +# ------------------------------------------------------------
   1.654 +
   1.655 +
   1.656 +# ------------------------------------------------------------
   1.657 +# imports
   1.658 +
   1.659 +import getpass
   1.660 +import glob
   1.661 +import json
   1.662 +import os
   1.663 +import os.path
   1.664 +import pickle
   1.665 +import platform
   1.666 +import socket
   1.667 +import subprocess
   1.668 +import sys
   1.669 +import threading
   1.670 +import time
   1.671 +import urllib
   1.672 +import urllib2
   1.673 +import web
   1.674 +import threading
   1.675 +import time
   1.676 +import string
   1.677 +import win32api
   1.678 +import win32con
   1.679 +
   1.680 +from opensecurity_util import logger, setupLogger, OpenSecurityException
   1.681 +if sys.platform == 'win32' or sys.platform == 'cygwin':
   1.682 +    from cygwin import Cygwin
   1.683 +
   1.684 +# local
   1.685 +import __init__ as opensecurity
   1.686 +from environment import Environment
   1.687 +
   1.688 +
   1.689 +# ------------------------------------------------------------
   1.690 +# const
   1.691 +
   1.692 +
   1.693 +"""All the URLs we know mapping to class handler"""
   1.694 +opensecurity_urls = (
   1.695 +    '/credentials',             'os_credentials',
   1.696 +    '/keyfile',                 'os_keyfile',
   1.697 +    '/log',                     'os_log',
   1.698 +    '/notification',            'os_notification',
   1.699 +    '/password',                'os_password',
   1.700 +    '/netmount',                'os_netmount',
   1.701 +    '/netumount',               'os_netumount',
   1.702 +    '/',                        'os_root'
   1.703 +)
   1.704 +
   1.705 +
   1.706 +# ------------------------------------------------------------
   1.707 +# vars
   1.708 +
   1.709 +
   1.710 +"""lock for read/write log file"""
   1.711 +log_file_lock = threading.Lock()
   1.712 +
   1.713 +"""timer for the log file bouncer"""
   1.714 +log_file_bouncer = None
   1.715 +
   1.716 +
   1.717 +"""The REST server object"""
   1.718 +server = None
   1.719 +
   1.720 +
   1.721 +# ------------------------------------------------------------
   1.722 +# code
   1.723 +
   1.724 +
   1.725 +class os_credentials:
   1.726 +
   1.727 +    """OpenSecurity '/credentials' handler.
   1.728 +    
   1.729 +    This is called on GET /credentials?text=TEXT.
   1.730 +    Ideally this should pop up a user dialog to insert his
   1.731 +    credentials based the given TEXT.
   1.732 +    """
   1.733 +    
   1.734 +    def GET(self):
   1.735 +        
   1.736 +        # pick the arguments
   1.737 +        args = web.input()
   1.738 +        
   1.739 +        # we _need_ a text
   1.740 +        if not "text" in args:
   1.741 +            raise web.badrequest('no text given')
   1.742 +        
   1.743 +        # remember remote ip
   1.744 +        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.745 +
   1.746 +        # create the process which queries the user
   1.747 +        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.748 +        process_command = [sys.executable, dlg_image, 'credentials', args.text]
   1.749 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.750 +        
   1.751 +        # run process result handling in seprate thread (not to block main one)
   1.752 +        bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
   1.753 +        bouncer.start()
   1.754 +         
   1.755 +        return 'user queried for credentials'
   1.756 +
   1.757 +
   1.758 +class os_keyfile:
   1.759 +
   1.760 +    """OpenSecurity '/keyfile' handler.
   1.761 +    
   1.762 +    This is called on GET /keyfile?text=TEXT.
   1.763 +    Ideally this should pop up a user dialog to insert his
   1.764 +    password along with a keyfile.
   1.765 +    """
   1.766 +    
   1.767 +    def GET(self):
   1.768 +        
   1.769 +        # pick the arguments
   1.770 +        args = web.input()
   1.771 +        
   1.772 +        # we _need_ a text
   1.773 +        if not "text" in args:
   1.774 +            raise web.badrequest('no text given')
   1.775 +            
   1.776 +        # remember remote ip
   1.777 +        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.778 +        
   1.779 +        # create the process which queries the user
   1.780 +        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.781 +        process_command = [sys.executable, dlg_image, 'keyfile', args.text]
   1.782 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.783 +        
   1.784 +        # run process result handling in seprate thread (not to block main one)
   1.785 +        bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
   1.786 +        bouncer.start()
   1.787 +         
   1.788 +        return 'user queried for password and keyfile'
   1.789 +
   1.790 +
   1.791 +class os_log:
   1.792 +
   1.793 +    """OpenSecurity '/log' handler.
   1.794 +    
   1.795 +    This is called on GET or POST on the log function /log
   1.796 +    """
   1.797 +    
   1.798 +    def GET(self):
   1.799 +        
   1.800 +        # pick the arguments
   1.801 +        self.POST()
   1.802 +
   1.803 +
   1.804 +    def POST(self):
   1.805 +        
   1.806 +        # pick the arguments
   1.807 +        args = web.input()
   1.808 +        args['user'] = getpass.getuser()
   1.809 +        args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
   1.810 +
   1.811 +        # add these to new data to log
   1.812 +        global log_file_lock
   1.813 +        log_file_name = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
   1.814 +        log_file_lock.acquire()
   1.815 +        pickle.dump(args,  open(log_file_name, 'ab'))
   1.816 +        log_file_lock.release()
   1.817 +
   1.818 +        return "Ok"
   1.819 +
   1.820 +
   1.821 +class os_notification:
   1.822 +
   1.823 +    """OpenSecurity '/notification' handler.
   1.824 +    
   1.825 +    This is called on GET /notification?msgtype=TYPE&text=TEXT.
   1.826 +    This will pop up an OpenSecurity notifcation window
   1.827 +    """
   1.828 +    
   1.829 +    def GET(self):
   1.830 +        
   1.831 +        # pick the arguments
   1.832 +        args = web.input()
   1.833 +        
   1.834 +        # we _need_ a type
   1.835 +        if not "msgtype" in args:
   1.836 +            raise web.badrequest('no msgtype given')
   1.837 +            
   1.838 +        if not args.msgtype in ['information', 'warning', 'critical']:
   1.839 +            raise web.badrequest('Unknown value for msgtype')
   1.840 +            
   1.841 +        # we _need_ a text
   1.842 +        if not "text" in args:
   1.843 +            raise web.badrequest('no text given')
   1.844 +            
   1.845 +        # invoke the user dialog as a subprocess
   1.846 +        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   1.847 +        process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
   1.848 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   1.849 +
   1.850 +        return "Ok"
   1.851 +
   1.852 +
   1.853 +class os_password:
   1.854 +
   1.855 +    """OpenSecurity '/password' handler.
   1.856 +    
   1.857 +    This is called on GET /password?text=TEXT.
   1.858 +    Ideally this should pop up a user dialog to insert his
   1.859 +    password based device name.
   1.860 +    """
   1.861 +    
   1.862 +    def GET(self):
   1.863 +        
   1.864 +        # pick the arguments
   1.865 +        args = web.input()
   1.866 +        
   1.867 +        # we _need_ a text
   1.868 +        if not "text" in args:
   1.869 +            raise web.badrequest('no text given')
   1.870 +            
   1.871 +        # remember remote ip
   1.872 +        remote_ip = web.ctx.environ['REMOTE_ADDR']
   1.873 +        
   1.874 +        # create the process which queries the user
   1.875 +        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   1.876 +        process_command = [sys.executable, dlg_image, 'password', args.text]
   1.877 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   1.878 +        
   1.879 +        # run process result handling in seprate thread (not to block main one)
   1.880 +        bouncer = ProcessResultBouncer(process, remote_ip, '/password')
   1.881 +        bouncer.start()
   1.882 +        
   1.883 +        return 'user queried for password'
   1.884 +
   1.885 +# handles netumount request                    
   1.886 +class MountNetworkDriveHandler(threading.Thread): 
   1.887 +    drive = None
   1.888 +    resource = None
   1.889 +    
   1.890 +    def __init__(self, drv, net_path):
   1.891 +        threading.Thread.__init__(self)
   1.892 +        self.drive = drv
   1.893 +        self.networkPath = net_path
   1.894 +    
   1.895 +    def run(self):
   1.896 +        #Check for drive availability
   1.897 +        if os.path.exists(self.drive):
   1.898 +            logger.error("Drive letter is already in use: " + self.drive)
   1.899 +            return 1
   1.900 +        
   1.901 +        #Check for network resource availability
   1.902 +        retry = 5
   1.903 +        while not os.path.exists(self.networkPath):
   1.904 +            time.sleep(1)
   1.905 +            if retry == 0:
   1.906 +                return 1
   1.907 +            logger.info("Path not accessible: " + self.networkPath + " retrying")
   1.908 +            retry-=1
   1.909 +    
   1.910 +        command = 'USE ' + self.drive + ' ' + self.networkPath + ' /PERSISTENT:NO'
   1.911 +    
   1.912 +        result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\NET', command))
   1.913 +        if string.find(result[1], 'successfully',) == -1:
   1.914 +            logger.error("Failed: NET " + command)
   1.915 +            return 1
   1.916 +        return 0
   1.917 +
   1.918 +class os_netmount:
   1.919 +    
   1.920 +    """OpenSecurity '/netmount' handler"""
   1.921 +    
   1.922 +    def GET(self):
   1.923 +        # pick the arguments
   1.924 +        args = web.input()
   1.925 +        
   1.926 +        # we _need_ a net_resource
   1.927 +        if not "net_resource" in args:
   1.928 +            raise web.badrequest('no net_resource given')
   1.929 +        
   1.930 +        # we _need_ a drive_letter
   1.931 +        if not "drive_letter" in args:
   1.932 +            raise web.badrequest('no drive_letter given')
   1.933 +
   1.934 +        driveHandler = MountNetworkDriveHandler(args['drive_letter'], args['net_resource'])
   1.935 +        driveHandler.start()
   1.936 +        driveHandler.join(None)
   1.937 +        return 'Ok'
   1.938 +
   1.939 +         
   1.940 +        
   1.941 +# handles netumount request                    
   1.942 +class UmountNetworkDriveHandler(threading.Thread): 
   1.943 +    drive = None
   1.944 +    running = True
   1.945 +    
   1.946 +    def __init__(self, drv):
   1.947 +        threading.Thread.__init__(self)
   1.948 +        self.drive = drv
   1.949 +
   1.950 +    def run(self):
   1.951 +        while self.running:
   1.952 +            result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', 'USE'))
   1.953 +            mappedDrives = list()
   1.954 +            for line in result[1].splitlines():
   1.955 +                if 'USB' in line or 'Download' in line:
   1.956 +                    parts = line.split()
   1.957 +                    mappedDrives.append(parts[1])
   1.958 +            
   1.959 +            logger.info(mappedDrives)
   1.960 +            logger.info(self.drive)
   1.961 +            if self.drive not in mappedDrives:
   1.962 +                self.running = False
   1.963 +            else:
   1.964 +                command = 'USE ' + self.drive + ' /DELETE /YES' 
   1.965 +                result = Cygwin.checkResult(Cygwin.execute('C:\\Windows\\system32\\net.exe', command)) 
   1.966 +                if string.find(str(result[1]), 'successfully',) == -1:
   1.967 +                    logger.error(result[2])
   1.968 +                    continue
   1.969 +                        
   1.970 +
   1.971 +class os_netumount:
   1.972 +    
   1.973 +    """OpenSecurity '/netumount' handler"""
   1.974 +    
   1.975 +    def GET(self):
   1.976 +        # pick the arguments
   1.977 +        args = web.input()
   1.978 +        
   1.979 +        # we _need_ a drive_letter
   1.980 +        if not "drive_letter" in args:
   1.981 +            raise web.badrequest('no drive_letter given')
   1.982 +        
   1.983 +        driveHandler = UmountNetworkDriveHandler(args['drive_letter'])
   1.984 +        driveHandler.start()
   1.985 +        driveHandler.join(None)
   1.986 +        return 'Ok'
   1.987 +    
   1.988 +
   1.989 +class os_root:
   1.990 +
   1.991 +    """OpenSecurity '/' handler"""
   1.992 +    
   1.993 +    def GET(self):
   1.994 +    
   1.995 +        res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
   1.996 +        
   1.997 +        # add some sample links
   1.998 +        res = res + """
   1.999 +        
  1.1000 +USAGE EXAMPLES:
  1.1001 +        
  1.1002 +Request a password: 
  1.1003 +    (copy paste this into your browser's address field after the host:port)
  1.1004 +    
  1.1005 +    /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
  1.1006 +    
  1.1007 +    (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
  1.1008 +    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
  1.1009 +    
  1.1010 +    
  1.1011 +Request a combination of user and password:
  1.1012 +    (copy paste this into your browser's address field after the host:port)
  1.1013 +    
  1.1014 +    /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
  1.1015 +    
  1.1016 +    (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
  1.1017 +    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
  1.1018 +    
  1.1019 +
  1.1020 +Request a combination of password and keyfile:
  1.1021 +    (copy paste this into your browser's address field after the host:port)
  1.1022 +    
  1.1023 +    /keyfile?text=Your%20private%20RSA%20Keyfile%3A
  1.1024 +    
  1.1025 +    (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
  1.1026 +    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
  1.1027 +    
  1.1028 +
  1.1029 +Start a Browser:
  1.1030 +    (copy paste this into your browser's address field after the host:port)
  1.1031 +
  1.1032 +    /application?vm=Debian+7&app=Browser
  1.1033 +
  1.1034 +    (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
  1.1035 +        """
  1.1036 +    
  1.1037 +        return res
  1.1038 +
  1.1039 +
  1.1040 +class ProcessResultBouncer(threading.Thread):
  1.1041 +
  1.1042 +    """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
  1.1043 +
  1.1044 +    def __init__(self, process, remote_ip, resource): 
  1.1045 +
  1.1046 +        """ctor"""
  1.1047 +
  1.1048 +        threading.Thread.__init__(self)
  1.1049 +        self._process = process
  1.1050 +        self._remote_ip = remote_ip
  1.1051 +        self._resource = resource
  1.1052 + 
  1.1053 +    
  1.1054 +    def stop(self):
  1.1055 +
  1.1056 +        """stop thread"""
  1.1057 +        self.running = False
  1.1058 +        
  1.1059 +    
  1.1060 +    def run(self):
  1.1061 +
  1.1062 +        """run the thread"""
  1.1063 +
  1.1064 +        # invoke the user dialog as a subprocess
  1.1065 +        result = self._process.communicate()[0]
  1.1066 +        if self._process.returncode != 0:
  1.1067 +            print 'user request has been aborted.'
  1.1068 +            return
  1.1069 +        
  1.1070 +        # all ok, tell send request back appropriate destination
  1.1071 +        try:
  1.1072 +            j = json.loads(result)
  1.1073 +        except:
  1.1074 +            print 'error in password parsing'
  1.1075 +            return
  1.1076 +        
  1.1077 +        # by provided a 'data' we turn this into a POST statement
  1.1078 +        url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
  1.1079 +        req = urllib2.Request(url_addr, urllib.urlencode(j))
  1.1080 +        try:
  1.1081 +            res = urllib2.urlopen(req)
  1.1082 +        except:
  1.1083 +            print 'failed to contact: ' + url_addr
  1.1084 +            return 
  1.1085 +
  1.1086 +
  1.1087 +class RESTServerThread(threading.Thread):
  1.1088 +
  1.1089 +    """Thread for serving the REST API."""
  1.1090 +
  1.1091 +    def __init__(self, port): 
  1.1092 +
  1.1093 +        """ctor"""
  1.1094 +        threading.Thread.__init__(self)
  1.1095 +        self._port = port 
  1.1096 +    
  1.1097 +    def stop(self):
  1.1098 +
  1.1099 +        """stop thread"""
  1.1100 +        self.running = False
  1.1101 +        
  1.1102 +    
  1.1103 +    def run(self):
  1.1104 +
  1.1105 +        """run the thread"""
  1.1106 +        _serve(self._port)
  1.1107 +
  1.1108 +
  1.1109 +
  1.1110 +def is_already_running(port = 8090):
  1.1111 +
  1.1112 +    """check if this is started twice"""
  1.1113 +
  1.1114 +    try:
  1.1115 +        s = socket.create_connection(('127.0.0.1', port), 0.5)
  1.1116 +    except:
  1.1117 +        return False
  1.1118 +
  1.1119 +    return True
  1.1120 +
  1.1121 +
  1.1122 +def _bounce_vm_logs():
  1.1123 +
  1.1124 +    """grab all logs from the VMs and push them to the log servers"""
  1.1125 +
  1.1126 +    global log_file_lock
  1.1127 +
  1.1128 +    # pick the highest current number
  1.1129 +    cur = 0
  1.1130 +    for f in glob.iglob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*')):
  1.1131 +        try:
  1.1132 +            n = f.split('.')[-1:][0]
  1.1133 +            if cur < int(n):
  1.1134 +                cur = int(n)
  1.1135 +        except:
  1.1136 +            pass
  1.1137 +
  1.1138 +    cur = cur + 1
  1.1139 +
  1.1140 +    # first add new vm logs to our existing one: rename the log file
  1.1141 +    log_file_name_new = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
  1.1142 +    log_file_name_cur = os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.' + str(cur))
  1.1143 +    log_file_lock.acquire()
  1.1144 +    try:
  1.1145 +        os.rename(log_file_name_new, log_file_name_cur)
  1.1146 +        print('new log file: ' + log_file_name_cur)
  1.1147 +    except:
  1.1148 +        pass
  1.1149 +    log_file_lock.release()
  1.1150 +
  1.1151 +    # now we have a list of next log files to dump
  1.1152 +    log_files = glob.glob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*'))
  1.1153 +    log_files.sort()
  1.1154 +    for log_file in log_files:
  1.1155 +
  1.1156 +        try:
  1.1157 +            f = open(log_file, 'rb')
  1.1158 +            while True:
  1.1159 +                l = pickle.load(f)
  1.1160 +                _push_log(l)
  1.1161 +
  1.1162 +        except EOFError:
  1.1163 +
  1.1164 +            try:
  1.1165 +                os.remove(log_file)
  1.1166 +            except:
  1.1167 +                logger.warning('tried to delete log file (pushed to EOF) "' + log_file + '" but failed')
  1.1168 +
  1.1169 +        except:
  1.1170 +            logger.warning('encountered error while pushing log file "' + log_file + '"')
  1.1171 +            break
  1.1172 +
  1.1173 +    # start bouncer again ...
  1.1174 +    global log_file_bouncer
  1.1175 +    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
  1.1176 +    log_file_bouncer.start()
  1.1177 +
  1.1178 +
  1.1179 +def _push_log(log):
  1.1180 +    """POST a single log to log server
  1.1181 +
  1.1182 +    @param  log     the log POST param
  1.1183 +    """
  1.1184 +
  1.1185 +    try:
  1.1186 +        key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\OpenSecurity')
  1.1187 +        log_server_url = str(win32api.RegQueryValueEx(key, 'LogServerURL')[0])
  1.1188 +        win32api.RegCloseKey(key)
  1.1189 +    except:
  1.1190 +        logger.critical('Cannot open Registry HKEY_LOCAL_MACHINE\SOFTWARE\OpenSecurity and get LogServerURL value')
  1.1191 +        raise
  1.1192 +
  1.1193 +    # by provided a 'data' we turn this into a POST statement
  1.1194 +    d = urllib.urlencode(log)
  1.1195 +    req = urllib2.Request(log_server_url, d)
  1.1196 +    urllib2.urlopen(req)
  1.1197 +    logger.debug('pushed log to server: ' + str(log_server_url))
  1.1198 +
  1.1199 +
  1.1200 +def _serve(port):
  1.1201 +
  1.1202 +    """Start the REST server"""
  1.1203 +
  1.1204 +    global server
  1.1205 +
  1.1206 +    # start the VM-log bouncer timer
  1.1207 +    global log_file_bouncer
  1.1208 +    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
  1.1209 +    log_file_bouncer.start()
  1.1210 +
  1.1211 +    # trick the web.py server 
  1.1212 +    sys.argv = [__file__, str(port)]
  1.1213 +    server = web.application(opensecurity_urls, globals())
  1.1214 +    server.run()
  1.1215 +
  1.1216 +
  1.1217 +def serve(port = 8090, background = False):
  1.1218 +
  1.1219 +    """Start serving the REST Api
  1.1220 +    port ... port number to listen on
  1.1221 +    background ... cease into background (spawn thread) and return immediately"""
  1.1222 +
  1.1223 +    # start threaded or direct version
  1.1224 +    if background == True:
  1.1225 +        t = RESTServerThread(port)
  1.1226 +        t.start()
  1.1227 +    else:
  1.1228 +        _serve(port)
  1.1229 +
  1.1230 +def stop():
  1.1231 +
  1.1232 +    """Stop serving the REST Api"""
  1.1233 +
  1.1234 +    global server
  1.1235 +    if server is None:
  1.1236 +        return
  1.1237 +
  1.1238 +    global log_file_bouncer
  1.1239 +    if log_file_bouncer is not None:
  1.1240 +        log_file_bouncer.cancel()
  1.1241 +
  1.1242 +    server.stop()
  1.1243 +
  1.1244 +# start
  1.1245 +if __name__ == "__main__":
  1.1246 +    serve()
  1.1247 +