OpenSecurity/bin/opensecurity_client_restful_server.py
author BarthaM@N3SIM1218.D03.arc.local
Fri, 05 Sep 2014 12:28:30 +0100
changeset 221 853af9cfab6a
parent 209 cc8a15da3a13
child 226 107dc235508f
permissions -rwxr-xr-x
Integrated import script (rewritten in python) into opensecurity/vmmanager.py
Improoved user feedback upon import and update as well as logging.
Reduced system shutdown times and ui response times
Improoved the decoupling between UI and OSec subsystem.
Various other fixes
oliver@168
     1
#!/usr/bin/env python
oliver@167
     2
# -*- coding: utf-8 -*-
oliver@167
     3
oliver@167
     4
# ------------------------------------------------------------
oliver@167
     5
# opensecurity_client_restful_server
oliver@167
     6
# 
oliver@167
     7
# the OpenSecurity client RESTful server
oliver@167
     8
#
oliver@167
     9
# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
oliver@167
    10
#
oliver@167
    11
# Copyright (C) 2013 AIT Austrian Institute of Technology
oliver@167
    12
# AIT Austrian Institute of Technology GmbH
oliver@167
    13
# Donau-City-Strasse 1 | 1220 Vienna | Austria
oliver@167
    14
# http://www.ait.ac.at
oliver@167
    15
#
oliver@167
    16
# This program is free software; you can redistribute it and/or
oliver@167
    17
# modify it under the terms of the GNU General Public License
oliver@167
    18
# as published by the Free Software Foundation version 2.
oliver@167
    19
# 
oliver@167
    20
# This program is distributed in the hope that it will be useful,
oliver@167
    21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
oliver@167
    22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
oliver@167
    23
# GNU General Public License for more details.
oliver@167
    24
# 
oliver@167
    25
# You should have received a copy of the GNU General Public License
oliver@167
    26
# along with this program; if not, write to the Free Software
oliver@167
    27
# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
oliver@167
    28
# Boston, MA  02110-1301, USA.
oliver@167
    29
# ------------------------------------------------------------
oliver@167
    30
oliver@167
    31
oliver@167
    32
# ------------------------------------------------------------
oliver@167
    33
# imports
oliver@167
    34
oliver@167
    35
import getpass
oliver@167
    36
import glob
oliver@167
    37
import json
oliver@167
    38
import os
oliver@167
    39
import os.path
oliver@167
    40
import pickle
oliver@167
    41
import platform
oliver@167
    42
import socket
oliver@167
    43
import subprocess
oliver@167
    44
import sys
oliver@167
    45
import threading
oliver@167
    46
import time
oliver@167
    47
import urllib
oliver@167
    48
import urllib2
oliver@167
    49
import web
oliver@167
    50
import threading
oliver@167
    51
import time
oliver@167
    52
import string
oliver@167
    53
import win32api
oliver@167
    54
import win32con
BarthaM@176
    55
import win32wnet
oliver@178
    56
import win32netcon
BarthaM@176
    57
import itertools
BarthaM@176
    58
import ctypes
oliver@167
    59
oliver@193
    60
from PyQt4 import QtGui
oliver@193
    61
oliver@167
    62
from opensecurity_util import logger, setupLogger, OpenSecurityException
oliver@167
    63
if sys.platform == 'win32' or sys.platform == 'cygwin':
oliver@167
    64
    from cygwin import Cygwin
oliver@167
    65
oliver@167
    66
# local
oliver@167
    67
import __init__ as opensecurity
oliver@167
    68
from environment import Environment
oliver@167
    69
oliver@167
    70
oliver@167
    71
# ------------------------------------------------------------
oliver@167
    72
# const
oliver@167
    73
oliver@167
    74
oliver@167
    75
"""All the URLs we know mapping to class handler"""
oliver@167
    76
opensecurity_urls = (
oliver@167
    77
    '/credentials',             'os_credentials',
oliver@167
    78
    '/keyfile',                 'os_keyfile',
oliver@167
    79
    '/log',                     'os_log',
oliver@193
    80
    '/message',                 'os_message',
oliver@167
    81
    '/notification',            'os_notification',
oliver@167
    82
    '/password',                'os_password',
oliver@167
    83
    '/netmount',                'os_netmount',
oliver@167
    84
    '/netumount',               'os_netumount',
BarthaM@176
    85
    '/netcleanup',              'os_netcleanup',
oliver@167
    86
    '/',                        'os_root'
oliver@167
    87
)
oliver@167
    88
