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