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