oliver@167
    89
oliver@167
    90
# ------------------------------------------------------------
oliver@167
    91
# vars
oliver@167
    92
oliver@167
    93
oliver@167
    94
"""lock for read/write log file"""
oliver@167
    95
log_file_lock = threading.Lock()
oliver@167
    96
oliver@167
    97
"""timer for the log file bouncer"""
oliver@167
    98
log_file_bouncer = None
oliver@167
    99
oliver@167
   100
"""The REST server object"""
oliver@167
   101
server = None
oliver@167
   102
oliver@167
   103
oliver@193
   104
"""The System Tray Icon instance"""
oliver@193
   105
tray_icon = None
oliver@193
   106
oliver@167
   107
# ------------------------------------------------------------
oliver@167
   108
# code
oliver@167
   109
oliver@167
   110
oliver@167
   111
class os_credentials:
oliver@167
   112
oliver@167
   113
    """OpenSecurity '/credentials' handler.
oliver@167
   114
    
oliver@167
   115
    This is called on GET /credentials?text=TEXT.
oliver@167
   116
    Ideally this should pop up a user dialog to insert his
oliver@167
   117
    credentials based the given TEXT.
oliver@167
   118
    """
oliver@167
   119
    
oliver@167
   120
    def GET(self):
oliver@167
   121
        
oliver@167
   122
        # pick the arguments
oliver@167
   123
        args = web.input()
oliver@167
   124
        
oliver@167
   125
        # we _need_ a text
oliver@167
   126
        if not "text" in args:
oliver@167
   127
            raise web.badrequest('no text given')
oliver@167
   128
        
oliver@167
   129
        # remember remote ip
oliver@167
   130
        remote_ip = web.ctx.environ['REMOTE_ADDR']
oliver@167
   131
oliver@167
   132
        # create the process which queries the user
oliver@167
   133
        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
oliver@167
   134
        process_command = [sys.executable, dlg_image, 'credentials', args.text]
oliver@167
   135
        
oliver@167
   136
        # run process result handling in seprate thread (not to block main one)
oliver@208
   137
        bouncer = ProcessResultBouncer(process_command, remote_ip, '/credentials')
oliver@167
   138
        bouncer.start()
oliver@167
   139
         
oliver@167
   140
        return 'user queried for credentials'
oliver@167
   141
oliver@167
   142
oliver@167
   143
class os_keyfile:
oliver@167
   144
oliver@167
   145
    """OpenSecurity '/keyfile' handler.
oliver@167
   146
    
oliver@167
   147
    This is called on GET /keyfile?text=TEXT.
oliver@167
   148
    Ideally this should pop up a user dialog to insert his
oliver@167
   149
    password along with a keyfile.
oliver@167
   150
    """
oliver@167
   151
    
oliver@167
   152
    def GET(self):
oliver@167
   153
        
oliver@167
   154
        # pick the arguments
oliver@167
   155
        args = web.input()
oliver@167
   156
        
oliver@167
   157
        # we _need_ a text
oliver@167
   158
        if not "text" in args:
oliver@167
   159
            raise web.badrequest('no text given')
oliver@167
   160
            
oliver@167
   161
        # remember remote ip
oliver@167
   162
        remote_ip = web.ctx.environ['REMOTE_ADDR']
oliver@167
   163
        
oliver@167
   164
        # create the process which queries the user
oliver@167
   165
        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
oliver@167
   166
        process_command = [sys.executable, dlg_image, 'keyfile', args.text]
oliver@167
   167
        
oliver@167
   168
        # run process result handling in seprate thread (not to block main one)
oliver@208
   169
        bouncer = ProcessResultBouncer(process_command, remote_ip, '/keyfile')
oliver@167
   170
        bouncer.start()
oliver@167
   171
         
oliver@167
   172
        return 'user queried for password and keyfile'
oliver@167
   173
oliver@167
   174
oliver@167
   175
class os_log:
oliver@167
   176
oliver@167
   177
    """OpenSecurity '/log' handler.
oliver@167
   178
    
oliver@167
   179
    This is called on GET or POST on the log function /log
oliver@167
   180
    """
oliver@167
   181
    
oliver@167
   182
    def GET(self):
oliver@167
   183
        
oliver@167
   184
        # pick the arguments
oliver@167
   185
        self.POST()
oliver@167
   186
oliver@167
   187
oliver@167
   188
    def POST(self):
