OpenSecurity/bin/vmmanager.pyw
author BarthaM@N3SIM1218.D03.arc.local
Fri, 06 Jun 2014 10:38:21 +0100
changeset 183 a4ea59670b6b
parent 182 6e9b8f105de1
child 193 8d5b7c9ff783
permissions -rwxr-xr-x
implemented blacklist for RSD devices
     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 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         handler = BrowsingHandler(self)
   640         handler.start()
   641         return 'ok'
   642     
   643     def getActiveUserName(self):
   644         key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI')
   645         v = str(win32api.RegQueryValueEx(key, 'LastLoggedOnUser')[0])
   646         win32api.RegCloseKey(key)
   647         user_name = win32api.ExpandEnvironmentStrings(v)
   648         return user_name
   649         
   650     def getUserSID(self, user_name):
   651         domain, user = user_name.split("\\")
   652         account_name = win32security.LookupAccountName(domain, user)
   653         if account_name == None:
   654             logger.error("Failed lookup account name for user " + user_name)
   655             return None
   656         sid = win32security.ConvertSidToStringSid(account_name[0])
   657         if sid == None:
   658             logger.error("Failed converting SID for account " + account_name[0])
   659             return None
   660         return sid
   661         
   662     def getAppDataDir(self, sid):    
   663         key = win32api.RegOpenKey(win32con.HKEY_USERS, sid + '\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
   664         value, type = win32api.RegQueryValueEx(key, "AppData")
   665         win32api.RegCloseKey(key)
   666         return value
   667         
   668         #key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' + '\\' + sid)
   669         #value, type = win32api.RegQueryValueEx(key, "ProfileImagePath")
   670         #print value
   671     
   672     def backupFile(self, src, dest):
   673         certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + self.browsingManager.vm_name + '/dvm_key'
   674         command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "osecuser@' + self.browsingManager.ip_addr + ':' + src + '" "' + dest + '"'
   675         return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
   676     
   677     def restoreFile(self, src, dest):
   678         certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + self.browsingManager.vm_name + '/dvm_key'
   679         #command = '-r -v -o StrictHostKeyChecking=no -i \"' + certificate + '\" \"' + src + '\" \"osecuser@' + self.browsingManager.ip_addr + ':' + dest + '\"'
   680         command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "' + src + '" "osecuser@' + self.browsingManager.ip_addr + ':' + dest + '"'
   681         return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)    
   682 
   683 
   684 #handles browsing session creation 
   685 class BrowsingHandler(threading.Thread):
   686     vmm = None
   687     def __init__(self, vmmanager):
   688          threading.Thread.__init__(self)
   689          self.vmm = vmmanager
   690         
   691     def run(self):
   692         #browser = '\\\"/usr/bin/chromium; pidof dbus-launch | xargs kill\\\"'
   693         browser = '\\\"/usr/bin/chromium\\\"'
   694         try:
   695             self.vmm.browsingManager.started.wait() 
   696             result = Cygwin.checkResult(Cygwin.sshExecuteX11(browser, self.vmm.browsingManager.ip_addr, 'osecuser', Cygwin.cygPath(self.vmm.getMachineFolder()) + '/' + self.vmm.browsingManager.vm_name + '/dvm_key'))
   697             self.vmm.backupFile('/home/osecuser/.config/chromium', self.vmm.browsingManager.appDataDir + '/OpenSecurity/')
   698         except:
   699             logger.info("BrowsingHandler closing. Restarting browsing SDVM.")
   700 
   701         self.vmm.browsingManager.restart.set()
   702         
   703             
   704 # handles browsing Vm creation and destruction                    
   705 class BrowsingManager(threading.Thread):   
   706     vmm = None
   707     running = True
   708     restart = None
   709     ip_addr = None
   710     vm_name = None
   711     net_resource = None
   712     appDataDir = None
   713     
   714     def __init__(self, vmmanager):
   715         threading.Thread.__init__(self)
   716         self.vmm = vmmanager
   717         self.restart = threading.Event()
   718         self.started = threading.Event()
   719     
   720     def stop(self):
   721         self.running = False
   722         self.restart.set()   
   723      
   724     def run(self):
   725         while self.running:
   726             self.restart.clear()
   727             self.started.clear()
   728             
   729             if self.net_resource == None:
   730                 logger.info("Missing browsing SDVM's network share. Skipping disconnect")
   731             else:
   732                 try:
   733                     browsing_vm = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+self.net_resource).readline()
   734                     self.net_resource = None
   735                 except urllib2.URLError:
   736                     logger.error("Network share disconnect failed. OpenSecurity Tray client not running.")
   737                     continue
   738             
   739             self.ip_addr = None
   740 
   741             if self.vm_name != None:
   742                 self.vmm.poweroffVM(self.vm_name)
   743                 self.vmm.removeVM(self.vm_name)
   744             
   745             try:
   746                 self.vm_name = self.vmm.newSDVM()
   747                 self.vmm.storageAttach(self.vm_name)
   748                 self.vmm.genCertificateISO(self.vm_name)
   749                 self.vmm.attachCertificateISO(self.vm_name)
   750                 self.vmm.startVM(self.vm_name)
   751                 self.ip_addr = self.vmm.waitStartup(self.vm_name)
   752                 if self.ip_addr == None:
   753                     logger.error("Failed to get ip address")
   754                     continue
   755                 else:
   756                     logger.info("Got IP address for " + self.vm_name + ' ' + self.ip_addr)
   757                 
   758                 try:
   759                     self.net_resource = '\\\\' + self.ip_addr + '\\Download'
   760                     result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+self.net_resource).readline()
   761                 except urllib2.URLError:
   762                     logger.error("Network drive connect failed. OpenSecurity Tray client not running.")
   763                     self.net_resource = None
   764                     continue
   765                 
   766                 user = self.vmm.getActiveUserName()
   767                 if user == None:
   768                     logger.error("Cannot get active user name")
   769                     continue
   770                 else:
   771                     logger.info('Got active user name ' + user)
   772                 sid = self.vmm.getUserSID(user)
   773                 if sid == None:
   774                     logger.error("Cannot get SID for active user")
   775                     continue
   776                 else:
   777                     logger.info("Got active user SID " + sid + " for user " + user)
   778                     
   779                 path = self.vmm.getAppDataDir(sid)
   780                 if path == None:
   781                     logger.error("Cannot get AppDataDir for active user")
   782                     continue
   783                 else:
   784                     logger.info("Got AppData dir for user " + user + ': ' + path)
   785                 
   786                 self.appDataDir = Cygwin.cygPath(path)
   787                 logger.info("Restoring browser settings in AppData dir " + self.appDataDir)
   788                 # create OpenSecurity settings dir on local machine user home /AppData/Roaming 
   789                 Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + self.appDataDir + '/OpenSecurity\\\"'))
   790                 # create chromium settings dir on local machine if not existing
   791                 Cygwin.checkResult(Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + self.appDataDir + '/OpenSecurity/chromium\\\"'))
   792                 # create chromium settings dir on remote machine if not existing
   793                 Cygwin.checkResult(Cygwin.sshExecute('"mkdir -p \\\"/home/osecuser/.config\\\""', self.ip_addr, 'osecuser', Cygwin.cygPath(self.vmm.getMachineFolder()) + '/' + self.vm_name + '/dvm_key'))
   794                 #restore settings on vm
   795                 self.vmm.restoreFile(self.appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
   796                 self.started.set()
   797                 logger.info("Browsing SDVM running.")
   798                 self.restart.wait()
   799             except:
   800                 logger.error("BrowsingHandler failed. Cleaning up")
   801                 
   802 class DeviceHandler(threading.Thread): 
   803     vmm = None
   804     existingRSDs = None
   805     attachedRSDs = None  
   806     running = True
   807     def __init__(self, vmmanger): 
   808         threading.Thread.__init__(self)
   809         self.vmm = vmmanger
   810  
   811     def stop(self):
   812         self.running = False
   813         
   814     def run(self):
   815         
   816         self.existingRSDs = dict()
   817         self.attachedRSDs = self.vmm.getAttachedRSDs()
   818         
   819         while self.running:
   820             tmp_rsds = self.vmm.getExistingRSDs()
   821             if tmp_rsds.keys() == self.existingRSDs.keys():
   822                 logger.debug("Nothing's changed. sleep(3)")
   823                 time.sleep(3)
   824                 continue
   825             
   826             logger.info("Something's changed")
   827             tmp_attached = self.attachedRSDs     
   828             for vm_name in tmp_attached.keys():
   829                 if tmp_attached[vm_name] not in tmp_rsds.values():
   830                     ip = self.vmm.getHostOnlyIP(vm_name)
   831                     if ip == None:
   832                         logger.error("Failed getting hostonly IP for " + vm_name)
   833                         continue
   834                     try:
   835                         net_resource = '\\\\' + ip + '\\USB'
   836                         result = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+net_resource).readline()
   837                     except urllib2.URLError:
   838                         logger.error("Network drive disconnect failed. OpenSecurity Tray client not running.")
   839                         continue
   840                     
   841                     # detach not necessary as already removed from vm description upon disconnect
   842                     #self.vmm.detachRSD(vm_name, self.attachedRSDs[vm_name])
   843                     del self.attachedRSDs[vm_name]
   844                     self.vmm.poweroffVM(vm_name)
   845                     self.vmm.removeVM(vm_name)
   846                     #break
   847                     
   848             #create new vms for new devices if any
   849             new_ip = None
   850             for new_device in tmp_rsds.values():
   851                 if (self.attachedRSDs and False) or (new_device not in self.attachedRSDs.values()):
   852                     new_sdvm = self.vmm.newSDVM()
   853                     self.vmm.storageAttach(new_sdvm)
   854                     self.vmm.startVM(new_sdvm)
   855                     new_ip = self.vmm.waitStartup(new_sdvm)
   856                     if new_ip == None:
   857                         logger.error("Error getting IP address of SDVM. Cleaning up.")
   858                         self.vmm.poweroffVM(new_sdvm)
   859                         self.vmm.removeVM(new_sdvm)
   860                         continue
   861                     else:
   862                         logger.info("Got IP address for " + new_sdvm + ' ' + new_ip)
   863                     try:
   864                         self.vmm.attachRSD(new_sdvm, new_device)
   865                         self.attachedRSDs[new_sdvm] = new_device
   866                     except:
   867                         logger.info("RSD prematurely removed. Cleaning up.")
   868                         self.vmm.poweroffVM(new_sdvm)
   869                         self.vmm.removeVM(new_sdvm)
   870                         continue
   871                     try:
   872                         net_resource = '\\\\' + new_ip + '\\USB'
   873                         result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+net_resource).readline()
   874                     except urllib2.URLError:
   875                         logger.error("Network drive connect failed (tray client not accessible). Cleaning up.")
   876                         self.vmm.poweroffVM(new_sdvm)
   877                         self.vmm.removeVM(new_sdvm)
   878                         continue
   879                     
   880             self.existingRSDs = tmp_rsds
   881                         
   882 
   883 if __name__ == '__main__':
   884     #man = VMManager.getInstance()
   885     #man.listVM()
   886     #print man.getConnectedRSDs()
   887     #print man.getNetworkDrives()
   888     #man.genNetworkDrive()
   889     #drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives()
   890     #print list(itertools.compress(string.ascii_uppercase,  map(lambda x:ord(x) - ord('0'), bin(drive_bitmask)[:1:-1])))
   891     #print list(map(chr, range(68, 91))) 
   892     #print Cygwin.getRegEntry('SYSTEM\CurrentControlSet\Enum\USB', 'VID_1058&PID_0704')[0]
   893     #devices = VMManager.getConnectedRSDS()
   894     #print devices
   895     
   896     drives = VMManager.getLogicalDrives()
   897     print drives
   898     print VMManager.getDriveType("E")
   899     print VMManager.getVolumeInfo("E")
   900     print VMManager.getNetworkPath("E")
   901     
   902     #vmm.backupFile()
   903     #for device in devices.values():
   904     #    #print device
   905     #    if VMManager.isMassStorageDevice(device):
   906     #        print device
   907         
   908     
   909     
   910     #time.sleep(-1)
   911     #man.listVM()
   912     #man.listVM()
   913     #man.listVM()
   914     #man.listVM()
   915     #man.genCertificateISO('SecurityDVM0')
   916     #man.guestExecute('SecurityDVM0', '/bin/ls -la')
   917     #logger = setupLogger('VMManager')
   918     #c = Cygwin()
   919     
   920     #man.sshExecute('/bin/ls -la', 'SecurityDVM0')
   921     #man.sshExecuteX11('/usr/bin/iceweasel', 'SecurityDVM0')
   922     #man.removeVM('SecurityDVM0')
   923     #man.netUse('192.168.56.134', 'USB\\')
   924     #ip = '192.168.56.139'
   925     
   926     #man.cygwin_path = 'c:\\cygwin64\\bin\\'
   927     #man.handleDeviceChange()
   928     #print man.listSDVM()
   929     #man.configureHostNetworking()
   930     #new_vm = man.generateSDVMName()
   931     #man.createVM(new_vm)
   932     
   933     #print Cygwin.cmd()
   934     #man.isAvailable('c:')
   935     #ip = man.getHostOnlyIP('SecurityDVM0')
   936     #man.mapNetworkDrive('h:', '\\\\' + ip + '\Download', None, None)
   937     
   938     #man.genCertificateISO(new_vm)
   939     #man.attachCertificateISO(new_vm)
   940     
   941     #man.attachCertificateISO(vm_name)
   942     #man.guestExecute(vm_name, "ls")
   943     #man.sshGuestX11Execute('SecurityDVM1', '/usr/bin/iceweasel')
   944     #time.sleep(60)
   945     #print man.cygwinPath("C:\Users\BarthaM\VirtualBox VMs\SecurityDVM\.ssh\*")
   946     #man.genCertificateISO('SecurityDVM')
   947     #man.attachCertificateISO('SecurityDVM')
   948     #man.isStorageAttached('SecurityDVM')
   949     #man.guestExecute('SecurityDVM', 'sudo apt-get -y update')
   950     #man.guestExecute('SecurityDVM', 'sudo apt-get -y upgrade' )
   951     
   952     #man.stopVM('SecurityDVM')
   953     #man.storageDetach('SecurityDVM')
   954     #man.changeStorageType('C:\Users\BarthaM\VirtualBox VMs\SecurityDVM\SecurityDVM.vmdk','immutable')
   955     #man.storageAttach('SecurityDVM')
   956     
   957     
   958     #cmd = "c:\\cygwin64\\bin\\bash.exe --login -c \"/bin/ls\""
   959     #man.execute(cmd)