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