oliver@167
   189
        
oliver@167
   190
        # pick the arguments
oliver@167
   191
        args = web.input()
oliver@167
   192
        args['user'] = getpass.getuser()
oliver@167
   193
        args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
oliver@167
   194
oliver@167
   195
        # add these to new data to log
oliver@167
   196
        global log_file_lock
oliver@167
   197
        log_file_name = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
oliver@167
   198
        log_file_lock.acquire()
oliver@167
   199
        pickle.dump(args,  open(log_file_name, 'ab'))
oliver@167
   200
        log_file_lock.release()
oliver@167
   201
oliver@167
   202
        return "Ok"
oliver@167
   203
oliver@167
   204
oliver@193
   205
class os_message:
oliver@193
   206
oliver@193
   207
    """OpenSecurity '/message' handler.
oliver@193
   208
    
oliver@193
   209
    This is called on GET /message?text=TEXTi&timeout=TIMEOUT.
oliver@193
   210
    This pops up the typical tray message (like a ballon on windows).
oliver@193
   211
    """
oliver@193
   212
    
oliver@193
   213
    def POST(self):
oliver@193
   214
        return self.GET()
oliver@193
   215
oliver@193
   216
    def GET(self):
oliver@193
   217
                
oliver@193
   218
        # pick the arguments
oliver@193
   219
        args = web.input()
oliver@193
   220
        
oliver@193
   221
        # we _need_ a text
oliver@193
   222
        if not "text" in args:
oliver@193
   223
            raise web.badrequest('no text given')
oliver@193
   224
oliver@193
   225
        timeout = 5000
oliver@193
   226
        if "timeout" in args:
oliver@193
   227
            try:
oliver@193
   228
                timeout=int(args.timeout)
oliver@193
   229
            except:
oliver@193
   230
                pass
oliver@193
   231
            
oliver@193
   232
        if tray_icon is None:
oliver@193
   233
            raise web.badrequest('unable to access tray icon instance')
oliver@193
   234
oliver@193
   235
        tray_icon.showMessage('OpenSecurity', args.text, QtGui.QSystemTrayIcon.Information, timeout)
oliver@193
   236
        return 'Shown: ' + args.text + ' timeout: ' + str(timeout) + ' ms'
oliver@193
   237
oliver@193
   238
oliver@167
   239
class os_notification:
oliver@167
   240
oliver@167
   241
    """OpenSecurity '/notification' handler.
oliver@167
   242
    
oliver@167
   243
    This is called on GET /notification?msgtype=TYPE&text=TEXT.
oliver@167
   244
    This will pop up an OpenSecurity notifcation window
oliver@167
   245
    """
oliver@178
   246
oliver@178
   247
    def POST(self):
oliver@178
   248
        return self.GET()
oliver@167
   249
    
oliver@167
   250
    def GET(self):
oliver@167
   251
        
oliver@167
   252
        # pick the arguments
oliver@167
   253
        args = web.input()
oliver@167
   254
        
oliver@167
   255
        # we _need_ a type
oliver@167
   256
        if not "msgtype" in args:
oliver@167
   257
            raise web.badrequest('no msgtype given')
oliver@167
   258
            
oliver@167
   259
        if not args.msgtype in ['information', 'warning', 'critical']:
oliver@167
   260
            raise web.badrequest('Unknown value for msgtype')
oliver@167
   261
            
oliver@167
   262
        # we _need_ a text
oliver@167
   263
        if not "text" in args:
oliver@167
   264
            raise web.badrequest('no text given')
oliver@167
   265
            
oliver@167
   266
        # invoke the user dialog as a subprocess
oliver@208
   267
        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
oliver@167
   268
        process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
oliver@167
   269
        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
oliver@167
   270
oliver@167
   271
        return "Ok"
oliver@167
   272
oliver@167
   273
oliver@167
   274
class os_password:
oliver@167
   275
oliver@167
   276
    """OpenSecurity '/password' handler.
oliver@167
   277
    
oliver@167
   278
    This is called on GET /password?text=TEXT.
oliver@167
   279
    Ideally this should pop up a user dialog to insert his
oliver@167
   280
    password based device name.
oliver@167
   281
    """
oliver@167
   282
    
oliver@167
   283
    def GET(self):
oliver@167
   284
        
oliver@167
   285
        # pick the arguments
oliver@167
   286
        args = web.input()
oliver@167
   287
        
oliver@167
   288
        # we _need_ a text
