OpenSecurity/bin/vmmanager.pyw
author BarthaM@N3SIM1218.D03.arc.local
Fri, 18 Jul 2014 13:45:09 +0100
changeset 213 2e0b94e12bfc
parent 212 59ebaa44c12c
child 217 4162648fb167
permissions -rwxr-xr-x
Addded http proxy server support.
     1 #!/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 # ------------------------------------------------------------
     5 # opensecurityd
     6 #   
     7 # the opensecurityd as RESTful server
     8 #
     9 # Autor: Mihai Bartha, <mihai.bartha@ait.ac.at>
    10 #
    11 # Copyright (C) 2013 AIT Austrian Institute of Technology
    12 # AIT Austrian Institute of Technology GmbH
    13 # Donau-City-Strasse 1 | 1220 Vienna | Austria
    14 # http://www.ait.ac.at
    15 #
    16 # This program is free software; you can redistribute it and/or
    17 # modify it under the terms of the GNU General Public License
    18 # as published by the Free Software Foundation version 2.
    19 # 
    20 # This program is distributed in the hope that it will be useful,
    21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    23 # GNU General Public License for more details.
    24 # 
    25 # You should have received a copy of the GNU General Public License
    26 # along with this program; if not, write to the Free Software
    27 # Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    28 # Boston, MA  02110-1301, USA.
    29 # ------------------------------------------------------------
    30 
    31 
    32 # ------------------------------------------------------------
    33 # imports
    34 
    35 import os
    36 import os.path
    37 from subprocess import Popen, PIPE, call, STARTUPINFO, _subprocess
    38 import sys
    39 import re
    40 
    41 from cygwin import Cygwin
    42 from environment import Environment
    43 import threading
    44 import time
    45 import string
    46 
    47 import shutil
    48 import stat
    49 import tempfile
    50 from opensecurity_util import logger, setupLogger, OpenSecurityException, showTrayMessage
    51 import ctypes
    52 import itertools
    53 import win32api
    54 import win32con
    55 import win32security
    56 import win32wnet
    57 import urllib
    58 import urllib2
    59 import unittest
    60 DEBUG = True
    61 
    62 
    63 new_sdvm_lock = threading.Lock()
    64 
    65 class VMManagerException(Exception):
    66     def __init__(self, value):
    67         self.value = value
    68     def __str__(self):
    69         return repr(self.value)
    70 
    71 class USBFilter:
    72     uuid = ""
    73     vendorid = ""
    74     productid = ""
    75     revision = ""
    76     serial = ""
    77     
    78     def __init__(self, uuid, vendorid, productid, revision, serial):
    79         self.uuid = uuid
    80         self.vendorid = vendorid.lower()
    81         self.productid = productid.lower()
    82         self.revision = revision.lower()
    83         self.serial = serial
    84         return
    85     
    86     def __eq__(self, other):
    87         return self.uuid == other.uuid #self.vendorid == other.vendorid and self.productid == other.productid and self.revision == other.revision
    88     
    89     def __hash__(self):
    90         return hash(self.uuid) ^ hash(self.vendorid) ^ hash(self.productid) ^ hash(self.revision) ^ hash(self.serial)
    91     
    92     def __repr__(self):
    93         return "UUID:" + str(self.uuid) + " VendorId = \'" + str(self.vendorid) + "\' ProductId = \'" + str(self.productid) + "\'" + "\' Revision = \'" + str(self.revision) + "\' SerialNumber = \'" + str(self.serial)
    94     
    95     #def __getitem__(self, item):
    96     #    return self.coords[item]
    97 def once(theClass):
    98     theClass.systemProperties = theClass.getSystemProperties()
    99     theClass.machineFolder =    theClass.systemProperties["Default machine folder"]
   100     theClass.hostonlyIFs =      theClass.getHostOnlyIFs()
   101     theClass.blacklistedRSD =   theClass.loadRSDBlacklist()
   102     return theClass
   103     
   104 @once
   105 class VMManager(object):
   106     vmRootName = "SecurityDVM"
   107     systemProperties = None
   108     _instance = None
   109     machineFolder = ''
   110     rsdHandler = None
   111     hostonlyIFs = None
   112     browsingManager = None
   113     blacklistedRSD = None
   114     status_message = 'Starting up...'
   115 
   116  
   117     def __init__(self):
   118         # only proceed if we have a working background environment
   119         if self.backend_ok():
   120             self.cleanup()
   121         else:
   122             logger.critical(self.status_message)
   123     
   124 
   125     @staticmethod
   126     def getInstance():
   127         if VMManager._instance == None:
   128             VMManager._instance = VMManager()
   129         return VMManager._instance
   130     
   131     #list the hostonly IFs exposed by the VBox host
   132     @staticmethod    
   133     def getHostOnlyIFs():
   134         result = Cygwin.vboxExecute('list hostonlyifs')[1]
   135         if result=='':
   136             return None
   137         props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result.strip().splitlines()))
   138         return props    
   139         
   140     # return hosty system properties
   141     @staticmethod
   142     def getSystemProperties():
   143         result = Cygwin.checkResult(Cygwin.vboxExecute('list systemproperties'))
   144         if result[1]=='':
   145             return None
   146         props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result[1].strip().splitlines()))
   147         return props
   148     
   149     # return the folder containing the guest VMs     
   150     def getMachineFolder(self):
   151         return VMManager.machineFolder
   152 
   153     def backend_ok(self):
   154 
   155         """check if the backend (VirtualBox) is sufficient for our task"""
   156 
   157         # ensure we have our system props
   158         if VMManager.systemProperties == None:
   159             VMManager.systemProperties = self.getSystemProperties()
   160         if VMManager.systemProperties == None:
   161             self.status_message = 'Failed to get backend system properties. Is Backend (VirtualBox?) installed?'
   162             return False
   163 
   164         # check for existing Extension pack
   165         if not 'Remote desktop ExtPack' in VMManager.systemProperties:
   166             self.status_message = 'No remote desktop extension pack found. Please install the "Oracle VM VirtualBox Extension Pack" from https://www.virtualbox.org/wiki/Downloads.'
   167             return False
   168         if VMManager.systemProperties['Remote desktop ExtPack'] == 'Oracle VM VirtualBox Extension Pack ':
   169             self.status_message = 'Unsure if suitable extension pack is installed. Please install the "Oracle VM VirtualBox Extension Pack" from https://www.virtualbox.org/wiki/Downloads.'
   170             return False
   171 
   172         # check if we do have our root VMs installed
   173         vms = self.listVM()
   174         if not self.vmRootName in vms:
   175             self.status_message = 'Unable to locate root SecurityDVM. Please download and setup the initial image.'
   176             return False
   177 
   178         # basically all seems nice and ready to rumble
   179         self.status_message = 'All is ok.'
   180 
   181         return True
   182     
   183     def stop(self):
   184         if self.rsdHandler != None:
   185             self.rsdHandler.stop()
   186             self.rsdHandler.join()
   187             self.rsdHandler = None
   188             
   189         if self.browsingManager != None:
   190             self.browsingManager.stop()
   191             self.browsingManager.join()
   192             self.browsingManager = None
   193     
   194     def start(self):
   195         self.stop()
   196         self.browsingManager = BrowsingManager(self)
   197         self.browsingManager.start()
   198         self.rsdHandler = DeviceHandler(self)
   199         self.rsdHandler.start()
   200         
   201 
   202     def cleanup(self):
   203         self.stop()
   204         ip = self.getHostOnlyIP(None)
   205         try:
   206             result = urllib2.urlopen('http://127.0.0.1:8090/netcleanup?'+'hostonly_ip='+ip).readline()
   207         except urllib2.URLError:
   208             logger.info("Network drive cleanup all skipped. OpenSecurity Tray client not started yet.")
   209             
   210         for vm in self.listSDVM():
   211             self.poweroffVM(vm)
   212             self.removeVM(vm)
   213 
   214     # list all existing VMs registered with VBox
   215     def listVM(self):
   216         result = Cygwin.checkResult(Cygwin.vboxExecute('list vms'))[1]
   217         vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
   218         return vms
   219     
   220     # list running VMs
   221     def listRunningVMS(self):
   222         result = Cygwin.checkResult(Cygwin.vboxExecute('list runningvms'))[1]
   223         vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
   224         return vms
   225     
   226     # list existing SDVMs
   227     def listSDVM(self):
   228         vms = self.listVM()
   229         svdms = []
   230         for vm in vms:
   231             if vm.startswith(self.vmRootName) and vm != self.vmRootName:
   232                 svdms.append(vm)
   233         return svdms
   234     
   235     # generate valid (not already existing SDVM name). necessary for creating a new VM
   236     def genSDVMName(self):
   237         vms = self.listVM()
   238         for i in range(0,999):
   239             if(not self.vmRootName+str(i) in vms):
   240                 return self.vmRootName+str(i)
   241         return ''
   242     
   243     @staticmethod
   244     def loadRSDBlacklist():
   245         blacklist = dict()
   246         try:
   247             fo = open(Environment('OpenSecurity').prefix_path +"\\bin\\blacklist.usb", "r")
   248         except IOError:
   249             logger.error("Could not open RSD blacklist file.")
   250             return blacklist
   251         
   252         lines = fo.readlines()
   253         for line in lines:
   254             if line != "":  
   255                 parts = line.strip().split(' ')
   256                 blacklist[parts[0].lower()] = parts[1].lower()
   257         return blacklist
   258          
   259     @staticmethod
   260     def isBlacklisted(device):
   261         if VMManager.blacklistedRSD:
   262             blacklisted = device.vendorid.lower() in VMManager.blacklistedRSD.keys() and device.productid.lower() == VMManager.blacklistedRSD[device.vendorid]
   263             return blacklisted
   264         return False 
   265     
   266     # check if the device is mass storage type
   267     @staticmethod
   268     def isMassStorageDevice(device):
   269         keyname = 'SYSTEM\CurrentControlSet\Enum\USB' + '\VID_' + device.vendorid+'&'+'PID_'+ device.productid
   270         key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname)
   271         devinfokeyname = win32api.RegEnumKey(key, 0)
   272         win32api.RegCloseKey(key)
   273 
   274         devinfokey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname+'\\'+devinfokeyname)
   275         value = win32api.RegQueryValueEx(devinfokey, 'SERVICE')[0]
   276         win32api.RegCloseKey(devinfokey)
   277         
   278         return 'USBSTOR' in value
   279     
   280     # return the RSDs connected to the host
   281     @staticmethod
   282     def getExistingRSDs():
   283         results = Cygwin.checkResult(Cygwin.vboxExecute('list usbhost'))[1]
   284         results = results.split('Host USB Devices:')[1].strip()
   285         
   286         items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
   287         rsds = dict()   
   288         for item in items:
   289             props = dict()
   290             for line in item.splitlines():     
   291                 if line != "":         
   292                     k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
   293                     props[k] = v
   294             
   295             uuid = re.search(r"(?P<uuid>[0-9A-Fa-f\-]+)", props['UUID']).groupdict()['uuid']
   296             vid = re.search(r"\((?P<vid>[0-9A-Fa-f]+)\)", props['VendorId']).groupdict()['vid']
   297             pid = re.search(r"\((?P<pid>[0-9A-Fa-f]+)\)", props['ProductId']).groupdict()['pid']
   298             rev = re.search(r"\((?P<rev>[0-9A-Fa-f]+)\)", props['Revision']).groupdict()['rev']
   299             serial = None
   300             if 'SerialNumber' in props.keys():
   301                 serial = re.search(r"(?P<ser>[0-9A-Fa-f]+)", props['SerialNumber']).groupdict()['ser']
   302             usb_filter = USBFilter( uuid, vid, pid, rev, serial)
   303              
   304             if VMManager.isMassStorageDevice(usb_filter) and not VMManager.isBlacklisted(usb_filter):
   305                 rsds[uuid] = usb_filter
   306                 logger.debug(usb_filter)
   307         return rsds
   308     
   309    
   310     #def getAttachedRSD(self, vm_name):
   311     #    props = self.getVMInfo(vm_name)
   312     #    keys = set(['USBFilterVendorId1', 'USBFilterProductId1', 'USBFilterRevision1', 'USBFilterSerialNumber1'])
   313     #    keyset = set(props.keys())
   314     #    usb_filter = None
   315     #    if keyset.issuperset(keys):
   316     #        usb_filter = USBFilter(props['USBFilterVendorId1'], props['USBFilterProductId1'], props['USBFilterRevision1'])
   317     #    return usb_filter
   318     
   319     # return the attached USB device as usb descriptor for an existing VM 
   320     def getAttachedRSD(self, vm_name):
   321         props = self.getVMInfo(vm_name)
   322         keys = set(['USBAttachedUUID1', 'USBAttachedVendorId1', 'USBAttachedProductId1', 'USBAttachedRevision1', 'USBAttachedSerialNumber1'])
   323         keyset = set(props.keys())
   324         usb_filter = None
   325         if keyset.issuperset(keys):
   326             usb_filter = USBFilter(props['USBAttachedUUID1'], props['USBAttachedVendorId1'], props['USBAttachedProductId1'], props['USBAttachedRevision1'], props['USBAttachedSerialNumber1'])
   327         return usb_filter
   328         
   329     # return the RSDs attached to all existing SDVMs
   330     def getAttachedRSDs(self):
   331         vms = self.listSDVM()
   332         attached_devices = dict()
   333         for vm in vms:
   334             rsd_filter = self.getAttachedRSD(vm)
   335             if rsd_filter != None:
   336                 attached_devices[vm] = rsd_filter
   337         return attached_devices
   338     
   339     # attach removable storage device to VM by provision of filter
   340     def attachRSD(self, vm_name, rsd_filter):
   341         #return Cygwin.checkResult(Cygwin.vboxExecute('usbfilter add 0 --target ' + vm_name + ' --name OpenSecurityRSD --vendorid ' + rsd_filter.vendorid + ' --productid ' + rsd_filter.productid + ' --revision ' + rsd_filter.revision + ' --serialnumber ' + rsd_filter.serial))
   342         return Cygwin.checkResult(Cygwin.vboxExecute('controlvm ' + vm_name + ' usbattach ' + rsd_filter.uuid ))
   343     
   344     # detach removable storage from VM by 
   345     def detachRSD(self, vm_name, rsd_filter):
   346         #return Cygwin.checkResult(Cygwin.vboxExecute('usbfilter remove 0 --target ' + vm_name))
   347         return Cygwin.checkResult(Cygwin.vboxExecute('controlvm ' + vm_name + ' usbdetach ' + rsd_filter.uuid ))
   348         
   349     # configures hostonly networking and DHCP server. requires admin rights
   350     def configureHostNetworking(self):
   351         #cmd = 'vboxmanage list hostonlyifs'
   352         #Cygwin.vboxExecute(cmd)
   353         #cmd = 'vboxmanage hostonlyif remove \"VirtualBox Host-Only Ethernet Adapter\"'
   354         #Cygwin.vboxExecute(cmd)
   355         #cmd = 'vboxmanage hostonlyif create'
   356         #Cygwin.vboxExecute(cmd)
   357         Cygwin.checkResult(Cygwin.vboxExecute('hostonlyif ipconfig \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.1 --netmask 255.255.255.0'))
   358         #cmd = 'vboxmanage dhcpserver add'
   359         #Cygwin.vboxExecute(cmd)
   360         Cygwin.checkResult(Cygwin.vboxExecute('dhcpserver modify --ifname \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.100 --netmask 255.255.255.0 --lowerip 192.168.56.101 --upperip 192.168.56.200'))
   361     
   362     def isSDVMExisting(self, vm_name):
   363         sdvms = self.listSDVM()
   364         return vm_name in sdvms
   365         
   366     #create new virtual machine instance based on template vm named SecurityDVM (\SecurityDVM\SecurityDVM.vmdk)
   367     def createVM(self, vm_name):
   368         if self.isSDVMExisting(vm_name):
   369             return
   370         #remove eventually existing SDVM folder
   371         machineFolder = Cygwin.cygPath(VMManager.machineFolder)
   372         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '\\\"'))
   373         hostonly_if = self.getHostOnlyIFs()
   374         Cygwin.checkResult(Cygwin.vboxExecute('createvm --name ' + vm_name + ' --ostype Debian --register'))
   375         Cygwin.checkResult(Cygwin.vboxExecute('modifyvm ' + vm_name + ' --memory 768 --vram 10 --cpus 1 --usb on --usbehci on --nic1 hostonly --hostonlyadapter1 \"' + hostonly_if['Name'] + '\" --nic2 nat'))
   376         Cygwin.checkResult(Cygwin.vboxExecute('storagectl ' + vm_name + ' --name SATA --add sata --portcount 2'))
   377 
   378     #create new SecurityDVM with automatically generated name from template (thread safe)        
   379     def newSDVM(self):
   380         with new_sdvm_lock:
   381             vm_name = self.genSDVMName()
   382             self.createVM(vm_name)
   383         return vm_name
   384     
   385     # attach storage image to controller
   386     def storageAttach(self, vm_name):
   387         if self.isStorageAttached(vm_name):
   388             self.storageDetach(vm_name)
   389         Cygwin.checkResult(Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 0 --device 0 --type hdd --medium \"'+ VMManager.machineFolder + '\SecurityDVM\SecurityDVM.vmdk\"'))
   390     
   391     # return true if storage is attached 
   392     def isStorageAttached(self, vm_name):
   393         info = self.getVMInfo(vm_name)
   394         return (info['SATA-0-0']!='none')
   395     
   396     # detach storage from controller
   397     def storageDetach(self, vm_name):
   398         if self.isStorageAttached(vm_name):
   399             Cygwin.checkResult(Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 0 --device 0 --type hdd --medium none'))
   400     
   401     def changeStorageType(self, filename, storage_type):
   402         Cygwin.checkResult(Cygwin.vboxExecute('modifyhd \"' + filename + '\" --type ' + storage_type))
   403                 
   404     # list storage snaphots for VM
   405     def updateTemplate(self):
   406         self.stop()
   407         self.cleanup()
   408         self.poweroffVM(self.vmRootName)
   409         self.waitShutdown(self.vmRootName)
   410         
   411         # check for updates
   412         self.genCertificateISO(self.vmRootName)
   413         self.attachCertificateISO(self.vmRootName)
   414         
   415         #templateUUID = self.getVMInfo(self.vmRootName)["SATA-ImageUUID-0-0"] #TODO: // verify value
   416         templateUUID = self.getTemplateUUID()
   417         
   418         self.storageDetach(self.vmRootName)
   419         self.removeSnapshots(templateUUID)
   420         
   421         template_storage = VMManager.machineFolder + '\\' + self.vmRootName + '\\' + self.vmRootName + '.vmdk'
   422         #TODO:// modify to take vm name as argument
   423         self.changeStorageType(template_storage,'normal')
   424         self.storageAttach(self.vmRootName)
   425         self.startVM(self.vmRootName)
   426         self.waitStartup(self.vmRootName, timeout_ms = 30000)
   427         
   428         tmp_ip = self.getHostOnlyIP(self.vmRootName)
   429         tmp_machine_folder = Cygwin.cygPath(VMManager.machineFolder)
   430         Cygwin.checkResult(Cygwin.sshExecute('"sudo apt-get -y update"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key'))
   431         Cygwin.checkResult(Cygwin.sshExecute('"sudo apt-get -y upgrade"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key'))
   432         
   433         #check if reboot is required
   434         result = Cygwin.checkResult(Cygwin.sshExecute('"if [ -f /var/run/reboot-required ]; then echo \\\"Yes\\\"; fi"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key'))
   435         if "Yes" in result[1]:
   436             self.stopVM(self.vmRootName)
   437             self.waitShutdown(self.vmRootName)
   438             self.startVM(self.vmRootName)
   439             self.waitStartup(self.vmRootName)
   440         
   441         #self.hibernateVM(self.vmRootName)
   442         self.stopVM(self.vmRootName)
   443         self.waitShutdown(self.vmRootName)
   444         self.storageDetach(self.vmRootName)
   445         self.changeStorageType(template_storage,'immutable')
   446         self.storageAttach(self.vmRootName)
   447         
   448         #self.start()
   449 
   450     #"SATA-0-0"="C:\Users\BarthaM\VirtualBox VMs\SecurityDVM\Snapshots\{d0af827d-f13a-49be-8ac1-df20b13bda83}.vmdk"
   451     #"SATA-ImageUUID-0-0"="d0af827d-f13a-49be-8ac1-df20b13bda83"
   452     @staticmethod    
   453     def getDiskImages():
   454         results = Cygwin.checkResult(Cygwin.vboxExecute('list hdds'))[1]
   455         results = results.replace('Parent UUID', 'Parent')
   456         items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
   457         
   458         snaps = dict()   
   459         for item in items:
   460             props = dict()
   461             for line in item.splitlines():
   462                 if line != "":         
   463                     k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
   464                     props[k] = v;
   465             snaps[props['UUID']] = props
   466         return snaps
   467     
   468     @staticmethod 
   469     def getTemplateUUID():
   470         images = VMManager.getDiskImages()
   471         template_storage = VMManager.machineFolder + '\\' + VMManager.vmRootName + '\\' + VMManager.vmRootName + '.vmdk'
   472         # find template uuid
   473         template_uuid = None
   474         for hdd in images.values():
   475             if hdd['Location'] == template_storage:
   476                 template_uuid = hdd['UUID']
   477                 break
   478         return template_uuid
   479         
   480     def removeSnapshots(self, imageUUID):
   481         snaps = self.getDiskImages()
   482         # remove snapshots 
   483         for hdd in snaps.values():
   484             if hdd['Parent'] == imageUUID:
   485                 snapshotUUID = hdd['UUID']
   486                 self.removeImage(snapshotUUID)
   487                 
   488     def removeImage(self, imageUUID):
   489         logger.debug('removing snapshot ' + imageUUID)
   490         Cygwin.checkResult(Cygwin.vboxExecute('closemedium disk {' + imageUUID + '} --delete'))#[1]
   491         # parse result 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
   492     
   493     #remove VM from the system. should be used on VMs returned by listSDVMs    
   494     def removeVM(self, vm_name):
   495         logger.info('Removing ' + vm_name)
   496         
   497         Cygwin.checkResult(Cygwin.vboxExecute('unregistervm ' + vm_name + ' --delete'))
   498         #TODO:// try to close medium if still existing
   499         #Cygwin.checkResult(Cygwin.vboxExecute('closemedium disk {' + hdd['UUID'] + '} --delete'))#[1]
   500         self.removeVMFolder(vm_name)
   501     
   502     def removeVMFolder(self, vm_name):
   503         machineFolder = Cygwin.cygPath(VMManager.machineFolder)
   504         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '\\\"'))
   505     
   506     # start VM
   507     def startVM(self, vm_name):
   508         logger.info('Starting ' +  vm_name)
   509         #TODO: modify to use Cygwin.checkResult() of make it retry 3 times
   510         result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless' )
   511         while 'successfully started' not in result[1]:
   512             logger.error("Failed to start SDVM: " + vm_name + " retrying")
   513             logger.error("Command returned:\n" + result[2])
   514             time.sleep(1)
   515             result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless')
   516         return result[0]
   517     
   518     # return wether VM is running or not
   519     def isVMRunning(self, vm_name):
   520         return vm_name in self.listRunningVMS()    
   521     
   522     # stop VM
   523     def stopVM(self, vm_name):
   524         logger.info('Sending shutdown signal to ' + vm_name)
   525         Cygwin.checkResult(Cygwin.sshExecute( '"sudo shutdown -h now"', self.getHostOnlyIP(vm_name), 'osecuser', Cygwin.cygPath(VMManager.machineFolder) + '/' + vm_name + '/dvm_key' ))
   526     
   527     # stop VM
   528     def hibernateVM(self, vm_name):
   529         logger.info('Sending hibernate-disk signal to ' + vm_name)
   530         Cygwin.checkResult(Cygwin.sshBackgroundExecute( '"sudo hibernate-disk"', self.getHostOnlyIP(vm_name), 'osecuser', Cygwin.cygPath(VMManager.machineFolder) + '/' + vm_name + '/dvm_key', wait_return=False))
   531             
   532     # poweroff VM
   533     def poweroffVM(self, vm_name):
   534         if not self.isVMRunning(vm_name):
   535             return
   536         logger.info('Powering off ' + vm_name)
   537         return Cygwin.checkResult(Cygwin.vboxExecute('controlvm ' + vm_name + ' poweroff'))
   538     
   539     # return the hostOnly IP for a running guest or the host    
   540     def getHostOnlyIP(self, vm_name):
   541         if vm_name == None:
   542             logger.info('Getting hostOnly IP address for Host')
   543             #TODO:// optimise to store on init local variable and return that value (avoid calling list hostonlyifs)
   544             return VMManager.hostonlyIFs['IPAddress']
   545         else:
   546             logger.info('Getting hostOnly IP address ' + vm_name)
   547             result = Cygwin.checkResult(Cygwin.vboxExecute('guestproperty get ' + vm_name + ' /VirtualBox/GuestInfo/Net/0/V4/IP'))
   548             if result=='':
   549                 return None
   550             result = result[1]
   551             if result.startswith('No value set!'):
   552                 return None
   553             return result[result.index(':')+1:].strip()
   554         
   555     # return the description set for an existing VM
   556     def getVMInfo(self, vm_name):
   557         results = Cygwin.checkResult(Cygwin.vboxExecute('showvminfo ' + vm_name + ' --machinereadable'))[1]
   558         props = dict((k.strip().strip('"'),v.strip().strip('"')) for k,v in (line.split('=', 1) for line in results.splitlines()))
   559         return props
   560     
   561     #generates ISO containing authorized_keys for use with guest VM
   562     def genCertificateISO(self, vm_name):
   563         machineFolder = Cygwin.cygPath(VMManager.machineFolder)
   564         # remove .ssh folder if exists
   565         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"'))
   566         # remove .ssh folder if exists
   567         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"'))
   568         # create .ssh folder in vm_name
   569         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"'))
   570         # generate dvm_key pair in vm_name / .ssh     
   571         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/ssh-keygen -q -t rsa -N \\\"\\\" -C \\\"' + vm_name + '\\\" -f \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\"'))
   572         # move out private key
   573         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\" \\\"' + machineFolder + '/' + vm_name + '\\\"'))
   574         # set permissions for private key
   575         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"'))
   576         # rename public key to authorized_keys
   577         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key.pub\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"'))
   578         # set permissions for authorized_keys
   579         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"'))
   580         # generate iso image with .ssh/authorized keys
   581         Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/genisoimage -J -R -o \\\"' + machineFolder + '/' + vm_name + '/'+ vm_name + '.iso\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"'))
   582     
   583     # attaches generated ssh public cert to guest vm
   584     def attachCertificateISO(self, vm_name):
   585         result = Cygwin.checkResult(Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 1 --device 0 --type dvddrive --mtype readonly --medium \"' + VMManager.machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\"'))
   586         return result
   587     
   588     # wait for machine to come up
   589     def waitStartup(self, vm_name, timeout_ms = 1000):
   590         Cygwin.checkResult(Cygwin.vboxExecute('guestproperty wait ' + vm_name + ' SDVMStarted --timeout ' + str(timeout_ms) + ' --fail-on-timeout', try_count = 60))
   591         return self.getHostOnlyIP(vm_name)
   592     
   593     # wait for machine to shutdown
   594     def waitShutdown(self, vm_name):
   595         while vm_name in self.listRunningVMS():
   596             time.sleep(1)
   597         return
   598     
   599     #Small function to check if the mentioned location is a directory
   600     def isDirectory(self, path):
   601         result = Cygwin.checkResult(Cygwin.cmdExecute('dir ' + path + ' | FIND ".."'))
   602         return string.find(result[1], 'DIR',)
   603     
   604     def genNetworkDrive(self):
   605         logical_drives = VMManager.getLogicalDrives()
   606         logger.info("Used logical drive letters: "+ str(logical_drives).strip('[]') )
   607         drives = list(map(chr, range(68, 91)))  
   608         for drive in drives:
   609             if drive not in logical_drives:
   610                 return drive
   611             
   612     @staticmethod
   613     def getLogicalDrives():
   614         drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives()
   615         drives = list(itertools.compress(string.ascii_uppercase,  map(lambda x:ord(x) - ord('0'), bin(drive_bitmask)[:1:-1])))
   616         return drives
   617     
   618     @staticmethod
   619     def getDriveType(drive):
   620         return ctypes.cdll.kernel32.GetDriveTypeW(u"%s:\\"%drive)
   621     
   622     @staticmethod
   623     def getNetworkPath(drive):
   624         return win32wnet.WNetGetConnection(drive+':')
   625     
   626     @staticmethod
   627     def getVolumeInfo(drive):
   628         volumeNameBuffer = ctypes.create_unicode_buffer(1024)
   629         fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
   630         serial_number = None
   631         max_component_length = None
   632         file_system_flags = None
   633         
   634         rc = ctypes.cdll.kernel32.GetVolumeInformationW(
   635             u"%s:\\"%drive,
   636             volumeNameBuffer,
   637             ctypes.sizeof(volumeNameBuffer),
   638             serial_number,
   639             max_component_length,
   640             file_system_flags,
   641             fileSystemNameBuffer,
   642             ctypes.sizeof(fileSystemNameBuffer)
   643         )
   644         return volumeNameBuffer.value, fileSystemNameBuffer.value
   645     
   646     def getNetworkDrive(self, vm_name):
   647         ip = self.getHostOnlyIP(vm_name)
   648         if ip == None:
   649             logger.error("Failed getting hostonly IP for " + vm_name)
   650             return None
   651         logger.info("Got IP address for " + vm_name + ': ' + ip)
   652         for drive in VMManager.getLogicalDrives():
   653             #if is a network drive
   654             if VMManager.getDriveType(drive) == 4:
   655                 network_path = VMManager.getNetworkPath(drive)
   656                 if ip in network_path:
   657                     return drive
   658         return None
   659     
   660     def getNetworkDrives(self):
   661         ip = self.getHostOnlyIP(None)
   662         if ip == None:
   663             logger.error("Failed getting hostonly IP for system")
   664             return None
   665         logger.info("Got IP address for system: " + ip)
   666         ip = ip[:ip.rindex('.')]
   667         network_drives = dict()
   668         for drive in VMManager.getLogicalDrives():
   669             #if is a network drive
   670             if VMManager.getDriveType(drive) == 4:
   671                 network_path = VMManager.getNetworkPath(drive)
   672                 if ip in network_path:
   673                     network_drives[drive] = network_path  
   674         return network_drives
   675     
   676     # handles browsing request    
   677     def handleBrowsingRequest(self, proxy):
   678         showTrayMessage('Starting Secure Browsing...', 7000)
   679         handler = BrowsingHandler(self, proxy)
   680         handler.start()
   681         return 'ok'
   682     
   683     def getActiveUserName(self):
   684         key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI')
   685         v = str(win32api.RegQueryValueEx(key, 'LastLoggedOnUser')[0])
   686         win32api.RegCloseKey(key)
   687         user_name = win32api.ExpandEnvironmentStrings(v)
   688         return user_name
   689         
   690     def getUserSID(self, user_name):
   691         domain, user = user_name.split("\\")
   692         account_name = win32security.LookupAccountName(domain, user)
   693         if account_name == None:
   694             logger.error("Failed lookup account name for user " + user_name)
   695             return None
   696         sid = win32security.ConvertSidToStringSid(account_name[0])
   697         if sid == None:
   698             logger.error("Failed converting SID for account " + account_name[0])
   699             return None
   700         return sid
   701         
   702     def getAppDataDir(self, sid):    
   703         key = win32api.RegOpenKey(win32con.HKEY_USERS, sid + '\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
   704         value, type = win32api.RegQueryValueEx(key, "AppData")
   705         win32api.RegCloseKey(key)
   706         return value
   707         
   708         #key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' + '\\' + sid)
   709         #value, type = win32api.RegQueryValueEx(key, "ProfileImagePath")
   710         #print value
   711     
   712     def backupFile(self, src, dest):
   713         certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + self.browsingManager.vm_name + '/dvm_key'
   714         command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "osecuser@' + self.browsingManager.ip_addr + ':' + src + '" "' + dest + '"'
   715         return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
   716     
   717     def restoreFile(self, src, dest):
   718         certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + self.browsingManager.vm_name + '/dvm_key'
   719         #command = '-r -v -o StrictHostKeyChecking=no -i \"' + certificate + '\" \"' + src + '\" \"osecuser@' + self.browsingManager.ip_addr + ':' + dest + '\"'
   720         command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "' + src + '" "osecuser@' + self.browsingManager.ip_addr + ':' + dest + '"'
   721         return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)    
   722 
   723 #handles browsing session creation 
   724 class BrowsingHandler(threading.Thread):
   725     vmm = None
   726     proxy = None
   727     def __init__(self, vmmanager, proxy):
   728         threading.Thread.__init__(self)
   729         self.vmm = vmmanager
   730         self.proxy = proxy
   731         
   732     def run(self):
   733         #browser = '\\\"/usr/bin/chromium; pidof dbus-launch | xargs kill\\\"'
   734         #browser = '\\\"/usr/bin/chromium\\\"'
   735         
   736         try:
   737             if self.proxy:
   738                 browser = '\\\"export http_proxy='+self.proxy+'; /usr/bin/chromium\\\"'
   739             else:
   740                 browser = '\\\"/usr/bin/chromium\\\"'
   741             self.vmm.browsingManager.started.wait() 
   742             result = Cygwin.checkResult(Cygwin.sshExecuteX11(browser, self.vmm.browsingManager.ip_addr, 'osecuser', Cygwin.cygPath(self.vmm.getMachineFolder()) + '/' + self.vmm.browsingManager.vm_name + '/dvm_key'))
   743             self.vmm.backupFile('/home/osecuser/.config/chromium', self.vmm.browsingManager.appDataDir + '/OpenSecurity/')
   744         except:
   745             logger.info("BrowsingHandler closing. Restarting browsing SDVM.")
   746 
   747         self.vmm.browsingManager.restart.set()
   748         
   749             
   750 # handles browsing Vm creation and destruction                    
   751 class BrowsingManager(threading.Thread):   
   752     vmm = None
   753     running = True
   754     restart = None
   755     ip_addr = None
   756     vm_name = None
   757     net_resource = None
   758     appDataDir = None
   759     
   760     def __init__(self, vmmanager):
   761         threading.Thread.__init__(self)
   762         self.vmm = vmmanager
   763         self.restart = threading.Event()
   764         self.started = threading.Event()
   765     
   766     def stop(self):
   767         self.running = False
   768         self.restart.set()   
   769      
   770     def run(self):
   771         while self.running:
   772             self.restart.clear()
   773             self.started.clear()
   774             
   775             if self.net_resource == None:
   776                 logger.info("Missing browsing SDVM's network share. Skipping disconnect")
   777             else:
   778                 try:
   779                     browsing_vm = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+self.net_resource).readline()
   780                     self.net_resource = None
   781                 except urllib2.URLError:
   782                     logger.error("Network share disconnect failed. OpenSecurity Tray client not running.")
   783                     continue
   784             
   785             self.ip_addr = None
   786 
   787             if self.vm_name != None:
   788                 self.vmm.poweroffVM(self.vm_name)
   789                 self.vmm.removeVM(self.vm_name)
   790             
   791             try:
   792                 self.vm_name = self.vmm.newSDVM()
   793                 self.vmm.storageAttach(self.vm_name)
   794                 self.vmm.genCertificateISO(self.vm_name)
   795                 self.vmm.attachCertificateISO(self.vm_name)
   796                 
   797                 self.vmm.startVM(self.vm_name)
   798                 
   799                 self.ip_addr = self.vmm.waitStartup(self.vm_name, timeout_ms=30000)
   800                 if self.ip_addr == None:
   801                     logger.error("Failed to get ip address")
   802                     continue
   803                 else:
   804                     logger.info("Got IP address for " + self.vm_name + ' ' + self.ip_addr)
   805                 
   806                 try:
   807                     self.net_resource = '\\\\' + self.ip_addr + '\\Download'
   808                     result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+self.net_resource).readline()
   809                 except urllib2.URLError:
   810                     logger.error("Network drive connect failed. OpenSecurity Tray client not running.")
   811                     self.net_resource = None
   812                     continue
   813                 
   814                 user = self.vmm.getActiveUserName()
   815                 if user == None:
   816                     logger.error("Cannot get active user name")
   817                     continue
   818                 else:
   819                     logger.info('Got active user name ' + user)
   820                 sid = self.vmm.getUserSID(user)
   821                 if sid == None:
   822                     logger.error("Cannot get SID for active user")
   823                     continue
   824                 else:
   825                     logger.info("Got active user SID " + sid + " for user " + user)
   826                     
   827                 path = self.vmm.getAppDataDir(sid)
   828                 if path == None:
   829                     logger.error("Cannot get AppDataDir for active user")
   830                     continue
   831                 else:
   832                     logger.info("Got AppData dir for user " + user + ': ' + path)
   833                 
   834                 self.appDataDir = Cygwin.cygPath(path)
   835                 logger.info("Restoring browser settings in AppData dir " + self.appDataDir)
   836                 # create OpenSecurity settings dir on local machine user home /AppData/Roaming 
   837                 Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + self.appDataDir + '/OpenSecurity\\\"'))
   838                 # create chromium settings dir on local machine if not existing
   839                 Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + self.appDataDir + '/OpenSecurity/chromium\\\"'))
   840                 # create chromium settings dir on remote machine if not existing
   841                 Cygwin.checkResult(Cygwin.sshExecute('"mkdir -p \\\"/home/osecuser/.config\\\""', self.ip_addr, 'osecuser', Cygwin.cygPath(self.vmm.getMachineFolder()) + '/' + self.vm_name + '/dvm_key'))
   842                 #restore settings on vm
   843                 self.vmm.restoreFile(self.appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
   844                 self.started.set()
   845                 logger.info("Browsing SDVM running.")
   846                 self.restart.wait()
   847             except OpenSecurityException, e:
   848                 logger.error(''.join(e))
   849             except:
   850                 logger.error("Unexpected error: " + sys.exc_info()[0])
   851                 logger.error("BrowsingHandler failed. Cleaning up")
   852                 #self.running= False
   853                 
   854 class DeviceHandler(threading.Thread): 
   855     vmm = None
   856     existingRSDs = None
   857     attachedRSDs = None  
   858     running = True
   859     def __init__(self, vmmanger): 
   860         threading.Thread.__init__(self)
   861         self.vmm = vmmanger
   862  
   863     def stop(self):
   864         self.running = False
   865         
   866     def run(self):
   867         
   868         self.existingRSDs = dict()
   869         self.attachedRSDs = self.vmm.getAttachedRSDs()
   870         
   871         while self.running:
   872             tmp_rsds = self.vmm.getExistingRSDs()
   873             if tmp_rsds.keys() == self.existingRSDs.keys():
   874                 logger.debug("Nothing's changed. sleep(3)")
   875                 time.sleep(3)
   876                 continue
   877             
   878             showTrayMessage('System changed.\nEvaluating...', 7000)
   879             logger.info("Something's changed")
   880             tmp_attached = self.attachedRSDs     
   881             for vm_name in tmp_attached.keys():
   882                 if tmp_attached[vm_name] not in tmp_rsds.values():
   883                     ip = self.vmm.getHostOnlyIP(vm_name)
   884                     if ip == None:
   885                         logger.error("Failed getting hostonly IP for " + vm_name)
   886                         continue
   887                     try:
   888                         net_resource = '\\\\' + ip + '\\USB'
   889                         result = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+net_resource).readline()
   890                     except urllib2.URLError:
   891                         logger.error("Network drive disconnect failed. OpenSecurity Tray client not running.")
   892                         continue
   893                     
   894                     # detach not necessary as already removed from vm description upon disconnect
   895                     #self.vmm.detachRSD(vm_name, self.attachedRSDs[vm_name])
   896                     del self.attachedRSDs[vm_name]
   897                     self.vmm.poweroffVM(vm_name)
   898                     self.vmm.removeVM(vm_name)
   899                     #break
   900                     
   901             #create new vms for new devices if any
   902             new_ip = None
   903             for new_device in tmp_rsds.values():
   904                 showTrayMessage('Mounting device...', 7000)
   905                 if (self.attachedRSDs and False) or (new_device not in self.attachedRSDs.values()):
   906                     new_sdvm = self.vmm.newSDVM()
   907                     self.vmm.storageAttach(new_sdvm)
   908                     self.vmm.startVM(new_sdvm)
   909                     new_ip = self.vmm.waitStartup(new_sdvm, timeout_ms=30000)
   910                     if new_ip == None:
   911                         logger.error("Error getting IP address of SDVM. Cleaning up.")
   912                         self.vmm.poweroffVM(new_sdvm)
   913                         self.vmm.removeVM(new_sdvm)
   914                         continue
   915                     else:
   916                         logger.info("Got IP address for " + new_sdvm + ' ' + new_ip)
   917                     try:
   918                         self.vmm.attachRSD(new_sdvm, new_device)
   919                         self.attachedRSDs[new_sdvm] = new_device
   920                     except:
   921                         logger.info("RSD prematurely removed. Cleaning up.")
   922                         self.vmm.poweroffVM(new_sdvm)
   923                         self.vmm.removeVM(new_sdvm)
   924                         continue
   925                     try:
   926                         net_resource = '\\\\' + new_ip + '\\USB'
   927                         result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+net_resource).readline()
   928                     except urllib2.URLError:
   929                         logger.error("Network drive connect failed (tray client not accessible). Cleaning up.")
   930                         self.vmm.poweroffVM(new_sdvm)
   931                         self.vmm.removeVM(new_sdvm)
   932                         continue
   933                     
   934             self.existingRSDs = tmp_rsds