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