oliver@167
   289
        if not "text" in args:
oliver@167
   290
            raise web.badrequest('no text given')
oliver@167
   291
            
oliver@167
   292
        # remember remote ip
oliver@167
   293
        remote_ip = web.ctx.environ['REMOTE_ADDR']
oliver@167
   294
        
oliver@167
   295
        # create the process which queries the user
oliver@167
   296
        dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
oliver@167
   297
        process_command = [sys.executable, dlg_image, 'password', args.text]
oliver@167
   298
        
oliver@167
   299
        # run process result handling in seprate thread (not to block main one)
oliver@208
   300
        bouncer = ProcessResultBouncer(process_command, remote_ip, '/password')
oliver@167
   301
        bouncer.start()
oliver@167
   302
        
oliver@167
   303
        return 'user queried for password'
oliver@167
   304
oliver@193
   305
oliver@193
   306
BarthaM@176
   307
def genNetworkDrive():
BarthaM@176
   308
    logical_drives = getLogicalDrives()
BarthaM@176
   309
    logger.info("Used logical drive letters: "+ str(logical_drives).strip('[]') )
BarthaM@176
   310
    drives = list(map(chr, range(68, 91)))  
BarthaM@176
   311
    for drive in drives:
BarthaM@176
   312
        if drive not in logical_drives:
BarthaM@176
   313
            return drive
BarthaM@176
   314
    return None
BarthaM@176
   315
            
BarthaM@176
   316
def getLogicalDrives():
BarthaM@176
   317
    drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives()
BarthaM@176
   318
    drives = list(itertools.compress(string.ascii_uppercase,  map(lambda x:ord(x) - ord('0'), bin(drive_bitmask)[:1:-1])))
BarthaM@176
   319
    return drives
BarthaM@176
   320
BarthaM@176
   321
def getNetworkPath(drive):
BarthaM@176
   322
    return win32wnet.WNetGetConnection(drive+':')
BarthaM@176
   323
BarthaM@176
   324
def getDriveType(drive):
BarthaM@176
   325
    return ctypes.cdll.kernel32.GetDriveTypeW(u"%s:\\"%drive)
BarthaM@176
   326
        
BarthaM@176
   327
def getNetworkDrive(path):
BarthaM@176
   328
    for drive in getLogicalDrives():
BarthaM@176
   329
        #if is a network drive
BarthaM@176
   330
        if getDriveType(drive) == 4:
BarthaM@176
   331
            network_path = getNetworkPath(drive)
BarthaM@176
   332
            if path in network_path:
BarthaM@176
   333
                return drive
BarthaM@176
   334
    return None
BarthaM@221
   335
oliver@178
   336
def mapDrive(drive, networkPath, user, password):
oliver@178
   337
    if (os.path.exists(networkPath)):
BarthaM@182
   338
        logger.debug(networkPath + " is found...")
BarthaM@182
   339
        logger.debug("Trying to map " + networkPath + " on to " + drive + " .....")
oliver@178
   340
        try:
oliver@178
   341
            win32wnet.WNetAddConnection2(win32netcon.RESOURCETYPE_DISK, drive, networkPath, None, user, password)
oliver@178
   342
        except:
BarthaM@182
   343
            logger.error("Unexpected error...")
oliver@178
   344
            return 1
BarthaM@182
   345
        logger.info("Mapping successful")
oliver@178
   346
        return 0
oliver@178
   347
    else:
BarthaM@182
   348
        logger.error("Network path unreachable...")
oliver@178
   349
        return 1    
BarthaM@184
   350
BarthaM@184
   351
mount_lock = threading.Lock()
BarthaM@221
   352
oliver@167
   353
# handles netumount request                    
oliver@167
   354
class MountNetworkDriveHandler(threading.Thread): 
BarthaM@176
   355
    networkPath = None
BarthaM@176
   356
    def __init__(self, net_path):
oliver@167
   357
        threading.Thread.__init__(self)
oliver@167
   358
        self.networkPath = net_path
oliver@167
   359
    
oliver@167
   360
    def run(self):
oliver@167
   361
        #Check for network resource availability
oliver@178
   362
        retry = 20
oliver@167
   363
        while not os.path.exists(self.networkPath):
oliver@178
   364
            if retry == 0:
oliver@178
   365
                break
oliver@178
   366
            logger.info("Path not accessible: " + self.networkPath + " retrying")
oliver@167
   367
            time.sleep(1)
