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