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