oliver@167
   368
            retry-=1
BarthaM@184
   369
        with mount_lock:
BarthaM@184
   370
            drive = genNetworkDrive()
BarthaM@184
   371
            if not drive:
BarthaM@184
   372
                logger.error("Failed to assign drive letter for: " + self.networkPath)
BarthaM@184
   373
                return 1
BarthaM@184
   374
            else:
BarthaM@184
   375
                logger.info("Assigned drive " + drive + " to " + self.networkPath)
BarthaM@184
   376
            
BarthaM@184
   377
            #Check for drive availability
BarthaM@184
   378
            drive = drive+':'
BarthaM@184
   379
            if os.path.exists(drive):
BarthaM@184
   380
                logger.error("Drive letter is already in use: " + drive)
BarthaM@184
   381
                return 1
BarthaM@184
   382
            
BarthaM@184
   383
            return mapDrive(drive, self.networkPath, "", "")
oliver@167
   384
oliver@167
   385
class os_netmount:
oliver@167
   386
    
oliver@167
   387
    """OpenSecurity '/netmount' handler"""
oliver@167
   388
    
oliver@167
   389
    def GET(self):
oliver@167
   390
        # pick the arguments
oliver@167
   391
        args = web.input()
oliver@167
   392
        
oliver@167
   393
        # we _need_ a net_resource
oliver@167
   394
        if not "net_resource" in args:
oliver@167
   395
            raise web.badrequest('no net_resource given')
oliver@167
   396
        
BarthaM@176
   397
        driveHandler = MountNetworkDriveHandler(args['net_resource'])
oliver@167
   398
        driveHandler.start()
oliver@167
   399
        driveHandler.join(None)
oliver@167
   400
        return 'Ok'
oliver@178
   401
oliver@178
   402
def unmapDrive(drive, force=0):
BarthaM@182
   403
    logger.debug("drive in use, trying to unmap...")
oliver@178
   404
    if force == 0:
BarthaM@182
   405
        logger.debug("Executing un-forced call...")
oliver@178
   406
    
oliver@178
   407
    try:
oliver@178
   408
        win32wnet.WNetCancelConnection2(drive, 1, force)
BarthaM@221
   409
        logger.info(drive + "successfully unmapped...")
oliver@178
   410
        return 0
oliver@178
   411
    except:
BarthaM@182
   412
        logger.error("Unmap failed, try again...")
oliver@178
   413
        return 1
oliver@178
   414
oliver@167
   415
# handles netumount request                    
oliver@167
   416
class UmountNetworkDriveHandler(threading.Thread): 
BarthaM@176
   417
    networkPath = None
oliver@167
   418
    running = True
oliver@167
   419
    
BarthaM@176
   420
    def __init__(self, path):
oliver@167
   421
        threading.Thread.__init__(self)
BarthaM@176
   422
        self.networkPath = path
oliver@167
   423
oliver@167
   424
    def run(self):
oliver@167
   425
        while self.running:
BarthaM@176
   426
            drive = getNetworkDrive(self.networkPath)
BarthaM@176
   427
            if not drive:
BarthaM@176
   428
                logger.info("Failed to retrieve drive letter for: " + self.networkPath + ". Successfully deleted or missing.")
oliver@167
   429
                self.running = False
oliver@167
   430
            else:
BarthaM@176
   431
                drive = drive+':'
BarthaM@176
   432
                logger.info("Unmounting drive " + drive + " for " + self.networkPath)
oliver@178
   433
                result = unmapDrive(drive, force=1) 
oliver@178
   434
                if result != 0:
oliver@167
   435
                    continue
oliver@167
   436
                        
oliver@167
   437
oliver@167
   438
class os_netumount:
oliver@167
   439
    
oliver@167
   440
    """OpenSecurity '/netumount' handler"""
oliver@167
   441
    
oliver@167
   442
    def GET(self):
oliver@167
   443
        # pick the arguments
oliver@167
   444
        args = web.input()
oliver@167
   445
        
BarthaM@176
   446
        # we _need_ a net_resource
BarthaM@176
   447
        if not "net_resource" in args:
BarthaM@176
   448
            raise web.badrequest('no net_resource given')
oliver@167
   449
        
BarthaM@176
   450
        driveHandler = UmountNetworkDriveHandler(args['net_resource'])
oliver@167
   451
        driveHandler.start()
oliver@167
   452
        driveHandler.join(None)
oliver@167
   453
        return 'Ok'
BarthaM@176
   454
BarthaM@176
   455
class os_netcleanup:
oliver@167
   456
    
BarthaM@176
   457
    """OpenSecurity '/netcleanup' handler"""
BarthaM@176
   458
    
BarthaM@176
   459
    def GET(self):
BarthaM@176
   460
        # pick the arguments
BarthaM@176
   461
        args = web.input()
BarthaM@176
   462
        
BarthaM@176
   463
        # we _need_ a net_resource
BarthaM@176
   464
        if not "hostonly_ip" in args:
BarthaM@181
   465
            raise web.badrequest('no hostonly_ip given')
BarthaM@176
   466
        
BarthaM@176
   467
        ip = args['hostonly_ip']
BarthaM@176
   468
        ip = ip[:ip.rindex('.')]
BarthaM@176
   469
        drives = getLogicalDrives()
BarthaM@176
   470
        for drive in drives:
BarthaM@176
   471
            # found network drive
BarthaM@176
   472
            if getDriveType(drive) == 4:
BarthaM@176
   473
                path = getNetworkPath(drive)
BarthaM@176
   474
                if ip in path:
BarthaM@176
   475
                    driveHandler = UmountNetworkDriveHandler(path)
BarthaM@176
   476
                    driveHandler.start()
BarthaM@176
   477
                    driveHandler.join(None)
oliver@167
   478
oliver@167
   479
class os_root:
oliver@167
   480
oliver@167
   481
    """OpenSecurity '/' handler"""
oliver@167
   482
    
oliver@167
   483
    def GET(self):
oliver@167
   484
    
oliver@167
   485
        res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
oliver@167
   486
        
oliver@167
   487
        # add some sample links
oliver@167
   488
        res = res + """
oliver@167
   489
        
oliver@167
   490
USAGE EXAMPLES:
oliver@167
   491
        
oliver@167
   492
Request a password: 
oliver@167
   493
    (copy paste this into your browser's address field after the host:port)
oliver@167
   494
    
oliver@167
   495
    /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
oliver@167
   496
    
oliver@167
   497
    (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
oliver@167
   498
    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
oliver@167
   499
    
oliver@167
   500
    
oliver@167
   501
Request a combination of user and password:
oliver@167
   502
    (copy paste this into your browser's address field after the host:port)
oliver@167
   503
    
oliver@167
   504
    /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
oliver@167
   505
    
oliver@167
   506
    (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
oliver@167
   507
    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
oliver@167
   508
    
oliver@167
   509
oliver@167
   510
Request a combination of password and keyfile:
oliver@167
   511
    (copy paste this into your browser's address field after the host:port)
oliver@167
   512
    
oliver@167
   513
    /keyfile?text=Your%20private%20RSA%20Keyfile%3A
oliver@167
   514
    
oliver@167
   515
    (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
oliver@167
   516
    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
oliver@167
   517
    
oliver@167
   518
oliver@167
   519
Start a Browser:
oliver@167
   520
    (copy paste this into your browser's address field after the host:port)
oliver@167
   521
oliver@167
   522
    /application?vm=Debian+7&app=Browser
oliver@167
   523
oliver@167
   524
    (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
oliver@167
   525
        """
oliver@167
   526
    
oliver@167
   527
        return res
oliver@167
   528
oliver@167
   529
oliver@167
   530
class ProcessResultBouncer(threading.Thread):
oliver@167
   531
oliver@167
   532
    """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
oliver@167
   533
oliver@208
   534
    def __init__(self, process_command, remote_ip, resource): 
oliver@167
   535
oliver@167
   536
        """ctor"""
oliver@167
   537
oliver@167
   538
        threading.Thread.__init__(self)
oliver@208
   539
        self._process_command = process_command
oliver@167
   540
        self._remote_ip = remote_ip
oliver@167
   541
        self._resource = resource
oliver@167
   542
 
oliver@167
   543
    
oliver@167
   544
    def stop(self):
oliver@167
   545
oliver@167
   546
        """stop thread"""
oliver@167
   547
        self.running = False
oliver@167
   548
        
oliver@167
   549
    
oliver@167
   550
    def run(self):
oliver@167
   551
oliver@167
   552
        """run the thread"""
oliver@167
   553
oliver@208
   554
        while True:
oliver@208
   555
oliver@208
   556
            # invoke the user dialog as a subprocess
oliver@208
   557
            process = subprocess.Popen(self._process_command, shell = False, stdout = subprocess.PIPE)        
oliver@208
   558
            result = process.communicate()[0]
oliver@208
   559
            if process.returncode != 0:
oliver@208
   560
                print 'user request has been aborted.'
oliver@208
   561
                return
oliver@208
   562
            
oliver@208
   563
            # all ok, tell send request back appropriate destination
oliver@208
   564
            try:
oliver@208
   565
                j = json.loads(result)
oliver@208
   566
            except:
oliver@208
   567
                print 'error in password parsing'
oliver@208
   568
                return
oliver@208
   569
            
oliver@208
   570
            # by provided a 'data' we turn this into a POST statement
oliver@208
   571
            url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
oliver@208
   572
            req = urllib2.Request(url_addr, urllib.urlencode(j))
oliver@208
   573
            try:
oliver@208
   574
                res = urllib2.urlopen(req)
oliver@208
   575
                if res.getcode() == 200:
oliver@208
   576
                    return
oliver@208
   577
oliver@208
   578
            except urllib2.HTTPError as e:
oliver@208
   579
oliver@208
   580
                # invoke the user dialog as a subprocess
oliver@208
   581
                dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
oliver@209
   582
                dlg_process_command = [sys.executable, dlg_image, 'notification-critical', 'Error is<br/>Code: {0!s}<br/>Reason: {1}<br/>{2}'.format(e.code, e.reason, e.read())]
oliver@208
   583
                dlg_process = subprocess.Popen(dlg_process_command, shell = False, stdout = subprocess.PIPE)
oliver@208
   584
                dlg_process.communicate()[0]
oliver@167
   585
oliver@167
   586
oliver@167
   587
class RESTServerThread(threading.Thread):
oliver@167
   588
oliver@167
   589
    """Thread for serving the REST API."""
oliver@167
   590
oliver@167
   591
    def __init__(self, port): 
oliver@167
   592
oliver@167
   593
        """ctor"""
oliver@167
   594
        threading.Thread.__init__(self)
oliver@167
   595
        self._port = port 
oliver@167
   596
    
oliver@167
   597
    def stop(self):
oliver@167
   598
oliver@167
   599
        """stop thread"""
oliver@167
   600
        self.running = False
oliver@167
   601
        
oliver@167
   602
    
oliver@167
   603
    def run(self):
oliver@167
   604
oliver@167
   605
        """run the thread"""
oliver@167
   606
        _serve(self._port)
oliver@167
   607
oliver@167
   608
oliver@167
   609
oliver@167
   610
def is_already_running(port = 8090):
oliver@167
   611
oliver@167
   612
    """check if this is started twice"""
oliver@167
   613
oliver@167
   614
    try:
oliver@167
   615
        s = socket.create_connection(('127.0.0.1', port), 0.5)
oliver@167
   616
    except:
oliver@167
   617
        return False
oliver@167
   618
oliver@167
   619
    return True
oliver@167
   620
oliver@167
   621
oliver@167
   622
def _bounce_vm_logs():
oliver@167
   623
oliver@167
   624
    """grab all logs from the VMs and push them to the log servers"""
oliver@167
   625
oliver@167
   626
    global log_file_lock
oliver@167
   627
oliver@167
   628
    # pick the highest current number
oliver@167
   629
    cur = 0
oliver@167
   630
    for f in glob.iglob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*')):
oliver@167
   631
        try:
oliver@167
   632
            n = f.split('.')[-1:][0]
oliver@167
   633
            if cur < int(n):
oliver@167
   634
                cur = int(n)
oliver@167
   635
        except:
oliver@167
   636
            pass
oliver@167
   637
oliver@167
   638
    cur = cur + 1
oliver@167
   639
oliver@167
   640
    # first add new vm logs to our existing one: rename the log file
oliver@167
   641
    log_file_name_new = os.path.join(Environment('OpenSecurity').log_path, 'vm_new.log')
oliver@167
   642
    log_file_name_cur = os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.' + str(cur))
oliver@167
   643
    log_file_lock.acquire()
oliver@167
   644
    try:
oliver@167
   645
        os.rename(log_file_name_new, log_file_name_cur)
oliver@167
   646
        print('new log file: ' + log_file_name_cur)
oliver@167
   647
    except:
oliver@167
   648
        pass
oliver@167
   649
    log_file_lock.release()
oliver@167
   650
oliver@167
   651
    # now we have a list of next log files to dump
oliver@167
   652
    log_files = glob.glob(os.path.join(Environment('OpenSecurity').log_path, 'vm_cur.log.*'))
oliver@167
   653
    log_files.sort()
oliver@167
   654
    for log_file in log_files:
oliver@167
   655
oliver@167
   656
        try:
oliver@167
   657
            f = open(log_file, 'rb')
oliver@167
   658
            while True:
oliver@167
   659
                l = pickle.load(f)
oliver@167
   660
                _push_log(l)
oliver@167
   661
oliver@167
   662
        except EOFError:
oliver@167
   663
            try:
BarthaM@185
   664
                f.close()
oliver@167
   665
                os.remove(log_file)
oliver@167
   666
            except:
oliver@167
   667
                logger.warning('tried to delete log file (pushed to EOF) "' + log_file + '" but failed')
oliver@167
   668
oliver@167
   669
        except:
oliver@167
   670
            logger.warning('encountered error while pushing log file "' + log_file + '"')
oliver@167
   671
            break
oliver@167
   672
oliver@167
   673
    # start bouncer again ...
oliver@167
   674
    global log_file_bouncer
oliver@167
   675
    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
oliver@167
   676
    log_file_bouncer.start()
oliver@167
   677
oliver@167
   678
oliver@167
   679
def _push_log(log):
oliver@167
   680
    """POST a single log to log server
oliver@167
   681
oliver@167
   682
    @param  log     the log POST param
oliver@167
   683
    """
oliver@167
   684
oliver@168
   685
    log_server_url = "http://extern.x-net.at/opensecurity/log"
oliver@167
   686
    try:
oliver@167
   687
        key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\OpenSecurity')
oliver@167
   688
        log_server_url = str(win32api.RegQueryValueEx(key, 'LogServerURL')[0])
oliver@167
   689
        win32api.RegCloseKey(key)
oliver@167
   690
    except:
oliver@168
   691
        logger.warning('Cannot open Registry HKEY_LOCAL_MACHINE\SOFTWARE\OpenSecurity and get "LogServerURL" value, using default instead')
oliver@167
   692
oliver@167
   693
    # by provided a 'data' we turn this into a POST statement
oliver@167
   694
    d = urllib.urlencode(log)
oliver@167
   695
    req = urllib2.Request(log_server_url, d)
oliver@167
   696
    urllib2.urlopen(req)
oliver@167
   697
    logger.debug('pushed log to server: ' + str(log_server_url))
oliver@167
   698
oliver@167
   699
oliver@167
   700
def _serve(port):
oliver@167
   701
oliver@167
   702
    """Start the REST server"""
oliver@167
   703
oliver@167
   704
    global server
oliver@167
   705
oliver@167
   706
    # start the VM-log bouncer timer
oliver@167
   707
    global log_file_bouncer
oliver@167
   708
    log_file_bouncer = threading.Timer(5.0, _bounce_vm_logs)
oliver@167
   709
    log_file_bouncer.start()
oliver@167
   710
oliver@167
   711
    # trick the web.py server 
oliver@167
   712
    sys.argv = [__file__, str(port)]
oliver@167
   713
    server = web.application(opensecurity_urls, globals())
oliver@167
   714
    server.run()
oliver@167
   715
oliver@167
   716
oliver@167
   717
def serve(port = 8090, background = False):
oliver@167
   718
oliver@167
   719
    """Start serving the REST Api
oliver@167
   720
    port ... port number to listen on
oliver@167
   721
    background ... cease into background (spawn thread) and return immediately"""
oliver@167
   722
oliver@167
   723
    # start threaded or direct version
oliver@167
   724
    if background == True:
oliver@167
   725
        t = RESTServerThread(port)
oliver@167
   726
        t.start()
oliver@167
   727
    else:
oliver@167
   728
        _serve(port)
oliver@167
   729
oliver@167
   730
def stop():
oliver@167
   731
oliver@167
   732
    """Stop serving the REST Api"""
oliver@167
   733
oliver@167
   734
    global server
oliver@167
   735
    if server is None:
oliver@167
   736
        return
oliver@167
   737
oliver@167
   738
    global log_file_bouncer
oliver@167
   739
    if log_file_bouncer is not None:
oliver@167
   740
        log_file_bouncer.cancel()
oliver@167
   741
oliver@167
   742
    server.stop()
oliver@167
   743
oliver@167
   744
# start
oliver@167
   745
if __name__ == "__main__":
oliver@167
   746
    serve()
oliver@167
   747