1<?php
2/**
3 *
4 * Connects to vboxwebsrv, calls SOAP methods, and returns data.
5 *
6 * @author Ian Moore (imoore76 at yahoo dot com)
7 * @copyright Copyright (C) 2010-2015 Ian Moore (imoore76 at yahoo dot com)
8 * @version $Id: vboxconnector.php 599 2015-07-27 10:40:37Z imoore76 $
9 * @package phpVirtualBox
10 *
11 */
12
13class vboxconnector {
14
15	/**
16	 * Error with safe HTML
17	 * @var integer
18	 */
19	const PHPVB_ERRNO_HTML = 1;
20
21	/**
22	 * Error number describing a fatal error
23	 * @var integer
24	 */
25	const PHPVB_ERRNO_FATAL = 32;
26
27	/**
28	 * Error number describing a connection error
29	 * @var integer
30	 */
31	const PHPVB_ERRNO_CONNECT = 64;
32
33	/**
34	 * phpVirtualBox groups extra value key
35	 * @var string
36	 */
37	const phpVboxGroupKey = 'phpvb/Groups';
38
39	/**
40	 * Holds any errors that occur during processing. Errors are placed in here
41	 * when we want calling functions to be aware of the error, but do not want to
42	 * halt processing
43	 *
44	 * @var array
45	 */
46	var $errors = array();
47
48	/**
49	 * Holds any debug messages
50	 *
51	 * @var array
52	 */
53	var $messages = array();
54
55	/**
56	 * Settings object
57	 * @var phpVBoxConfigClass
58	 * @see phpVBoxConfigClass
59	 */
60	var $settings = null;
61
62	/**
63	 * true if connected to vboxwebsrv
64	 * @var boolean
65	 */
66	var $connected = false;
67
68	/**
69	 * IVirtualBox instance
70	 * @var IVirtualBox
71	 */
72	var $vbox = null;
73
74	/**
75	 * VirtualBox web session manager
76	 * @var IWebsessionManager
77	 */
78	var $websessionManager = null;
79
80	/**
81	 * Holds IWebsessionManager session object if created
82	 * during processing so that it can be properly shutdown
83	 * in __destruct
84	 * @var ISession
85	 * @see vboxconnector::__destruct()
86	 */
87	var $session = null;
88
89	/**
90	 * Holds VirtualBox version information
91	 * @var array
92	 */
93	var $version = null;
94
95	/**
96	 * If true, vboxconnector will not verify that there is a valid
97	 * (PHP) session before connecting.
98	 * @var boolean
99	 */
100	var $skipSessionCheck = false;
101
102	/**
103	 * Holds items that should persist accross requests
104	 * @var array
105	 */
106	var $persistentRequest = array();
107
108	/**
109	 * Holds VirtualBox host OS specific directory separator set by getDSep()
110	 * @var string
111	 * @see vboxconnector::getDsep()
112	 */
113	var $dsep = null;
114
115	/**
116	 * Obtain configuration settings and set object vars
117	 * @param boolean $useAuthMaster use the authentication master obtained from configuration class
118	 * @see phpVBoxConfigClass
119	 */
120	public function __construct($useAuthMaster = false) {
121
122		require_once(dirname(__FILE__).'/language.php');
123		require_once(dirname(__FILE__).'/vboxServiceWrappers.php');
124
125		/* Set up.. .. settings */
126
127		/** @var phpVBoxConfigClass */
128		$this->settings = new phpVBoxConfigClass();
129
130		// Are default settings being used?
131		if(@$this->settings->warnDefault) {
132			throw new Exception("No configuration found. Rename the file <b>config.php-example</b> in phpVirtualBox's folder to ".
133					"<b>config.php</b> and edit as needed.<p>For more detailed instructions, please see the installation wiki on ".
134					"phpVirtualBox's web site. <p><a href='https://github.com/phpvirtualbox/phpvirtualbox/wiki' target=_blank>".
135					"https://github.com/phpvirtualbox/phpvirtualbox/wiki</a>.</p>",
136						(vboxconnector::PHPVB_ERRNO_FATAL + vboxconnector::PHPVB_ERRNO_HTML));
137		}
138
139		// Check for SoapClient class
140		if(!class_exists('SoapClient')) {
141			throw new Exception('PHP does not have the SOAP extension enabled.',vboxconnector::PHPVB_ERRNO_FATAL);
142		}
143
144		// use authentication master server?
145		if(@$useAuthMaster) {
146			$this->settings->setServer($this->settings->getServerAuthMaster());
147		}
148
149	}
150
151	/**
152	 * Connect to vboxwebsrv
153	 * @see SoapClient
154	 * @see phpVBoxConfigClass
155	 * @return boolean true on success or if already connected
156	 */
157	public function connect() {
158
159		// Already connected?
160		if(@$this->connected)
161			return true;
162
163		// Valid session?
164		if(!@$this->skipSessionCheck && !$_SESSION['valid']) {
165			throw new Exception(trans('Not logged in.','UIUsers'),vboxconnector::PHPVB_ERRNO_FATAL);
166		}
167
168		// Persistent server?
169		if(@$this->persistentRequest['vboxServer']) {
170			$this->settings->setServer($this->persistentRequest['vboxServer']);
171		}
172
173		//Connect to webservice
174		$pvbxver = substr(@constant('PHPVBOX_VER'),0,(strpos(@constant('PHPVBOX_VER'),'-')));
175		$this->client = new SoapClient(dirname(__FILE__)."/vboxwebService-".$pvbxver.".wsdl",
176		    array(
177		    	'features' => (SOAP_USE_XSI_ARRAY_TYPE + SOAP_SINGLE_ELEMENT_ARRAYS),
178		        'cache_wsdl' => WSDL_CACHE_BOTH,
179		        'trace' => (@$this->settings->debugSoap),
180				'connection_timeout' => (@$this->settings->connectionTimeout ? $this->settings->connectionTimeout : 20),
181		        'location' => @$this->settings->location
182		    ));
183
184
185		// Persistent handles?
186		if(@$this->persistentRequest['vboxHandle']) {
187
188			try {
189
190				// Check for existing sessioin
191				$this->websessionManager = new IWebsessionManager($this->client);
192				$this->vbox = new IVirtualBox($this->client, $this->persistentRequest['vboxHandle']);
193
194				// force valid vbox check
195				$ev = $this->vbox->eventSource;
196
197				if($this->vbox->handle)
198					return ($this->connected = true);
199
200
201			} catch (Exception $e) {
202				// nothing. Fall through to new login.
203
204			}
205		}
206
207		/* Try / catch / throw here hides login credentials from exception if one is thrown */
208		try {
209			$this->websessionManager = new IWebsessionManager($this->client);
210			$this->vbox = $this->websessionManager->logon($this->settings->username,$this->settings->password);
211
212
213		} catch (Exception $e) {
214
215			if(!($msg = $e->getMessage()))
216				$msg = 'Error logging in to vboxwebsrv.';
217			else
218				$msg .= " ({$this->settings->location})";
219
220			throw new Exception($msg,vboxconnector::PHPVB_ERRNO_CONNECT);
221		}
222
223
224		// Error logging in
225		if(!$this->vbox->handle) {
226			throw new Exception('Error logging in or connecting to vboxwebsrv.',vboxconnector::PHPVB_ERRNO_CONNECT);
227		}
228
229		// Hold handle
230		if(array_key_exists('vboxHandle',$this->persistentRequest)) {
231			$this->persistentRequest['vboxHandle'] = $this->vbox->handle;
232		}
233
234		return ($this->connected = true);
235
236	}
237
238
239	/**
240	 * Get VirtualBox version
241	 * @return array version information
242	 */
243	public function getVersion() {
244
245		if(!@$this->version) {
246
247			$this->connect();
248
249			$this->version = explode('.',$this->vbox->version);
250			$this->version = array(
251				'ose' => (stripos($this->version[2],'ose') > 0),
252				'string' => join('.',$this->version),
253				'major' => intval(array_shift($this->version)),
254				'minor' => intval(array_shift($this->version)),
255				'sub' => intval(array_shift($this->version)),
256				'revision' => (string)$this->vbox->revision,
257				'settingsFilePath' => $this->vbox->settingsFilePath
258			);
259		}
260
261		return $this->version;
262
263	}
264
265	/**
266	 *
267	 * Log out of vboxwebsrv
268	 */
269	public function __destruct() {
270
271		// Do not logout if there are persistent handles
272		if($this->connected && @$this->vbox->handle && !array_key_exists('vboxHandle' ,$this->persistentRequest)) {
273
274			// Failsafe to close session
275			if(@$this->session && @(string)$this->session->state == 'Locked') {
276				try {$this->session->unlockMachine();}
277				catch (Exception $e) { }
278			}
279
280			// Logoff
281			if($this->vbox->handle)
282				$this->websessionManager->logoff($this->vbox->handle);
283
284		}
285
286		unset($this->client);
287	}
288
289	/**
290	 * Add a machine event listener to the listener list
291	 *
292	 * @param string $vm id of virtual machine to subscribe to
293	 */
294	private function _machineSubscribeEvents($vm) {
295
296		// Check for existing listener
297		if($this->persistentRequest['vboxEventListeners'][$vm]) {
298
299			try {
300
301				$listener = new IEventListener($this->client, $this->persistentRequest['vboxEventListeners'][$vm]['listener']);
302				$source = new IEventSource($this->client, $this->persistentRequest['vboxEventListeners'][$vm]['source']);
303
304				$source->unregisterListener($listener);
305
306				$listener->releaseRemote();
307				$source->releaseRemote();
308
309			} catch (Exception $e) {
310				// Pass
311			}
312		}
313
314		try {
315
316			/* @var $machine IMachine */
317			$machine = $this->vbox->findMachine($vm);
318
319			/* Ignore if not running */
320			$state = (string)$machine->state;
321			if($state != 'Running' && $state != 'Paused') {
322				$machine->releaseRemote();
323				return;
324			}
325
326			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
327			$machine->lockMachine($this->session->handle, 'Shared');
328
329			// Create and register event listener
330			$listener = $this->session->console->eventSource->createListener();
331			$this->session->console->eventSource->registerListener($listener,array('Any'), false);
332
333			// Add to event listener list
334			$this->persistentRequest['vboxEventListeners'][$vm] = array(
335					'listener' => $listener->handle,
336					'source' => $this->session->console->eventSource->handle);
337
338
339			$machine->releaseRemote();
340
341		} catch (Exception $e) {
342			// pass
343		}
344
345		if($this->session) {
346			try {
347				$this->session->unlockMachine();
348			} catch (Exception $e) {
349				// pass
350			}
351			unset($this->session);
352		}
353
354		// Machine events before vbox events. This is in place to handle the "DrvVD_DEKMISSING"
355		// IRuntimeErrorEvent which tells us that a medium attached to a VM requires a password.
356		// This event needs to be presented to the client before the VM state change. This way
357		// the client can track whether or not the runtime error occurred in response to its
358		// startup request because the machine's RunTimeError will occur before vbox's
359		// StateChange.
360		uksort($this->persistentRequest['vboxEventListeners'], function($a, $b){
361		    if($a == 'vbox') return 1;
362		    if($b == 'vbox') return -1;
363		    return 0;
364		});
365
366	}
367
368	/**
369	 * Get pending vbox and machine events
370	 *
371	 * @param array $args array of arguments. See function body for details.
372	 * @return array list of events
373	 */
374	public function remote_getEvents($args) {
375
376		$this->connect();
377
378		$eventlist = array();
379
380		// This should be an array
381		if(!is_array($this->persistentRequest['vboxEventListeners'])) {
382
383				$this->persistentRequest['vboxEventListeners'] = array();
384				$listenerWait = 1000;
385
386		} else {
387
388			// The amount of time we will wait for events is determined by
389			// the amount of listeners - at least half a second
390			$listenerWait = max(100,intval(500/count($this->persistentRequest['vboxEventListeners'])));
391		}
392
393		// Get events from each configured event listener
394		foreach($this->persistentRequest['vboxEventListeners'] as $k => $el) {
395
396			try {
397
398				$listener = new IEventListener($this->client, $el['listener']);
399				$source = new IEventSource($this->client, $el['source']);
400
401				$event = $source->getEvent($listener,$listenerWait);
402
403				try {
404
405					while($event->handle) {
406
407						$eventData = $this->_getEventData($event, $k);
408						$source->eventProcessed($listener, $event);
409						$event->releaseRemote();
410
411
412						// Only keep the last event of one particular type
413						//$eventlist[$eventData['dedupId']] = $eventData;
414
415						if($eventData)
416						    $eventlist[$eventData['dedupId']] = $eventData;
417
418						$event = $source->getEvent($listener,100);
419					}
420
421				} catch (Exception $e) {
422
423					$this->errors[] = $e;
424
425				}
426
427			} catch (Exception $e) {
428
429				// Machine powered off or client has stale MO reference
430				if($listener)
431					try { $listener->releaseRemote(); } catch (Exceptoin $e) {
432						/// pass
433					}
434				if($source)
435					try { $source->releaseRemote(); } catch (Exceptoin $e) {
436						// pass
437					}
438
439				// Remove listener from list
440				unset($this->persistentRequest['vboxEventListeners'][$k]);
441
442			}
443
444		}
445
446		// Enrich events
447		foreach($eventlist as $k=>$event) {
448
449			switch($event['eventType']) {
450
451				/* Network adapter changed */
452				case 'OnNetworkAdapterChanged':
453
454					try {
455
456						$machine = $this->vbox->findMachine($event['sourceId']);
457						$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
458
459						// Session locked?
460						if((string)$this->session->state != 'Unlocked')
461							$this->session->unlockMachine();
462
463						$machine->lockMachine($this->session->handle, 'Shared');
464
465						try {
466
467							list($eventlist[$k]['enrichmentData']) = $this->_machineGetNetworkAdapters($this->session->machine, $event['networkAdapterSlot']);
468
469						} catch (Exception $e) {
470							// Just unlock the machine
471							$eventlist[$k]['enrichmentData'] = array($e->getMessage());
472						}
473
474						$this->session->unlockMachine();
475						$machine->releaseRemote();
476
477					} catch (Exception $e) {
478						$eventlist[$k]['enrichmentData'] = array($e->getMessage());
479					}
480					break;
481
482
483				/* VRDE server changed */
484				case 'OnVRDEServerChanged':
485					try {
486
487						$machine = $this->vbox->findMachine($event['sourceId']);
488						$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
489
490						// Session locked?
491						if((string)$this->session->state != 'Unlocked')
492							$this->session->unlockMachine();
493
494						$machine->lockMachine($this->session->handle, 'Shared');
495						$vrde = $this->session->machine->VRDEServer;
496
497						try {
498							$eventlist[$k]['enrichmentData'] = (!$vrde ? null : array(
499								'enabled' => $vrde->enabled,
500								'ports' => $vrde->getVRDEProperty('TCP/Ports'),
501								'netAddress' => $vrde->getVRDEProperty('TCP/Address'),
502								'VNCPassword' => $vrde->getVRDEProperty('VNCPassword'),
503								'authType' => (string)$vrde->authType,
504								'authTimeout' => $vrde->authTimeout
505								)
506							);
507						} catch (Exception $e) {
508							// Just unlock the machine
509							$eventlist[$k]['enrichmentData'] = array($e->getMessage());
510						}
511
512						$this->session->unlockMachine();
513						$machine->releaseRemote();
514
515					} catch (Exception $e) {
516						$eventlist[$k]['enrichmentData'] = array($e->getMessage());
517					}
518					break;
519
520
521
522				/* VRDE server info changed. Just need port and enabled/disabled */
523				case 'OnVRDEServerInfoChanged':
524					try {
525
526						$machine = $this->vbox->findMachine($event['sourceId']);
527						$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
528
529						// Session locked?
530						if((string)$this->session->state != 'Unlocked')
531							$this->session->unlockMachine();
532
533						$machine->lockMachine($this->session->handle, 'Shared');
534
535						try {
536							$eventlist[$k]['enrichmentData'] = array(
537									'port' => $this->session->console->VRDEServerInfo->port,
538									'enabled' => $this->session->machine->VRDEServer->enabled
539							);
540						} catch (Exception $e) {
541							// Just unlock the machine
542							$eventlist[$k]['enrichmentData'] = array($e->getMessage());
543						}
544
545						$this->session->unlockMachine();
546						$machine->releaseRemote();
547
548					} catch (Exception $e) {
549						$eventlist[$k]['enrichmentData'] = array($e->getMessage());
550					}
551					break;
552
553				/* Machine registered */
554				case 'OnMachineRegistered':
555
556					if(!$event['registered']) break;
557
558					// Get same data that is in VM list data
559					$vmdata = $this->remote_vboxGetMachines(array('vm'=>$event['machineId']));
560					$eventlist[$k]['enrichmentData'] = $vmdata[0];
561					unset($vmdata);
562
563					break;
564
565				/* enrich with basic machine data */
566				case 'OnMachineDataChanged':
567
568					try {
569
570						$machine = $this->vbox->findMachine($event['machineId']);
571
572						if($this->settings->phpVboxGroups) {
573							$groups = explode(',',$machine->getExtraData(vboxconnector::phpVboxGroupKey));
574							if(!is_array($groups) || (count($groups) == 1 && !$groups[0])) $groups = array("/");
575						} else {
576							$groups = $machine->groups;
577						}
578
579						usort($groups, 'strnatcasecmp');
580
581						$eventlist[$k]['enrichmentData'] = array(
582								'id' => $event['machineId'],
583								'name' => @$this->settings->enforceVMOwnership ? preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $machine->name) : $machine->name,
584								'OSTypeId' => $machine->getOSTypeId(),
585								'owner' => (@$this->settings->enforceVMOwnership ? $machine->getExtraData("phpvb/sso/owner") : ''),
586								'groups' => $groups
587						);
588						$machine->releaseRemote();
589
590					} catch (Exception $e) {
591						// pass
592					}
593					break;
594
595				/* Update lastStateChange on OnMachineStateChange events */
596				case 'OnMachineStateChanged':
597					try {
598
599						$machine = $this->vbox->findMachine($event['machineId']);
600						$eventlist[$k]['enrichmentData'] = array(
601							'lastStateChange' => (string)($machine->lastStateChange/1000),
602							'currentStateModified' => $machine->currentStateModified
603						);
604						$machine->releaseRemote();
605
606					} catch (Exception $e) {
607						$eventlist[$k]['enrichmentData'] = array('lastStateChange' => 0);
608					}
609					break;
610
611				/* enrich with snapshot name and new snapshot count*/
612				case 'OnSnapshotTaken':
613				case 'OnSnapshotDeleted':
614				case 'OnSnapshotRestored':
615				case 'OnSnapshotChanged':
616
617					try {
618						$machine = $this->vbox->findMachine($event['machineId']);
619						$eventlist[$k]['enrichmentData'] = array(
620								'currentSnapshotName' => ($machine->currentSnapshot->handle ? $machine->currentSnapshot->name : ''),
621								'snapshotCount' => $machine->snapshotCount,
622								'currentStateModified' => $machine->currentStateModified
623						);
624						$machine->releaseRemote();
625
626					} catch (Exception $e) {
627						// pass
628						$this->errors[] = $e;
629					}
630					break;
631
632			}
633
634		}
635
636		return array_values($eventlist);
637
638	}
639
640	/**
641	 * Subscribe to a single machine's events
642	 *
643	 * @param array $args array of arguments. See function body for details.
644	 * @return boolean true on success
645	 */
646	public function remote_machineSubscribeEvents($args) {
647
648		$this->connect();
649		foreach($args['vms'] as $vm)
650			$this->_machineSubscribeEvents($vm);
651
652		return true;
653	}
654
655	/**
656	 * Unsubscribe from vbox and machine events
657	 *
658	 * @param array $args array of arguments. See function body for details.
659	 * @return boolean true on success
660	 */
661	public function remote_unsubscribeEvents($args) {
662
663		$this->connect();
664
665		if(!is_array($this->persistentRequest['vboxEventListeners']))
666			$this->persistentRequest['vboxEventListeners'] = array();
667
668		// Get events from each configured event listener
669		foreach($this->persistentRequest['vboxEventListeners'] as $k => $el) {
670
671			try {
672
673				$listener = new IEventListener($this->client, $el['listener']);
674				$source = new IEventSource($this->client, $el['source']);
675
676				$source->unregisterListener($listener);
677
678				$source->releaseRemote();
679				$listener->releaseRemote();
680
681
682
683			} catch (Exception $e) {
684				$this->errors[] = $e;
685			}
686
687			$this->persistentRequest['vboxEventListeners'][$k] = null;
688
689		}
690
691		$this->websessionManager->logoff($this->vbox->handle);
692		unset($this->vbox);
693
694		return true;
695	}
696
697	/**
698	 * Subscribe to vbox and machine events
699	 *
700	 * @param array $args array of arguments. See function body for details.
701	 * @return boolean true on success
702	 */
703	public function remote_subscribeEvents($args) {
704
705		$this->connect();
706
707		// Check for existing listener
708		if($this->persistentRequest['vboxEventListeners']['vbox']) {
709
710			try {
711
712				$listener = new IEventListener($this->client, $this->persistentRequest['vboxEventListeners']['vbox']['listener']);
713				$source = new IEventSource($this->client, $this->persistentRequest['vboxEventListeners']['vbox']['source']);
714
715				$source->unregisterListener($listener);
716
717				$listener->releaseRemote();
718				$source->releaseRemote();
719
720			} catch (Exception $e) {
721				// Pass
722			}
723		}
724
725		// Create and register event listener
726		$listener = $this->vbox->eventSource->createListener();
727		$this->vbox->eventSource->registerListener($listener,array('MachineEvent', 'SnapshotEvent', 'OnMediumRegistered', 'OnExtraDataChanged', 'OnSnapshotRestored'), false);
728
729		// Add to event listener list
730		$this->persistentRequest['vboxEventListeners']['vbox'] = array(
731			'listener' => $listener->handle,
732			'source' => $this->vbox->eventSource->handle);
733
734		// Subscribe to each machine in list
735		foreach($args['vms'] as $vm) {
736			$this->_machineSubscribeEvents($vm);
737		}
738
739		$this->persistentRequest['vboxHandle'] = $this->vbox->handle;
740
741		return true;
742
743	}
744
745	/**
746	 * Return relevant event data for the event.
747	 *
748	 * @param IEvent $event
749	 * @param String $listenerKey Key of event listener - 'vbox' or
750	 * 		machine id
751	 * @return array associative array of event attributes
752	 */
753	private function _getEventData($event, $listenerKey) {
754
755		$data = array('eventType'=>(string)$event->type,'sourceId'=>$listenerKey);
756
757		// Convert to parent class
758		$parentClass = 'I'.substr($data['eventType'],2).'Event';
759		$eventDataObject = new $parentClass($this->client, $event->handle);
760
761		// Dedup ID is at least listener key ('vbox' or machine id) and event type
762		$data['dedupId'] = $listenerKey.'-'.$data['eventType'];
763
764		switch($data['eventType']) {
765
766			case 'OnMachineStateChanged':
767				$data['machineId'] = $eventDataObject->machineId;
768				$data['state'] = (string)$eventDataObject->state;
769				$data['dedupId'] .= '-'. $data['machineId'];
770		        break;
771
772			case 'OnMachineDataChanged':
773		        $data['machineId'] = $eventDataObject->machineId;
774		        $data['dedupId'] .= '-'. $data['machineId'];
775		        break;
776
777	        case 'OnExtraDataCanChange':
778			case 'OnExtraDataChanged':
779		        $data['machineId'] = $eventDataObject->machineId;
780		        $data['key'] = $eventDataObject->key;
781		        $data['value'] = $eventDataObject->value;
782		        $data['dedupId'] .= '-'. $data['machineId'] .'-' . $data['key'];
783		        break;
784
785			case 'OnMediumRegistered':
786				$data['machineId'] = $data['sourceId'];
787		        $data['mediumId'] = $eventDataObject->mediumId;
788		        $data['registered'] = $eventDataObject->registered;
789		        $data['dedupId'] .= '-'. $data['mediumId'];
790		        break;
791
792			case 'OnMachineRegistered':
793		        $data['machineId'] = $eventDataObject->machineId;
794		        $data['registered'] = $eventDataObject->registered;
795		        $data['dedupId'] .= '-'. $data['machineId'];
796		        break;
797
798			case 'OnSessionStateChanged':
799				$data['machineId'] = $eventDataObject->machineId;
800				$data['state'] = (string)$eventDataObject->state;
801				$data['dedupId'] .= '-'. $data['machineId'];
802		        break;
803
804		    /* Snapshot events */
805			case 'OnSnapshotTaken':
806			case 'OnSnapshotDeleted':
807			case 'OnSnapshotRestored':
808			case 'OnSnapshotChanged':
809				$data['machineId'] = $eventDataObject->machineId;
810				$data['snapshotId'] = $eventDataObject->snapshotId;
811				$data['dedupId'] .= '-'. $data['machineId'] .'-' . $data['snapshotId'];
812		        break;
813
814			case 'OnGuestPropertyChanged':
815				$data['machineId'] = $eventDataObject->machineId;
816				$data['name'] = $eventDataObject->name;
817				$data['value'] = $eventDataObject->value;
818				$data['flags'] = $eventDataObject->flags;
819				$data['dedupId'] .= '-'. $data['machineId'] .'-' . $data['name'];
820		        break;
821
822			case 'OnCPUChanged':
823				$data['machineId'] = $data['sourceId'];
824				$data['cpu'] = $eventDataObject->cpu;
825				$data['add'] = $eventDataObject->add;
826				$data['dedupId'] .= '-' . $data['cpu'];
827				break;
828
829			/* Same end-result as network adapter changed */
830			case 'OnNATRedirect':
831				$data['machineId'] = $data['sourceId'];
832				$data['eventType'] = 'OnNetworkAdapterChanged';
833				$data['networkAdapterSlot'] = $eventDataObject->slot;
834				$data['dedupId'] = $listenerKey .'-OnNetworkAdapterChanged-'. $data['networkAdapterSlot'];
835				break;
836
837			case 'OnNetworkAdapterChanged':
838				$data['machineId'] = $data['sourceId'];
839		        $data['networkAdapterSlot'] = $eventDataObject->networkAdapter->slot;
840		        $data['dedupId'] .= '-'. $data['networkAdapterSlot'];
841		        break;
842
843	        /* Storage controller of VM changed */
844	        case 'OnStorageControllerChanged':
845	        	$data['machineId'] = $eventDataObject->machineId;
846	        	$data['dedupId'] .= '-'. $data['machineId'];
847	        	break;
848
849	        /* Medium attachment changed */
850	        case 'OnMediumChanged':
851	        	$data['machineId'] = $data['sourceId'];
852	        	$ma = $eventDataObject->mediumAttachment;
853	        	$data['controller'] = $ma->controller;
854	        	$data['port'] = $ma->port;
855	        	$data['device'] = $ma->device;
856	        	try {
857	        		$data['medium'] = $ma->medium->id;
858	        	} catch (Exception $e) {
859	        		$data['medium'] = '';
860	        	}
861	        	$data['dedupId'] .= '-'. $data['controller'] .'-'. $data['port'] .'-'.$data['device'];
862	        	break;
863
864	        /* Generic machine changes that should query IMachine */
865	        case 'OnVRDEServerChanged':
866	        	$data['machineId'] = $data['sourceId'];
867	        	break;
868	        case 'OnUSBControllerChanged':
869	        	$data['machineId'] = $data['sourceId'];
870	        	break;
871	        case 'OnSharedFolderChanged':
872	        	$data['machineId'] = $data['sourceId'];
873	        	$data['scope'] = (string)$eventDataObject->scope;
874	        	break;
875	        case 'OnVRDEServerInfoChanged':
876	        	$data['machineId'] = $data['sourceId'];
877	        	break;
878	        case 'OnCPUExecutionCapChanged':
879	        	$data['machineId'] = $data['sourceId'];
880	        	$data['executionCap'] = $eventDataObject->executionCap;
881	        	break;
882
883
884        	/* Notification when a USB device is attached to or detached from the virtual USB controller */
885	        case 'OnUSBDeviceStateChanged':
886	        	$data['machineId'] = $data['sourceId'];
887	        	$data['deviceId'] = $eventDataObject->device->id;
888	        	$data['attached'] = $eventDataObject->attached;
889	        	$data['dedupId'] .= '-'. $data['deviceId'];
890	        	break;
891
892	        /* Machine execution error */
893	        case 'OnRuntimeError':
894	        	$data['id'] = (string)$eventDataObject->id;
895	        	$data['machineId'] = $data['sourceId'];
896	        	$data['message'] = $eventDataObject->message;
897	        	$data['fatal'] = $eventDataObject->fatal;
898	        	$data['dedupId'] .= '-' . $data['id'];
899	        	break;
900
901	        /* Notification when a storage device is attached or removed. */
902        	case 'OnStorageDeviceChanged':
903        		$data['machineId'] = $eventDataObject->machineId;
904        		$data['storageDevice'] = $eventDataObject->storageDevice;
905        		$data['removed'] = $eventDataObject->removed;
906        		break;
907
908        	/* On nat network delete / create */
909        	case 'OnNATNetworkCreationDeletion':
910        		$data['creationEvent'] = $eventDataObject->creationEvent;
911        	/* NAT network change */
912        	case 'OnNATNetworkSetting':
913        		$data['networkName'] = $eventDataObject->networkName;
914        		$data['dedupId'] .= '-' . $data['networkName'];
915        		break;
916
917        	default:
918        	    return null;
919		}
920
921
922		return $data;
923
924	}
925
926
927	/**
928	 * Call overloader.
929	 * Returns result of method call. Here is where python's decorators would come in handy.
930	 *
931	 * @param string $fn method to call
932	 * @param array $args arguments for method
933	 * @throws Exception
934	 * @return array
935	 */
936	function __call($fn,$args) {
937
938		// Valid session?
939		global $_SESSION;
940
941		if(!@$this->skipSessionCheck && !$_SESSION['valid']) {
942			throw new Exception(trans('Not logged in.','UIUsers'),vboxconnector::PHPVB_ERRNO_FATAL);
943		}
944
945		$req = &$args[0];
946
947
948		# Access to undefined methods prefixed with remote_
949		if(method_exists($this,'remote_'.$fn)) {
950
951			$args[1][0]['data']['responseData'] = $this->{'remote_'.$fn}($req);
952			$args[1][0]['data']['success'] = ($args[1][0]['data']['responseData'] !== false);
953			$args[1][0]['data']['key'] = $this->settings->key;
954
955		// Not found
956		} else {
957
958			throw new Exception('Undefined method: ' . $fn ." - Clear your web browser's cache.",vboxconnector::PHPVB_ERRNO_FATAL);
959
960		}
961
962		return true;
963	}
964
965	/**
966	 * Enumerate guest properties of a vm
967	 *
968	 * @param array $args array of arguments. See function body for details.
969	 * @return array of guest properties
970	 */
971	public function remote_machineEnumerateGuestProperties($args) {
972
973		$this->connect();
974
975		/* @var $m IMachine */
976		$m = $this->vbox->findMachine($args['vm']);
977
978		$props = $m->enumerateGuestProperties($args['pattern']);
979		$m->releaseRemote();
980
981		return $props;
982
983	}
984
985	/**
986	 * Set extra data of a vm
987	 *
988	 * @param array $args array of arguments. See function body for details.
989	 * @return array of extra data
990	 */
991	public function remote_machineSetExtraData($args) {
992
993		$this->connect();
994
995		/* @var $m IMachine */
996		$m = $this->vbox->findMachine($args['vm']);
997
998		$m->setExtraData($args['key'],$args['value']);
999		$m->releaseRemote();
1000
1001		return true;
1002
1003	}
1004
1005	/**
1006	 * Enumerate extra data of a vm
1007	 *
1008	 * @param array $args array of arguments. See function body for details.
1009	 * @return array of extra data
1010	 */
1011	public function remote_machineEnumerateExtraData($args) {
1012
1013		$this->connect();
1014
1015		/* @var $m IMachine */
1016		$m = $this->vbox->findMachine($args['vm']);
1017
1018		$props = array();
1019
1020		$keys = $m->getExtraDataKeys();
1021
1022		usort($keys,'strnatcasecmp');
1023
1024		foreach($keys as $k) {
1025			$props[$k] = $m->getExtraData($k);
1026		}
1027		$m->releaseRemote();
1028
1029		return $props;
1030
1031	}
1032
1033	/**
1034	 * Uses VirtualBox's vfsexplorer to check if a file exists
1035	 *
1036	 * @param array $args array of arguments. See function body for details.
1037	 * @return boolean true if file exists
1038	 */
1039	public function remote_fileExists($args) {
1040
1041		/* No need to go through vfs explorer if local browser is true */
1042		if($this->settings->browserLocal) {
1043			return file_exists($args['file']);
1044		}
1045
1046		$this->connect();
1047
1048		$dsep = $this->getDsep();
1049
1050		$path = str_replace($dsep.$dsep,$dsep,$args['file']);
1051		$dir = dirname($path);
1052		$file = basename($path);
1053
1054		if(substr($dir,-1) != $dsep) $dir .= $dsep;
1055
1056		/* @var $appl IAppliance */
1057		$appl = $this->vbox->createAppliance();
1058
1059
1060		/* @var $vfs IVFSExplorer */
1061		$vfs = $appl->createVFSExplorer('file://'.$dir);
1062
1063		/* @var $progress IProgress */
1064		$progress = $vfs->update();
1065		$progress->waitForCompletion(-1);
1066		$progress->releaseRemote();
1067
1068		$exists = $vfs->exists(array($file));
1069
1070		$vfs->releaseRemote();
1071		$appl->releaseRemote();
1072
1073
1074		return count($exists);
1075
1076	}
1077
1078	/**
1079	 * Install guest additions
1080	 *
1081	 * @param array $args array of arguments. See function body for details.
1082	 * @return array result data
1083	 */
1084	public function remote_consoleGuestAdditionsInstall($args) {
1085
1086		$this->connect();
1087
1088		$results = array('errored' => 0);
1089
1090		/* @var $gem IMedium|null */
1091		$gem = null;
1092		foreach($this->vbox->DVDImages as $m) { /* @var $m IMedium */
1093			if(strtolower($m->name) == 'vboxguestadditions.iso') {
1094				$gem = $m;
1095				break;
1096			}
1097			$m->releaseRemote();
1098		}
1099
1100		// Not in media registry. Try to register it.
1101		if(!$gem) {
1102			$checks = array(
1103				'linux' => '/usr/share/virtualbox/VBoxGuestAdditions.iso',
1104				'osx' => '/Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso',
1105				'sunos' => '/opt/VirtualBox/additions/VBoxGuestAdditions.iso',
1106				'windows' => 'C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso',
1107				'windowsx86' => 'C:\Program Files (x86)\Oracle\VirtualBox\VBoxGuestAdditions.iso' // Does this exist?
1108			);
1109			$hostos = $this->vbox->host->operatingSystem;
1110			if(stripos($hostos,'windows') !== false) {
1111				$checks = array($checks['windows'],$checks['windowsx86']);
1112			} elseif(stripos($hostos,'solaris') !== false || stripos($hostos,'sunos') !== false) {
1113				$checks = array($checks['sunos']);
1114			// not sure of uname returned on Mac. This should cover all of them
1115			} elseif(stripos($hostos,'mac') !== false || stripos($hostos,'apple') !== false || stripos($hostos,'osx') !== false || stripos($hostos,'os x') !== false || stripos($hostos,'darwin') !== false) {
1116				$checks = array($checks['osx']);
1117			} elseif(stripos($hostos,'linux') !== false) {
1118				$checks = array($checks['linux']);
1119			}
1120
1121			// Check for config setting
1122			if(@$this->settings->vboxGuestAdditionsISO)
1123				$checks = array($this->settings->vboxGuestAdditionsISO);
1124
1125			// Unknown os and no config setting leaves all checks in place.
1126			// Try to register medium.
1127			foreach($checks as $iso) {
1128				try {
1129					$gem = $this->vbox->openMedium($iso,'DVD','ReadOnly',false);
1130					break;
1131				} catch (Exception $e) {
1132					// Ignore
1133				}
1134			}
1135			$results['sources'] = $checks;
1136		}
1137
1138		// No guest additions found
1139		if(!$gem) {
1140			$results['result'] = 'noadditions';
1141			return $results;
1142		}
1143
1144		// create session and lock machine
1145		/* @var $machine IMachine */
1146		$machine = $this->vbox->findMachine($args['vm']);
1147		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1148		$machine->lockMachine($this->session->handle, 'Shared');
1149
1150		// Try update from guest if it is supported
1151		if(!@$args['mount_only']) {
1152			try {
1153
1154				/* @var $progress IProgress */
1155				$progress = $this->session->console->guest->updateGuestAdditions($gem->location,array(),'WaitForUpdateStartOnly');
1156
1157				// No error info. Save progress.
1158				$gem->releaseRemote();
1159				$this->_util_progressStore($progress);
1160				$results['progress'] = $progress->handle;
1161				return $results;
1162
1163			} catch (Exception $e) {
1164
1165				if(!empty($results['progress']))
1166					unset($results['progress']);
1167
1168				// Try to mount medium
1169				$results['errored'] = 1;
1170			}
1171		}
1172
1173		// updateGuestAdditions is not supported. Just try to mount image.
1174		$results['result'] = 'nocdrom';
1175		$mounted = false;
1176		foreach($machine->storageControllers as $sc) { /* @var $sc IStorageController */
1177			foreach($machine->getMediumAttachmentsOfController($sc->name) as $ma) { /* @var $ma IMediumAttachment */
1178				if((string)$ma->type == 'DVD') {
1179					$this->session->machine->mountMedium($sc->name, $ma->port, $ma->device, $gem->handle, true);
1180					$results['result'] = 'mounted';
1181					$mounted = true;
1182					break;
1183				}
1184			}
1185			$sc->releaseRemote();
1186			if($mounted) break;
1187		}
1188
1189
1190		$this->session->unlockMachine();
1191		unset($this->session);
1192		$machine->releaseRemote();
1193		$gem->releaseRemote();
1194
1195		return $results;
1196	}
1197
1198	/**
1199	 * Attach USB device identified by $args['id'] to a running VM
1200	 *
1201	 * @param array $args array of arguments. See function body for details.
1202	 * @return boolean true on success
1203	 */
1204	public function remote_consoleUSBDeviceAttach($args) {
1205
1206		$this->connect();
1207
1208		// create session and lock machine
1209		/* @var $machine IMachine */
1210		$machine = $this->vbox->findMachine($args['vm']);
1211		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1212		$machine->lockMachine($this->session->handle, 'Shared');
1213
1214		$this->session->console->attachUSBDevice($args['id']);
1215
1216		$this->session->unlockMachine();
1217		unset($this->session);
1218		$machine->releaseRemote();
1219
1220		return true;
1221	}
1222
1223	/**
1224	 * Detach USB device identified by $args['id'] from a running VM
1225	 *
1226	 * @param array $args array of arguments. See function body for details.
1227	 * @return boolean true on success
1228	 */
1229	public function remote_consoleUSBDeviceDetach($args) {
1230
1231		$this->connect();
1232
1233		// create session and lock machine
1234		/* @var $machine IMachine */
1235		$machine = $this->vbox->findMachine($args['vm']);
1236		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1237		$machine->lockMachine($this->session->handle, 'Shared');
1238
1239		$this->session->console->detachUSBDevice($args['id']);
1240
1241		$this->session->unlockMachine();
1242		unset($this->session);
1243		$machine->releaseRemote();
1244
1245		return true;
1246	}
1247
1248	/**
1249	 * Save vms' groups if they have changed
1250	 *
1251	 * @param array $args array of arguments. See function body for details.
1252	 * @return array response data
1253	 */
1254	public function remote_machinesSaveGroups($args) {
1255
1256		$this->connect();
1257
1258		$response = array('saved'=>array(),'errored'=>false);
1259
1260		foreach($args['vms'] as $vm) {
1261
1262			// create session and lock machine
1263			/* @var $machine IMachine */
1264			try  {
1265				$machine = $this->vbox->findMachine($vm['id']);
1266			} catch (Exception $null) {
1267				continue;
1268			}
1269
1270			$newGroups = $vm['groups'];
1271
1272			if($this->settings->phpVboxGroups) {
1273
1274				$oldGroups = explode(',',$machine->getExtraData(vboxconnector::phpVboxGroupKey));
1275				if(!is_array($oldGroups)) $oldGroups = array("/");
1276				if(!count(array_diff($oldGroups,$newGroups)) && !count(array_diff($newGroups,$oldGroups))) {
1277					continue;
1278				}
1279
1280			} else {
1281
1282				$oldGroups = $machine->groups;
1283
1284				if((string)$machine->sessionState != 'Unlocked' || (!count(array_diff($oldGroups,$newGroups)) && !count(array_diff($newGroups,$oldGroups)))) {
1285					$machine->releaseRemote();
1286					continue;
1287				}
1288
1289			}
1290
1291			try {
1292
1293				$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1294
1295				$machine->lockMachine($this->session->handle, 'Shared');
1296
1297				usort($newGroups,'strnatcasecmp');
1298
1299				if($this->settings->phpVboxGroups) {
1300					$this->session->machine->setExtraData(vboxconnector::phpVboxGroupKey, implode(',', $newGroups));
1301				} else {
1302					$this->session->machine->groups = $newGroups;
1303				}
1304
1305				$this->session->machine->saveSettings();
1306				$this->session->unlockMachine();
1307
1308				unset($this->session);
1309				$machine->releaseRemote();
1310
1311			} catch (Exception $e) {
1312
1313				$this->errors[] = $e;
1314				$response['errored'] = true;
1315
1316				try {
1317				    $this->session->unlockMachine();
1318				    unset($this->session);
1319				} catch (Exception $e) {
1320				    // pass
1321				}
1322
1323				continue;
1324
1325			}
1326
1327			// Add to saved list
1328			$response['saved'][] = $vm['id'];
1329
1330		}
1331
1332
1333		return $response;
1334
1335
1336	}
1337
1338
1339	/**
1340	 * Clone a virtual machine
1341	 *
1342	 * @param array $args array of arguments. See function body for details.
1343	 * @return array response data
1344	 */
1345	public function remote_machineClone($args) {
1346
1347		// Connect to vboxwebsrv
1348		$this->connect();
1349
1350		/* @var $src IMachine */
1351		$src = $this->vbox->findMachine($args['src']);
1352
1353		if($args['snapshot'] && $args['snapshot']['id']) {
1354			/* @var $nsrc ISnapshot */
1355			$nsrc = $src->findSnapshot($args['snapshot']['id']);
1356			$src->releaseRemote();
1357			$src = null;
1358			$src = $nsrc->machine;
1359		}
1360		/* @var $m IMachine */
1361		$m = $this->vbox->createMachine($this->vbox->composeMachineFilename($args['name'],null,null,null),$args['name'],null,null,null,false);
1362		$sfpath = $m->settingsFilePath;
1363
1364		/* @var $cm CloneMode */
1365		$cm = new CloneMode(null,$args['vmState']);
1366		$state = $cm->ValueMap[$args['vmState']];
1367
1368
1369		$opts = array();
1370		if(!$args['reinitNetwork']) $opts[] = 'KeepAllMACs';
1371		if($args['link']) $opts[] = 'Link';
1372
1373		/* @var $progress IProgress */
1374		$progress = $src->cloneTo($m->handle,$args['vmState'],$opts);
1375
1376		// Does an exception exist?
1377		try {
1378			if($progress->errorInfo->handle) {
1379				$this->errors[] = new Exception($progress->errorInfo->text);
1380				$progress->releaseRemote();
1381				return false;
1382			}
1383		} catch (Exception $null) {}
1384
1385		$m->releaseRemote();
1386		$src->releaseRemote();
1387
1388		$this->_util_progressStore($progress);
1389
1390		return array(
1391				'progress' => $progress->handle,
1392				'settingsFilePath' => $sfpath);
1393
1394	}
1395
1396
1397	/**
1398	 * Turn VRDE on / off on a running VM
1399	 *
1400	 * @param array $args array of arguments. See function body for details.
1401	 * @return boolean true on success
1402	 */
1403	public function remote_consoleVRDEServerSave($args) {
1404
1405		$this->connect();
1406
1407		// create session and lock machine
1408		/* @var $m IMachine */
1409		$m = $this->vbox->findMachine($args['vm']);
1410		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1411		$m->lockMachine($this->session->handle, 'Shared');
1412
1413		if(intval($args['enabled']) == -1) {
1414			$args['enabled'] = intval(!$this->session->machine->VRDEServer->enabled);
1415		}
1416
1417		$this->session->machine->VRDEServer->enabled = intval($args['enabled']);
1418
1419		$this->session->unlockMachine();
1420		unset($this->session);
1421
1422		$m->releaseRemote();
1423
1424		return true;
1425	}
1426
1427	/**
1428	 * Save running VM settings. Called from machineSave method if the requested VM is running.
1429	 *
1430	 * @param array $args array of machine configuration items.
1431	 * @param string $state state of virtual machine.
1432	 * @return boolean true on success
1433	 */
1434	private function _machineSaveRunning($args, $state) {
1435
1436		// Client and server must agree on advanced config setting
1437		$this->settings->enableAdvancedConfig = (@$this->settings->enableAdvancedConfig && @$args['clientConfig']['enableAdvancedConfig']);
1438		$this->settings->enableHDFlushConfig = (@$this->settings->enableHDFlushConfig && @$args['clientConfig']['enableHDFlushConfig']);
1439
1440		// Shorthand
1441		/* @var $m IMachine */
1442		$m = &$this->session->machine;
1443
1444		$m->CPUExecutionCap = $args['CPUExecutionCap'];
1445		$m->description = $args['description'];
1446
1447		// Start / stop config
1448		if(@$this->settings->startStopConfig) {
1449			$m->setExtraData('pvbx/startupMode', $args['startupMode']);
1450		}
1451
1452		// VirtualBox style start / stop config
1453		if(@$this->settings->vboxAutostartConfig && @$args['clientConfig']['vboxAutostartConfig']) {
1454
1455			$m->autostopType = $args['autostopType'];
1456			$m->autostartEnabled = $args['autostartEnabled'];
1457			$m->autostartDelay = $args['autostartDelay'];
1458
1459		}
1460
1461		// Custom Icon
1462		if(@$this->settings->enableCustomIcons) {
1463			$m->setExtraData('phpvb/icon', $args['customIcon']);
1464		}
1465
1466		// VRDE settings
1467		try {
1468			if($m->VRDEServer && $this->vbox->systemProperties->defaultVRDEExtPack) {
1469				$m->VRDEServer->enabled = $args['VRDEServer']['enabled'];
1470				$m->VRDEServer->setVRDEProperty('TCP/Ports',$args['VRDEServer']['ports']);
1471				$m->VRDEServer->setVRDEProperty('VNCPassword',$args['VRDEServer']['VNCPassword'] ? $args['VRDEServer']['VNCPassword'] : null);
1472				$m->VRDEServer->authType = ($args['VRDEServer']['authType'] ? $args['VRDEServer']['authType'] : null);
1473				$m->VRDEServer->authTimeout = $args['VRDEServer']['authTimeout'];
1474			}
1475		} catch (Exception $e) {
1476		}
1477
1478		// Storage Controllers if machine is in a valid state
1479		if($state != 'Saved') {
1480
1481			$scs = $m->storageControllers;
1482			$attachedEx = $attachedNew = array();
1483			foreach($scs as $sc) { /* @var $sc IStorageController */
1484				$mas = $m->getMediumAttachmentsOfController($sc->name);
1485				foreach($mas as $ma) { /* @var $ma IMediumAttachment */
1486					$attachedEx[$sc->name.$ma->port.$ma->device] = (($ma->medium->handle && $ma->medium->id) ? $ma->medium->id : null);
1487				}
1488			}
1489
1490			// Incoming list
1491			foreach($args['storageControllers'] as $sc) {
1492
1493				$sc['name'] = trim($sc['name']);
1494				$name = ($sc['name'] ? $sc['name'] : $sc['bus']);
1495
1496				// Medium attachments
1497				foreach($sc['mediumAttachments'] as $ma) {
1498
1499					if($ma['medium'] == 'null') $ma['medium'] = null;
1500
1501					$attachedNew[$name.$ma['port'].$ma['device']] = $ma['medium']['id'];
1502
1503					// Compare incoming list with existing
1504					if($ma['type'] != 'HardDisk' && $attachedNew[$name.$ma['port'].$ma['device']] != $attachedEx[$name.$ma['port'].$ma['device']]) {
1505
1506						if(is_array($ma['medium']) && $ma['medium']['id'] && $ma['type']) {
1507
1508							// Host drive
1509							if(strtolower($ma['medium']['hostDrive']) == 'true' || $ma['medium']['hostDrive'] === true) {
1510								// CD / DVD Drive
1511								if($ma['type'] == 'DVD') {
1512									$drives = $this->vbox->host->DVDDrives;
1513								// floppy drives
1514								} else {
1515									$drives = $this->vbox->host->floppyDrives;
1516								}
1517								foreach($drives as $md) {
1518									if($md->id == $ma['medium']['id']) {
1519										$med = &$md;
1520										break;
1521									}
1522									$md->releaseRemote();
1523								}
1524							} else {
1525								$med = $this->vbox->openMedium($ma['medium']['location'],$ma['type'],'ReadWrite',false);
1526							}
1527						} else {
1528							$med = null;
1529						}
1530						$m->mountMedium($name,$ma['port'],$ma['device'],(is_object($med) ? $med->handle : null),true);
1531						if(is_object($med)) $med->releaseRemote();
1532					}
1533
1534					// Set Live CD/DVD
1535					if($ma['type'] == 'DVD') {
1536						if(!$ma['medium']['hostDrive'])
1537							$m->temporaryEjectDevice($name, $ma['port'], $ma['device'], $ma['temporaryEject']);
1538
1539					// Set IgnoreFlush
1540					} elseif($ma['type'] == 'HardDisk') {
1541
1542						// Remove IgnoreFlush key?
1543						if($this->settings->enableHDFlushConfig) {
1544
1545							$xtra = $this->_util_getIgnoreFlushKey($ma['port'], $ma['device'], $sc['controllerType']);
1546
1547							if($xtra) {
1548								if((bool)($ma['ignoreFlush'])) {
1549									$m->setExtraData($xtra, '0');
1550								} else {
1551									$m->setExtraData($xtra, '');
1552								}
1553							}
1554						}
1555
1556
1557					}
1558				}
1559
1560			}
1561		}
1562
1563
1564		/* Networking */
1565		$netprops = array('enabled','attachmentType','bridgedInterface','hostOnlyInterface','internalNetwork','NATNetwork','promiscModePolicy','genericDriver');
1566		if(@$this->settings->enableVDE) $netprops[] = 'VDENetwork';
1567
1568		for($i = 0; $i < count($args['networkAdapters']); $i++) {
1569
1570			/* @var $n INetworkAdapter */
1571			$n = $m->getNetworkAdapter($i);
1572
1573			// Skip disabled adapters
1574			if(!$n->enabled) {
1575				$n->releaseRemote();
1576				continue;
1577			}
1578
1579			for($p = 0; $p < count($netprops); $p++) {
1580				switch($netprops[$p]) {
1581					case 'enabled':
1582					case 'cableConnected':
1583						break;
1584					default:
1585						if((string)$n->{$netprops[$p]} != (string)$args['networkAdapters'][$i][$netprops[$p]])
1586							$n->{$netprops[$p]} = $args['networkAdapters'][$i][$netprops[$p]];
1587				}
1588			}
1589
1590			/// Not if in "Saved" state
1591			if($state != 'Saved') {
1592
1593				// Network properties
1594				$eprops = $n->getProperties(null);
1595				$eprops = array_combine($eprops[1],$eprops[0]);
1596				$iprops = array_map(function($a){$b=explode("=",$a); return array($b[0]=>$b[1]);},preg_split('/[\r|\n]+/',$args['networkAdapters'][$i]['properties']));
1597				$inprops = array();
1598				foreach($iprops as $a) {
1599					foreach($a as $k=>$v)
1600					$inprops[$k] = $v;
1601				}
1602
1603				// Remove any props that are in the existing properties array
1604				// but not in the incoming properties array
1605				foreach(array_diff(array_keys($eprops),array_keys($inprops)) as $dk) {
1606					$n->setProperty($dk, '');
1607				}
1608
1609				// Set remaining properties
1610				foreach($inprops as $k => $v) {
1611					if(!$k) continue;
1612					$n->setProperty($k, $v);
1613				}
1614
1615				if($n->cableConnected != $args['networkAdapters'][$i]['cableConnected'])
1616					$n->cableConnected = $args['networkAdapters'][$i]['cableConnected'];
1617
1618			}
1619
1620			if($args['networkAdapters'][$i]['attachmentType'] == 'NAT') {
1621
1622				// Remove existing redirects
1623				foreach($n->NATEngine->getRedirects() as $r) {
1624					$n->NATEngine->removeRedirect(array_shift(explode(',',$r)));
1625				}
1626				// Add redirects
1627				foreach($args['networkAdapters'][$i]['redirects'] as $r) {
1628					$r = explode(',',$r);
1629					$n->NATEngine->addRedirect($r[0],$r[1],$r[2],$r[3],$r[4],$r[5]);
1630				}
1631
1632				// Advanced NAT settings
1633				if($state != 'Saved' && @$this->settings->enableAdvancedConfig) {
1634					$aliasMode = $n->NATEngine->aliasMode & 1;
1635					if(intval($args['networkAdapters'][$i]['NATEngine']['aliasMode'] & 2)) $aliasMode |= 2;
1636					if(intval($args['networkAdapters'][$i]['NATEngine']['aliasMode'] & 4)) $aliasMode |= 4;
1637					$n->NATEngine->aliasMode = $aliasMode;
1638					$n->NATEngine->DNSProxy = $args['networkAdapters'][$i]['NATEngine']['DNSProxy'];
1639					$n->NATEngine->DNSPassDomain = $args['networkAdapters'][$i]['NATEngine']['DNSPassDomain'];
1640					$n->NATEngine->DNSUseHostResolver = $args['networkAdapters'][$i]['NATEngine']['DNSUseHostResolver'];
1641					$n->NATEngine->hostIP = $args['networkAdapters'][$i]['NATEngine']['hostIP'];
1642				}
1643
1644			} else if($args['networkAdapters'][$i]['attachmentType'] == 'NATNetwork') {
1645
1646				if($n->NATNetwork = $args['networkAdapters'][$i]['NATNetwork']);
1647			}
1648
1649			$n->releaseRemote();
1650
1651		}
1652
1653		/* Shared Folders */
1654		$sf_inc = array();
1655		foreach($args['sharedFolders'] as $s) {
1656			$sf_inc[$s['name']] = $s;
1657		}
1658
1659
1660		// Get list of perm shared folders
1661		$psf_tmp = $m->sharedFolders;
1662		$psf = array();
1663		foreach($psf_tmp as $sf) {
1664			$psf[$sf->name] = $sf;
1665		}
1666
1667		// Get a list of temp shared folders
1668		$tsf_tmp = $this->session->console->sharedFolders;
1669		$tsf = array();
1670		foreach($tsf_tmp as $sf) {
1671			$tsf[$sf->name] = $sf;
1672		}
1673
1674		/*
1675		 *  Step through list and remove non-matching folders
1676		 */
1677		foreach($sf_inc as $sf) {
1678
1679			// Already exists in perm list. Check Settings.
1680			if($sf['type'] == 'machine' && $psf[$sf['name']]) {
1681
1682				/* Remove if it doesn't match */
1683				if($sf['hostPath'] != $psf[$sf['name']]->hostPath || (bool)$sf['autoMount'] != (bool)$psf[$sf['name']]->autoMount || (bool)$sf['writable'] != (bool)$psf[$sf['name']]->writable) {
1684
1685					$m->removeSharedFolder($sf['name']);
1686					$m->createSharedFolder($sf['name'],$sf['hostPath'],(bool)$sf['writable'],(bool)$sf['autoMount']);
1687				}
1688
1689				unset($psf[$sf['name']]);
1690
1691			// Already exists in perm list. Check Settings.
1692			} else if($sf['type'] != 'machine' && $tsf[$sf['name']]) {
1693
1694				/* Remove if it doesn't match */
1695				if($sf['hostPath'] != $tsf[$sf['name']]->hostPath || (bool)$sf['autoMount'] != (bool)$tsf[$sf['name']]->autoMount || (bool)$sf['writable'] != (bool)$tsf[$sf['name']]->writable) {
1696
1697					$this->session->console->removeSharedFolder($sf['name']);
1698					$this->session->console->createSharedFolder($sf['name'],$sf['hostPath'],(bool)$sf['writable'],(bool)$sf['autoMount']);
1699
1700				}
1701
1702				unset($tsf[$sf['name']]);
1703
1704			} else {
1705
1706				// Does not exist or was removed. Add it.
1707				if($sf['type'] != 'machine') $this->session->console->createSharedFolder($sf['name'],$sf['hostPath'],(bool)$sf['writable'],(bool)$sf['autoMount']);
1708				else $this->session->machine->createSharedFolder($sf['name'],$sf['hostPath'],(bool)$sf['writable'],(bool)$sf['autoMount']);
1709			}
1710
1711		}
1712
1713		/*
1714		 * Remove remaining
1715		 */
1716		foreach($psf as $sf) $m->removeSharedFolder($sf->name);
1717		foreach($tsf as $sf) $this->session->console->removeSharedFolder($sf->name);
1718
1719		/*
1720		 * USB Filters
1721		 */
1722
1723		$usbEx = array();
1724		$usbNew = array();
1725
1726		$usbc = $this->_machineGetUSBControllers($this->session->machine);
1727
1728		$deviceFilters = $this->_machineGetUSBDeviceFilters($this->session->machine);
1729
1730		if($state != 'Saved') {
1731
1732			// filters
1733			if(!is_array($args['USBDeviceFilters'])) $args['USBDeviceFilters'] = array();
1734
1735			if(count($deviceFilters) != count($args['USBDeviceFilters']) || @serialize($deviceFilters) != @serialize($args['USBDeviceFilters'])) {
1736
1737				// usb filter properties to change
1738				$usbProps = array('vendorId','productId','revision','manufacturer','product','serialNumber','port','remote');
1739
1740				// Remove and Add filters
1741				try {
1742
1743
1744					$max = max(count($deviceFilters),count($args['USBDeviceFilters']));
1745					$offset = 0;
1746
1747					// Remove existing
1748					for($i = 0; $i < $max; $i++) {
1749
1750						// Only if filter differs
1751						if(@serialize($deviceFilters[$i]) != @serialize($args['USBDeviceFilters'][$i])) {
1752
1753							// Remove existing?
1754							if($i < count($deviceFilters)) {
1755								$m->USBDeviceFilters->removeDeviceFilter(($i-$offset));
1756								$offset++;
1757							}
1758
1759							// Exists in new?
1760							if(count($args['USBDeviceFilters'][$i])) {
1761
1762								// Create filter
1763								$f = $m->USBDeviceFilters->createDeviceFilter($args['USBDeviceFilters'][$i]['name']);
1764								$f->active = (bool)$args['USBDeviceFilters'][$i]['active'];
1765
1766								foreach($usbProps as $p) {
1767									$f->$p = $args['USBDeviceFilters'][$i][$p];
1768								}
1769
1770								$m->USBDeviceFilters->insertDeviceFilter($i,$f->handle);
1771								$f->releaseRemote();
1772								$offset--;
1773							}
1774						}
1775
1776					}
1777
1778				} catch (Exception $e) { $this->errors[] = $e; }
1779
1780			}
1781
1782		}
1783
1784
1785		$this->session->machine->saveSettings();
1786		$this->session->unlockMachine();
1787		unset($this->session);
1788		$m->releaseRemote();
1789
1790		return true;
1791
1792	}
1793
1794	/**
1795	 * Save virtual machine settings.
1796	 *
1797	 * @param array $args array of arguments. See function body for details.
1798	 * @return boolean true on success
1799	 */
1800	public function remote_machineSave($args) {
1801
1802		$this->connect();
1803
1804		// create session and lock machine
1805		/* @var $machine IMachine */
1806		$machine = $this->vbox->findMachine($args['id']);
1807
1808		$vmState = (string)$machine->state;
1809		$vmRunning = ($vmState == 'Running' || $vmState == 'Paused' || $vmState == 'Saved');
1810		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
1811		$machine->lockMachine($this->session->handle, ($vmRunning ? 'Shared' : 'Write'));
1812
1813		// Switch to machineSaveRunning()?
1814		if($vmRunning) {
1815			return $this->_machineSaveRunning($args, $vmState);
1816		}
1817
1818
1819		// Client and server must agree on advanced config setting
1820		$this->settings->enableAdvancedConfig = (@$this->settings->enableAdvancedConfig && @$args['clientConfig']['enableAdvancedConfig']);
1821		$this->settings->enableHDFlushConfig = (@$this->settings->enableHDFlushConfig && @$args['clientConfig']['enableHDFlushConfig']);
1822
1823		// Shorthand
1824		/* @var $m IMachine */
1825		$m = $this->session->machine;
1826
1827		// General machine settings
1828		if (@$this->settings->enforceVMOwnership ) {
1829
1830			$args['name'] = "{$_SESSION['user']}_" . preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $args['name']);
1831
1832			if ( ($owner = $machine->getExtraData("phpvb/sso/owner")) && $owner !== $_SESSION['user'] && !$_SESSION['admin'] )
1833			{
1834				// skip this VM as it is not owned by the user we're logged in as
1835				throw new Exception("Not authorized to modify this VM");
1836			}
1837
1838		}
1839
1840		// Change OS type and update LongMode
1841		if(strcasecmp($m->OSTypeId,$args['OSTypeId']) != 0) {
1842
1843			$m->OSTypeId = $args['OSTypeId'];
1844
1845			$guestOS = $this->vbox->getGuestOSType($args['OSTypeId']);
1846
1847			$m->setCPUProperty('LongMode', ($guestOS->is64Bit ? 1 : 0));
1848		}
1849
1850		$m->CPUCount = $args['CPUCount'];
1851		$m->memorySize = $args['memorySize'];
1852		$m->firmwareType = $args['firmwareType'];
1853		if($args['chipsetType']) $m->chipsetType = $args['chipsetType'];
1854		if($m->snapshotFolder != $args['snapshotFolder']) $m->snapshotFolder = $args['snapshotFolder'];
1855		$m->RTCUseUTC = ($args['RTCUseUTC'] ? 1 : 0);
1856		$m->setCpuProperty('PAE', ($args['CpuProperties']['PAE'] ? 1 : 0));
1857		$m->setCPUProperty('LongMode', (strpos($args['OSTypeId'],'_64') > - 1 ? 1 : 0));
1858
1859		// IOAPIC
1860		$m->BIOSSettings->IOAPICEnabled = ($args['BIOSSettings']['IOAPICEnabled'] ? 1 : 0);
1861		$m->CPUExecutionCap = $args['CPUExecutionCap'];
1862		$m->description = $args['description'];
1863
1864		// Start / stop config
1865		if(@$this->settings->startStopConfig) {
1866			$m->setExtraData('pvbx/startupMode', $args['startupMode']);
1867		}
1868
1869
1870		// VirtualBox style start / stop config
1871		if(@$this->settings->vboxAutostartConfig && @$args['clientConfig']['vboxAutostartConfig']) {
1872
1873			$m->autostopType = $args['autostopType'];
1874			$m->autostartEnabled = $args['autostartEnabled'];
1875			$m->autostartDelay = $args['autostartDelay'];
1876
1877		}
1878
1879		// Determine if host is capable of hw accel
1880		$hwAccelAvail = $this->vbox->host->getProcessorFeature('HWVirtEx');
1881
1882		$m->paravirtProvider = $args['paravirtProvider'];
1883		$m->setHWVirtExProperty('Enabled', $args['HWVirtExProperties']['Enabled']);
1884		$m->setHWVirtExProperty('NestedPaging', ($args['HWVirtExProperties']['Enabled'] && $hwAccelAvail && $args['HWVirtExProperties']['NestedPaging']));
1885
1886		/* Only if advanced configuration is enabled */
1887		if(@$this->settings->enableAdvancedConfig) {
1888
1889			/** @def VBOX_WITH_PAGE_SHARING
1890 			* Enables the page sharing code.
1891			* @remarks This must match GMMR0Init; currently we only support page fusion on
1892			 *          all 64-bit hosts except Mac OS X */
1893
1894			if($this->vbox->host->getProcessorFeature('LongMode')) {
1895
1896				$m->pageFusionEnabled = $args['pageFusionEnabled'];
1897			}
1898
1899			$m->HPETEnabled = $args['HPETEnabled'];
1900			$m->setExtraData("VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled", $args['disableHostTimeSync']);
1901			$m->keyboardHIDType = $args['keyboardHIDType'];
1902			$m->pointingHIDType = $args['pointingHIDType'];
1903			$m->setHWVirtExProperty('LargePages', $args['HWVirtExProperties']['LargePages']);
1904			$m->setHWVirtExProperty('UnrestrictedExecution', $args['HWVirtExProperties']['UnrestrictedExecution']);
1905			$m->setHWVirtExProperty('VPID', $args['HWVirtExProperties']['VPID']);
1906
1907		}
1908
1909		/* Custom Icon */
1910		if(@$this->settings->enableCustomIcons)
1911			$m->setExtraData('phpvb/icon', $args['customIcon']);
1912
1913		$m->VRAMSize = $args['VRAMSize'];
1914
1915		// Video
1916		$m->accelerate3DEnabled = $args['accelerate3DEnabled'];
1917		$m->accelerate2DVideoEnabled = $args['accelerate2DVideoEnabled'];
1918
1919		// VRDE settings
1920		try {
1921			if($m->VRDEServer && $this->vbox->systemProperties->defaultVRDEExtPack) {
1922				$m->VRDEServer->enabled = $args['VRDEServer']['enabled'];
1923				$m->VRDEServer->setVRDEProperty('TCP/Ports',$args['VRDEServer']['ports']);
1924				if(@$this->settings->enableAdvancedConfig)
1925					$m->VRDEServer->setVRDEProperty('TCP/Address',$args['VRDEServer']['netAddress']);
1926				$m->VRDEServer->setVRDEProperty('VNCPassword',$args['VRDEServer']['VNCPassword'] ? $args['VRDEServer']['VNCPassword'] : null);
1927				$m->VRDEServer->authType = ($args['VRDEServer']['authType'] ? $args['VRDEServer']['authType'] : null);
1928				$m->VRDEServer->authTimeout = $args['VRDEServer']['authTimeout'];
1929				$m->VRDEServer->allowMultiConnection = $args['VRDEServer']['allowMultiConnection'];
1930			}
1931		} catch (Exception $e) {
1932		}
1933
1934		// Audio controller settings
1935		$m->audioAdapter->enabled = ($args['audioAdapter']['enabled'] ? 1 : 0);
1936		$m->audioAdapter->audioController = $args['audioAdapter']['audioController'];
1937		$m->audioAdapter->audioDriver = $args['audioAdapter']['audioDriver'];
1938
1939		// Boot order
1940		$mbp = $this->vbox->systemProperties->maxBootPosition;
1941		for($i = 0; $i < $mbp; $i ++) {
1942			if($args['bootOrder'][$i]) {
1943				$m->setBootOrder(($i + 1),$args['bootOrder'][$i]);
1944			} else {
1945				$m->setBootOrder(($i + 1),'Null');
1946			}
1947		}
1948
1949		// Storage Controllers
1950		$scs = $m->storageControllers;
1951		$attachedEx = $attachedNew = array();
1952		foreach($scs as $sc) { /* @var $sc IStorageController */
1953
1954			$mas = $m->getMediumAttachmentsOfController($sc->name);
1955
1956			$cType = (string)$sc->controllerType;
1957
1958			foreach($mas as $ma) { /* @var $ma IMediumAttachment */
1959
1960				$attachedEx[$sc->name.$ma->port.$ma->device] = (($ma->medium->handle && $ma->medium->id) ? $ma->medium->id : null);
1961
1962				// Remove IgnoreFlush key?
1963				if($this->settings->enableHDFlushConfig && (string)$ma->type == 'HardDisk') {
1964					$xtra = $this->_util_getIgnoreFlushKey($ma->port, $ma->device, $cType);
1965					if($xtra) {
1966						$m->setExtraData($xtra,'');
1967					}
1968				}
1969
1970				if($ma->controller) {
1971					$m->detachDevice($ma->controller,$ma->port,$ma->device);
1972				}
1973
1974			}
1975			$scname = $sc->name;
1976			$sc->releaseRemote();
1977			$m->removeStorageController($scname);
1978		}
1979
1980		// Add New
1981		foreach($args['storageControllers'] as $sc) {
1982
1983			$sc['name'] = trim($sc['name']);
1984			$name = ($sc['name'] ? $sc['name'] : $sc['bus']);
1985
1986
1987			$bust = new StorageBus(null,$sc['bus']);
1988			$c = $m->addStorageController($name,(string)$bust);
1989			$c->controllerType = $sc['controllerType'];
1990			$c->useHostIOCache = $sc['useHostIOCache'];
1991
1992			// Set sata port count
1993			if($sc['bus'] == 'SATA') {
1994				$max = max(1,intval(@$sc['portCount']));
1995				foreach($sc['mediumAttachments'] as $ma) {
1996					$max = max($max,(intval($ma['port'])+1));
1997				}
1998				$c->portCount = min(intval($c->maxPortCount),max(count($sc['mediumAttachments']),$max));
1999
2000			}
2001			$c->releaseRemote();
2002
2003
2004			// Medium attachments
2005			foreach($sc['mediumAttachments'] as $ma) {
2006
2007				if($ma['medium'] == 'null') $ma['medium'] = null;
2008
2009				$attachedNew[$name.$ma['port'].$ma['device']] = $ma['medium']['id'];
2010
2011				if(is_array($ma['medium']) && $ma['medium']['id'] && $ma['type']) {
2012
2013					// Host drive
2014					if(strtolower($ma['medium']['hostDrive']) == 'true' || $ma['medium']['hostDrive'] === true) {
2015						// CD / DVD Drive
2016						if($ma['type'] == 'DVD') {
2017							$drives = $this->vbox->host->DVDDrives;
2018						// floppy drives
2019						} else {
2020							$drives = $this->vbox->host->floppyDrives;
2021						}
2022						foreach($drives as $md) { /* @var $md IMedium */
2023							if($md->id == $ma['medium']['id']) {
2024								$med = &$md;
2025								break;
2026							}
2027							$md->releaseRemote();
2028						}
2029					} else {
2030						/* @var $med IMedium */
2031						$med = $this->vbox->openMedium($ma['medium']['location'],$ma['type'], 'ReadWrite', false);
2032					}
2033				} else {
2034					$med = null;
2035				}
2036				$m->attachDevice($name,$ma['port'],$ma['device'],$ma['type'],(is_object($med) ? $med->handle : null));
2037
2038				// CD / DVD medium attachment type
2039				if($ma['type'] == 'DVD') {
2040
2041					if($ma['medium']['hostDrive'])
2042						$m->passthroughDevice($name, $ma['port'], $ma['device'], $ma['passthrough']);
2043					else
2044						$m->temporaryEjectDevice($name, $ma['port'], $ma['device'], $ma['temporaryEject']);
2045
2046				// HardDisk medium attachment type
2047				} else if($ma['type'] == 'HardDisk') {
2048
2049					$m->nonRotationalDevice($name, $ma['port'], $ma['device'], $ma['nonRotational']);
2050
2051					// Remove IgnoreFlush key?
2052					if($this->settings->enableHDFlushConfig) {
2053
2054						$xtra = $this->_util_getIgnoreFlushKey($ma['port'], $ma['device'], $sc['controllerType']);
2055
2056						if($xtra) {
2057							if($ma['ignoreFlush']) {
2058								$m->setExtraData($xtra, '');
2059							} else {
2060								$m->setExtraData($xtra, 0);
2061							}
2062						}
2063					}
2064
2065
2066				}
2067
2068				if($sc['bus'] == 'SATA' || $sc['bus'] == 'USB') {
2069					$m->setHotPluggableForDevice($name, $ma['port'], $ma['device'], $ma['hotPluggable']);
2070				}
2071
2072				if(is_object($med))
2073					$med->releaseRemote();
2074			}
2075
2076		}
2077
2078		/*
2079		 *
2080		 * Network Adapters
2081		 *
2082		 */
2083
2084		$netprops = array('enabled','attachmentType','adapterType','MACAddress','bridgedInterface',
2085				'hostOnlyInterface','internalNetwork','NATNetwork','cableConnected','promiscModePolicy','genericDriver');
2086
2087		for($i = 0; $i < count($args['networkAdapters']); $i++) {
2088		if(@$this->settings->enableVDE) $netprops[] = 'VDENetwork';
2089
2090			$n = $m->getNetworkAdapter($i);
2091
2092			// Skip disabled adapters
2093			if(!($n->enabled || @$args['networkAdapters'][$i]['enabled']))
2094				continue;
2095
2096			for($p = 0; $p < count($netprops); $p++) {
2097			    /*
2098				switch($netprops[$p]) {
2099					case 'enabled':
2100					case 'cableConnected':
2101						continue;
2102				}
2103				*/
2104				$n->{$netprops[$p]} = @$args['networkAdapters'][$i][$netprops[$p]];
2105			}
2106
2107			// Special case for boolean values
2108			/*
2109			$n->enabled = $args['networkAdapters'][$i]['enabled'];
2110			$n->cableConnected = $args['networkAdapters'][$i]['cableConnected'];
2111			*/
2112
2113			// Network properties
2114			$eprops = $n->getProperties(null);
2115			$eprops = array_combine($eprops[1],$eprops[0]);
2116			$iprops = array_map(function($a){$b=explode("=",$a); return array($b[0]=>$b[1]);},preg_split('/[\r|\n]+/',$args['networkAdapters'][$i]['properties']));
2117			$inprops = array();
2118			foreach($iprops as $a) {
2119				foreach($a as $k=>$v)
2120					$inprops[$k] = $v;
2121			}
2122			// Remove any props that are in the existing properties array
2123			// but not in the incoming properties array
2124			foreach(array_diff(array_keys($eprops),array_keys($inprops)) as $dk)
2125				$n->setProperty($dk, '');
2126
2127			// Set remaining properties
2128			foreach($inprops as $k => $v)
2129				$n->setProperty($k, $v);
2130
2131			// Nat redirects and advanced settings
2132			if($args['networkAdapters'][$i]['attachmentType'] == 'NAT') {
2133
2134				// Remove existing redirects
2135				foreach($n->NATEngine->getRedirects() as $r) {
2136					$n->NATEngine->removeRedirect(array_shift(explode(',',$r)));
2137				}
2138				// Add redirects
2139				foreach($args['networkAdapters'][$i]['redirects'] as $r) {
2140					$r = explode(',',$r);
2141					$n->NATEngine->addRedirect($r[0],$r[1],$r[2],$r[3],$r[4],$r[5]);
2142				}
2143
2144				// Advanced NAT settings
2145				if(@$this->settings->enableAdvancedConfig) {
2146					$aliasMode = $n->NATEngine->aliasMode & 1;
2147					if(intval($args['networkAdapters'][$i]['NATEngine']['aliasMode'] & 2)) $aliasMode |= 2;
2148					if(intval($args['networkAdapters'][$i]['NATEngine']['aliasMode'] & 4)) $aliasMode |= 4;
2149					$n->NATEngine->aliasMode = $aliasMode;
2150					$n->NATEngine->DNSProxy = $args['networkAdapters'][$i]['NATEngine']['DNSProxy'];
2151					$n->NATEngine->DNSPassDomain = $args['networkAdapters'][$i]['NATEngine']['DNSPassDomain'];
2152					$n->NATEngine->DNSUseHostResolver = $args['networkAdapters'][$i]['NATEngine']['DNSUseHostResolver'];
2153					$n->NATEngine->hostIP = $args['networkAdapters'][$i]['NATEngine']['hostIP'];
2154				}
2155
2156			} else if($args['networkAdapters'][$i]['attachmentType'] == 'NATNetwork') {
2157
2158				if($n->NATNetwork = $args['networkAdapters'][$i]['NATNetwork']);
2159			}
2160
2161			$n->releaseRemote();
2162		}
2163
2164		// Serial Ports
2165		for($i = 0; $i < count($args['serialPorts']); $i++) {
2166
2167			/* @var $p ISerialPort */
2168			$p = $m->getSerialPort($i);
2169
2170			if(!($p->enabled || $args['serialPorts'][$i]['enabled']))
2171				continue;
2172
2173			try {
2174				$p->enabled = $args['serialPorts'][$i]['enabled'];
2175				$p->IOBase = @hexdec($args['serialPorts'][$i]['IOBase']);
2176				$p->IRQ = intval($args['serialPorts'][$i]['IRQ']);
2177				if($args['serialPorts'][$i]['path']) {
2178					$p->path = $args['serialPorts'][$i]['path'];
2179					$p->hostMode = $args['serialPorts'][$i]['hostMode'];
2180				} else {
2181					$p->hostMode = $args['serialPorts'][$i]['hostMode'];
2182					$p->path = $args['serialPorts'][$i]['path'];
2183				}
2184				$p->server = $args['serialPorts'][$i]['server'];
2185				$p->releaseRemote();
2186			} catch (Exception $e) {
2187				$this->errors[] = $e;
2188			}
2189		}
2190
2191		// LPT Ports
2192		if(@$this->settings->enableLPTConfig) {
2193			$lptChanged = false;
2194
2195			for($i = 0; $i < count($args['parallelPorts']); $i++) {
2196
2197				/* @var $p IParallelPort */
2198				$p = $m->getParallelPort($i);
2199
2200				if(!($p->enabled || $args['parallelPorts'][$i]['enabled']))
2201					continue;
2202
2203				$lptChanged = true;
2204				try {
2205					$p->IOBase = @hexdec($args['parallelPorts'][$i]['IOBase']);
2206					$p->IRQ = intval($args['parallelPorts'][$i]['IRQ']);
2207					$p->path = $args['parallelPorts'][$i]['path'];
2208					$p->enabled = $args['parallelPorts'][$i]['enabled'];
2209					$p->releaseRemote();
2210				} catch (Exception $e) {
2211					$this->errors[] = $e;
2212				}
2213			}
2214		}
2215
2216
2217		$sharedEx = array();
2218		$sharedNew = array();
2219		foreach($this->_machineGetSharedFolders($m) as $s) {
2220			$sharedEx[$s['name']] = array('name'=>$s['name'],'hostPath'=>$s['hostPath'],'autoMount'=>(bool)$s['autoMount'],'writable'=>(bool)$s['writable']);
2221		}
2222		foreach($args['sharedFolders'] as $s) {
2223			$sharedNew[$s['name']] = array('name'=>$s['name'],'hostPath'=>$s['hostPath'],'autoMount'=>(bool)$s['autoMount'],'writable'=>(bool)$s['writable']);
2224		}
2225		// Compare
2226		if(count($sharedEx) != count($sharedNew) || (@serialize($sharedEx) != @serialize($sharedNew))) {
2227			foreach($sharedEx as $s) { $m->removeSharedFolder($s['name']);}
2228			try {
2229				foreach($sharedNew as $s) {
2230					$m->createSharedFolder($s['name'],$s['hostPath'],(bool)$s['writable'],(bool)$s['autoMount']);
2231				}
2232			} catch (Exception $e) { $this->errors[] = $e; }
2233		}
2234
2235		// USB Filters
2236
2237		$usbEx = array();
2238		$usbNew = array();
2239
2240		$usbc = $this->_machineGetUSBControllers($this->session->machine);
2241		if(!$args['USBControllers'] || !is_array($args['USBControllers'])) $args['USBControllers'] = array();
2242
2243		// Remove old
2244		$newNames = array();
2245		$newByName = array();
2246		foreach($args['USBControllers'] as $c) {
2247			$newNames[] = $c['name'];
2248			$newByName[$c['name']] = $c;
2249		}
2250		$exNames = array();
2251		foreach($usbc as $c) {
2252			$exNames[] = $c['name'];
2253			if(in_array($c['name'], $newNames)) continue;
2254			$this->session->machine->removeUSBController($c['name']);
2255		}
2256
2257		$addNames = array_diff($newNames, $exNames);
2258		foreach($addNames as $name) {
2259			$this->session->machine->addUSBController($name, $newByName[$name]['type']);
2260		}
2261
2262		// filters
2263		$deviceFilters = $this->_machineGetUSBDeviceFilters($this->session->machine);
2264		if(!is_array($args['USBDeviceFilters'])) $args['USBDeviceFilters'] = array();
2265
2266		if(count($deviceFilters) != count($args['USBDeviceFilters']) || @serialize($deviceFilters) != @serialize($args['USBDeviceFilters'])) {
2267
2268			// usb filter properties to change
2269			$usbProps = array('vendorId','productId','revision','manufacturer','product','serialNumber','port','remote');
2270
2271			// Remove and Add filters
2272			try {
2273
2274
2275				$max = max(count($deviceFilters),count($args['USBDeviceFilters']));
2276				$offset = 0;
2277
2278				// Remove existing
2279				for($i = 0; $i < $max; $i++) {
2280
2281					// Only if filter differs
2282					if(@serialize($deviceFilters[$i]) != @serialize($args['USBDeviceFilters'][$i])) {
2283
2284						// Remove existing?
2285						if($i < count($deviceFilters)) {
2286							$m->USBDeviceFilters->removeDeviceFilter(($i-$offset));
2287							$offset++;
2288						}
2289
2290						// Exists in new?
2291						if(count($args['USBDeviceFilters'][$i])) {
2292
2293							// Create filter
2294							$f = $m->USBDeviceFilters->createDeviceFilter($args['USBDeviceFilters'][$i]['name']);
2295							$f->active = (bool)$args['USBDeviceFilters'][$i]['active'];
2296
2297							foreach($usbProps as $p) {
2298								$f->$p = $args['USBDeviceFilters'][$i][$p];
2299							}
2300
2301							$m->USBDeviceFilters->insertDeviceFilter($i,$f->handle);
2302							$f->releaseRemote();
2303							$offset--;
2304						}
2305					}
2306
2307				}
2308
2309			} catch (Exception $e) { $this->errors[] = $e; }
2310
2311		}
2312
2313		// Rename goes last
2314		if($m->name != $args['name']) {
2315			$m->name = $args['name'];
2316		}
2317		$this->session->machine->saveSettings();
2318
2319
2320		$this->session->unlockMachine();
2321		unset($this->session);
2322		$machine->releaseRemote();
2323
2324		return true;
2325
2326	}
2327
2328	/**
2329	 * Add a virtual machine via its settings file.
2330	 *
2331	 * @param array $args array of arguments. See function body for details.
2332	 * @return boolean true on success
2333	 */
2334	public function remote_machineAdd($args) {
2335
2336		$this->connect();
2337
2338
2339		/* @var $m IMachine */
2340		$m = $this->vbox->openMachine($args['file']);
2341		$this->vbox->registerMachine($m->handle);
2342
2343		$m->releaseRemote();
2344
2345		return true;
2346
2347	}
2348
2349	/**
2350	 * Get progress operation status. On completion, destory progress operation.
2351	 *
2352	 * @param array $args array of arguments. See function body for details.
2353	 * @return array response data
2354	 */
2355	public function remote_progressGet($args) {
2356
2357		// progress operation result
2358		$response = array();
2359		$error = 0;
2360
2361		// Connect to vboxwebsrv
2362		$this->connect();
2363
2364		try {
2365
2366			try {
2367
2368				// Force web call to keep session open.
2369				if($this->persistentRequest['sessionHandle']) {
2370    				$this->session = new ISession($this->client, $this->persistentRequest['sessionHandle']);
2371    				if((string)$this->session->state) {}
2372				}
2373
2374				/* @var $progress IProgress */
2375				$progress = new IProgress($this->client, $args['progress']);
2376
2377			} catch (Exception $e) {
2378				$this->errors[] = $e;
2379				throw new Exception('Could not obtain progress operation: '.$args['progress']);
2380			}
2381
2382			$response['progress'] = $args['progress'];
2383
2384			$response['info'] = array(
2385				'completed' => $progress->completed,
2386				'canceled' => $progress->canceled,
2387				'description' => $progress->description,
2388				'operationDescription' => $progress->operationDescription,
2389				'timeRemaining' => $this->_util_splitTime($progress->timeRemaining),
2390				'timeElapsed' => $this->_util_splitTime((time() - $pop['started'])),
2391				'percent' => $progress->percent
2392			);
2393
2394
2395			// Completed? Do not return. Fall to _util_progressDestroy() called later
2396			if($response['info']['completed'] || $response['info']['canceled']) {
2397
2398				try {
2399					if(!$response['info']['canceled'] && $progress->errorInfo->handle) {
2400						$error = array('message'=>$progress->errorInfo->text,'err'=>$this->_util_resultCodeText($progress->resultCode));
2401					}
2402				} catch (Exception $null) {}
2403
2404
2405			} else {
2406
2407				$response['info']['cancelable'] = $progress->cancelable;
2408
2409				return $response;
2410			}
2411
2412
2413		} catch (Exception $e) {
2414
2415			// Force progress dialog closure
2416			$response['info'] = array('completed'=>1);
2417
2418			// Does an exception exist?
2419			try {
2420				if($progress->errorInfo->handle) {
2421					$error = array('message'=>$progress->errorInfo->text,'err'=>$this->_util_resultCodeText($progress->resultCode));
2422				}
2423			} catch (Exception $null) {}
2424
2425		}
2426
2427		if($error) {
2428			if(@$args['catcherrs']) $response['error'] = $error;
2429			else $this->errors[] = new Exception($error['message']);
2430
2431		}
2432
2433		$this->_util_progressDestroy($pop);
2434
2435		return $response;
2436
2437	}
2438
2439	/**
2440	 * Cancel a running progress operation
2441	 *
2442	 * @param array $args array of arguments. See function body for details.
2443	 * @param array $response response data passed byref populated by the function
2444	 * @return boolean true on success
2445	 */
2446	public function remote_progressCancel($args) {
2447
2448		// Connect to vboxwebsrv
2449		$this->connect();
2450
2451		try {
2452			/* @var $progress IProgress */
2453			$progress = new IProgress($this->client,$args['progress']);
2454			if(!($progress->completed || $progress->canceled))
2455				$progress->cancel();
2456		} catch (Exception $e) {
2457			$this->errors[] = $e;
2458		}
2459
2460		return true;
2461	}
2462
2463	/**
2464	 * Destory a progress operation.
2465	 *
2466	 * @param array $pop progress operation details
2467	 * @return boolean true on success
2468	 */
2469	private function _util_progressDestroy($pop) {
2470
2471		// Connect to vboxwebsrv
2472		$this->connect();
2473
2474		try {
2475			/* @var $progress IProgress */
2476			$progress = new IProgress($this->client,$pop['progress']);
2477			$progress->releaseRemote();
2478		} catch (Exception $e) {}
2479		try {
2480
2481			// Close session and logoff
2482			try {
2483
2484				if($this->session->handle) {
2485				    if((string)$this->session->state != 'Unlocked') {
2486    					$this->session->unlockMachine();
2487				    }
2488    				$this->session->releaseRemote();
2489    				unset($this->session);
2490				}
2491
2492
2493			} catch (Exception $e) {
2494				$this->errors[] = $e;
2495			}
2496
2497
2498			// Logoff session associated with progress operation
2499			$this->websessionManager->logoff($this->vbox->handle);
2500			unset($this->vbox);
2501
2502		} catch (Exception $e) {
2503			$this->errors[] = $e;
2504		}
2505
2506		// Remove progress handles
2507		$this->persistentRequest = array();
2508
2509		return true;
2510	}
2511
2512	/**
2513	 * Returns a key => value mapping of an enumeration class contained
2514	 * in vboxServiceWrappers.php (classes that extend VBox_Enum).
2515	 *
2516	 * @param array $args array of arguments. See function body for details.
2517	 * @return array response data
2518	 * @see vboxServiceWrappers.php
2519	 */
2520	public function remote_vboxGetEnumerationMap($args) {
2521
2522		$c = new $args['class'](null, null);
2523		return (@isset($args['ValueMap']) ? $c->ValueMap : $c->NameMap);
2524	}
2525
2526	/**
2527	 * Save VirtualBox system properties
2528	 *
2529	 * @param array $args array of arguments. See function body for details.
2530	 * @return boolean true on success
2531	 */
2532	public function remote_vboxSystemPropertiesSave($args) {
2533
2534		// Connect to vboxwebsrv
2535		$this->connect();
2536
2537		$this->vbox->systemProperties->defaultMachineFolder = $args['SystemProperties']['defaultMachineFolder'];
2538		$this->vbox->systemProperties->VRDEAuthLibrary = $args['SystemProperties']['VRDEAuthLibrary'];
2539		if(@$this->settings->vboxAutostartConfig) {
2540			$this->vbox->systemProperties->autostartDatabasePath = $args['SystemProperties']['autostartDatabasePath'];
2541		}
2542
2543		return true;
2544
2545	}
2546
2547	/**
2548	 * Import a virtual appliance
2549	 *
2550	 * @param array $args array of arguments. See function body for details.
2551	 * @return array response data
2552	 */
2553	public function remote_applianceImport($args) {
2554
2555		// Connect to vboxwebsrv
2556		$this->connect();
2557
2558		/* @var $app IAppliance */
2559		$app = $this->vbox->createAppliance();
2560
2561		/* @var $progress IProgress */
2562		$progress = $app->read($args['file']);
2563
2564		// Does an exception exist?
2565		try {
2566			if($progress->errorInfo->handle) {
2567				$this->errors[] = new Exception($progress->errorInfo->text);
2568				$app->releaseRemote();
2569				return false;
2570			}
2571		} catch (Exception $null) {}
2572
2573		$progress->waitForCompletion(-1);
2574
2575		$app->interpret();
2576
2577		$a = 0;
2578		foreach($app->virtualSystemDescriptions as $d) { /* @var $d IVirtualSystemDescription */
2579			// Replace with passed values
2580			$args['descriptions'][$a][5] = array_pad($args['descriptions'][$a][5], count($args['descriptions'][$a][3]),true);
2581			foreach(array_keys($args['descriptions'][$a][5]) as $k) $args['descriptions'][$a][5][$k] = (bool)$args['descriptions'][$a][5][$k];
2582			$d->setFinalValues($args['descriptions'][$a][5],$args['descriptions'][$a][3],$args['descriptions'][$a][4]);
2583			$a++;
2584		}
2585
2586		/* @var $progress IProgress */
2587		$progress = $app->importMachines(array($args['reinitNetwork'] ? 'KeepNATMACs' : 'KeepAllMACs'));
2588
2589		$app->releaseRemote();
2590
2591		// Does an exception exist?
2592		try {
2593			if($progress->errorInfo->handle) {
2594				$this->errors[] = new Exception($progress->errorInfo->text);
2595				$progress->releaseRemote();
2596				return false;
2597			}
2598		} catch (Exception $null) {}
2599
2600		// Save progress
2601		$this->_util_progressStore($progress);
2602
2603		return array('progress' => $progress->handle);
2604
2605	}
2606
2607	/**
2608	 * Get a list of VMs that are available for export.
2609	 *
2610	 * @param array $args array of arguments. See function body for details.
2611	 * @return array list of exportable machiens
2612	 */
2613	public function remote_vboxGetExportableMachines($args) {
2614
2615		// Connect to vboxwebsrv
2616		$this->connect();
2617
2618		//Get a list of registered machines
2619		$machines = $this->vbox->machines;
2620
2621		$response = array();
2622
2623		foreach ($machines as $machine) { /* @var $machine IMachine */
2624
2625			if ( @$this->settings->enforceVMOwnership && ($owner = $machine->getExtraData("phpvb/sso/owner")) && $owner !== $_SESSION['user'] && !$_SESSION['admin'] )
2626			{
2627				// skip this VM as it is not owned by the user we're logged in as
2628				continue;
2629			}
2630
2631			try {
2632				$response[] = array(
2633					'name' => @$this->settings->enforceVMOwnership ? preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $machine->name) : $machine->name,
2634					'state' => (string)$machine->state,
2635					'OSTypeId' => $machine->getOSTypeId(),
2636					'id' => $machine->id,
2637					'description' => $machine->description
2638				);
2639				$machine->releaseRemote();
2640
2641			} catch (Exception $e) {
2642				// Ignore. Probably inaccessible machine.
2643			}
2644		}
2645		return $response;
2646	}
2647
2648
2649	/**
2650	 * Read and interpret virtual appliance file
2651	 *
2652	 * @param array $args array of arguments. See function body for details.
2653	 * @return array appliance file content descriptions
2654	 */
2655	public function remote_applianceReadInterpret($args) {
2656
2657		// Connect to vboxwebsrv
2658		$this->connect();
2659
2660		/* @var $app IAppliance */
2661		$app = $this->vbox->createAppliance();
2662
2663		/* @var $progress IProgress */
2664		$progress = $app->read($args['file']);
2665
2666		// Does an exception exist?
2667		try {
2668			if($progress->errorInfo->handle) {
2669				$this->errors[] = new Exception($progress->errorInfo->text);
2670				$app->releaseRemote();
2671				return false;
2672			}
2673		} catch (Exception $null) {}
2674
2675		$progress->waitForCompletion(-1);
2676
2677		$app->interpret();
2678
2679		$response = array('warnings' => $app->getWarnings(),
2680			'descriptions' => array());
2681
2682		$i = 0;
2683		foreach($app->virtualSystemDescriptions as $d) { /* @var $d IVirtualSystemDescription */
2684			$desc = array();
2685			$response['descriptions'][$i] = $d->getDescription();
2686			foreach($response['descriptions'][$i][0] as $ddesc) {
2687				$desc[] = (string)$ddesc;
2688			}
2689			$response['descriptions'][$i][0] = $desc;
2690			$i++;
2691			$d->releaseRemote();
2692		}
2693		$app->releaseRemote();
2694		$app=null;
2695
2696		return $response;
2697
2698	}
2699
2700
2701	/**
2702	 * Export VMs to a virtual appliance file
2703	 *
2704	 * @param array $args array of arguments. See function body for details.
2705	 * @return array response data
2706	 */
2707	public function remote_applianceExport($args) {
2708
2709		// Connect to vboxwebsrv
2710		$this->connect();
2711
2712		/* @var $app IAppliance */
2713		$app = $this->vbox->createAppliance();
2714
2715		// Overwrite existing file?
2716		if($args['overwrite']) {
2717
2718			$dsep = $this->getDsep();
2719
2720			$path = str_replace($dsep.$dsep,$dsep,$args['file']);
2721			$dir = dirname($path);
2722			$file = basename($path);
2723
2724			if(substr($dir,-1) != $dsep) $dir .= $dsep;
2725
2726			/* @var $vfs IVFSExplorer */
2727			$vfs = $app->createVFSExplorer('file://'.$dir);
2728
2729			/* @var $progress IProgress */
2730			$progress = $vfs->remove(array($file));
2731			$progress->waitForCompletion(-1);
2732			$progress->releaseRemote();
2733
2734			$vfs->releaseRemote();
2735		}
2736
2737		$appProps = array(
2738			'name' => 'Name',
2739			'description' => 'Description',
2740			'product' => 'Product',
2741			'vendor' => 'Vendor',
2742			'version' => 'Version',
2743			'product-url' => 'ProductUrl',
2744			'vendor-url' => 'VendorUrl',
2745			'license' => 'License');
2746
2747
2748		foreach($args['vms'] as $vm) {
2749
2750			/* @var $m IMachine */
2751			$m = $this->vbox->findMachine($vm['id']);
2752			if (@$this->settings->enforceVMOwnership && ($owner = $m->getExtraData("phpvb/sso/owner")) && $owner !== $_SESSION['user'] && !$_SESSION['admin'] )
2753			{
2754				// skip this VM as it is not owned by the user we're logged in as
2755				continue;
2756			}
2757			$desc = $m->exportTo($app->handle, $args['file']);
2758			$props = $desc->getDescription();
2759			$ptypes = array();
2760			foreach($props[0] as $p) {$ptypes[] = (string)$p;}
2761			$typecount = 0;
2762			foreach($appProps as $k=>$v) {
2763				// Check for existing property
2764				if(($i=array_search($v,$ptypes)) !== false) {
2765					$props[3][$i] = $vm[$k];
2766				} else {
2767					$desc->addDescription($v,$vm[$k],null);
2768					$props[3][] = $vm[$k];
2769					$props[4][] = null;
2770				}
2771				$typecount++;
2772			}
2773			$enabled = array_pad(array(),count($props[3]),true);
2774			foreach(array_keys($enabled) as $k) $enabled[$k] = (bool)$enabled[$k];
2775			$desc->setFinalValues($enabled,$props[3],$props[4]);
2776			$desc->releaseRemote();
2777			$m->releaseRemote();
2778		}
2779
2780		/* @var $progress IProgress */
2781		$progress = $app->write($args['format'],($args['manifest'] ? array('CreateManifest') : array()),$args['file']);
2782		$app->releaseRemote();
2783
2784		// Does an exception exist?
2785		try {
2786			if($progress->errorInfo->handle) {
2787				$this->errors[] = new Exception($progress->errorInfo->text);
2788				$progress->releaseRemote();
2789				return false;
2790			}
2791		} catch (Exception $null) {}
2792
2793		// Save progress
2794		$this->_util_progressStore($progress);
2795
2796		return array('progress' => $progress->handle);
2797
2798	}
2799
2800	/**
2801	 * Get nat network info
2802	 *
2803	 * @param unused $args
2804	 * @param array $response response data passed byref populated by the function
2805	 * @return array networking info data
2806	 */
2807	public function remote_vboxNATNetworksGet($args) {
2808
2809		$this->connect();
2810
2811		$props = array('networkName','enabled','network','IPv6Enabled',
2812				'advertiseDefaultIPv6RouteEnabled','needDhcpServer','portForwardRules4',
2813				'portForwardRules6');
2814
2815		$natNetworks = array();
2816
2817		foreach($this->vbox->NATNetworks as $n) {
2818
2819
2820			$netDetails = array();
2821			foreach($props as $p) {
2822				$netDetails[$p] = $n->$p;
2823			}
2824
2825			$natNetworks[] = $netDetails;
2826		}
2827
2828		return $natNetworks;
2829
2830	}
2831
2832	/**
2833	 * Get nat network details
2834	 *
2835	 * @param array $args contains network name
2836	 * @param array $response response data passed byref populated by the function
2837	 * @return array networking info data
2838	 */
2839	public function remote_vboxNATNetworksSave($args) {
2840
2841		$this->connect();
2842
2843		$props = array('networkName','enabled','network','IPv6Enabled',
2844				'advertiseDefaultIPv6RouteEnabled','needDhcpServer');
2845
2846		$exNetworks = array();
2847		foreach($this->vbox->NATNetworks as $n) { $exNetworks[$n->networkName] = false; }
2848
2849		/* Incoming network list */
2850		foreach($args['networks'] as $net) {
2851
2852			/* Existing network */
2853			if($net['orig_networkName']) {
2854
2855				$network = $this->vbox->findNATNetworkByName($net['orig_networkName']);
2856
2857
2858				$exNetworks[$net['orig_networkName']] = true;
2859
2860				foreach($props as $p) {
2861					$network->$p = $net[$p];
2862				}
2863
2864				foreach(array('portForwardRules4','portForwardRules6') as $rules) {
2865
2866					if(!$net[$rules] || !is_array($net[$rules])) $net[$rules] = array();
2867
2868					$rules_remove = array_diff($network->$rules, $net[$rules]);
2869					$rules_add = array_diff($net[$rules], $network->$rules);
2870
2871					foreach($rules_remove as $rule) {
2872						$network->removePortForwardRule((strpos($rules,'6')>-1), array_shift(preg_split('/:/',$rule)));
2873					}
2874					foreach($rules_add as $r) {
2875						preg_match('/(.*?):(.+?):\[(.*?)\]:(\d+):\[(.*?)\]:(\d+)/', $r, $rule);
2876						array_shift($rule);
2877						$network->addPortForwardRule((strpos($rules,'6')>-1), $rule[0],strtoupper($rule[1]),$rule[2],$rule[3],$rule[4],$rule[5]);
2878					}
2879				}
2880
2881			/* New network */
2882			} else {
2883
2884				$network = $this->vbox->createNATNetwork($net['networkName']);
2885
2886				foreach($props as $p) {
2887					if($p == 'network' && $net[$p] == '') continue;
2888					$network->$p = $net[$p];
2889				}
2890
2891				foreach($net['portForwardRules4'] as $r) {
2892					preg_match('/(.*?):(.+?):\[(.*?)\]:(\d+):\[(.*?)\]:(\d+)/', $r, $rule);
2893					array_shift($rule);
2894					$network->addPortForwardRule(false, $rule[0],strtoupper($rule[1]),$rule[2],$rule[3],$rule[4],$rule[5]);
2895				}
2896				foreach($net['portForwardRules6'] as $r) {
2897					preg_match('/(.*?):(.+?):\[(.*?)\]:(\d+):\[(.*?)\]:(\d+)/', $r, $rule);
2898					array_shift($rule);
2899					$network->addPortForwardRule(true, $rule[0],strtoupper($rule[1]),$rule[2],$rule[3],$rule[4],$rule[5]);
2900				}
2901
2902			}
2903
2904		}
2905
2906		/* Remove networks not in list */
2907		foreach($exNetworks as $n=>$v) {
2908			if($v) continue;
2909			$n = $this->vbox->findNATNetworkByName($n);
2910			$this->vbox->removeNATNetwork($n);
2911		}
2912
2913		return true;
2914
2915	}
2916
2917	/**
2918	 * Get networking info
2919	 *
2920	 * @param unused $args
2921	 * @param array $response response data passed byref populated by the function
2922	 * @return array networking info data
2923	 */
2924	public function remote_getNetworking($args) {
2925
2926		// Connect to vboxwebsrv
2927		$this->connect();
2928
2929		$response = array();
2930		$networks = array();
2931		$nics = array();
2932		$genericDrivers = array();
2933		$vdenetworks = array();
2934
2935		/* Get host nics */
2936		foreach($this->vbox->host->networkInterfaces as $d) { /* @var $d IHostNetworkInterface */
2937			$nics[] = $d->name;
2938			$d->releaseRemote();
2939		}
2940
2941		/* Get internal Networks */
2942		$networks = $this->vbox->internalNetworks;
2943		/* Generic Drivers */
2944		$genericDrivers = $this->vbox->genericNetworkDrivers;
2945
2946		$natNetworks = array();
2947		foreach($this->vbox->NATNetworks as $n) {
2948			$natNetworks[] = $n->networkName;
2949		}
2950
2951		return array(
2952			'nics' => $nics,
2953			'networks' => $networks,
2954			'genericDrivers' => $genericDrivers,
2955			'vdenetworks' => $vdenetworks,
2956			'natNetworks' => $natNetworks
2957		);
2958
2959	}
2960
2961	/**
2962	 * Get host-only interface information
2963	 *
2964	 * @param unused $args
2965	 * @return array host only interface data
2966	 */
2967	public function remote_hostOnlyInterfacesGet($args) {
2968
2969		// Connect to vboxwebsrv
2970		$this->connect();
2971
2972		/*
2973		 * NICs
2974		 */
2975		$response = array('networkInterfaces' => array());
2976		foreach($this->vbox->host->networkInterfaces as $d) { /* @var $d IHostNetworkInterface */
2977
2978			if((string)$d->interfaceType != 'HostOnly') {
2979				$d->releaseRemote();
2980				continue;
2981			}
2982
2983
2984			// Get DHCP Info
2985			try {
2986				/* @var $dhcp IDHCPServer */
2987				$dhcp = $this->vbox->findDHCPServerByNetworkName($d->networkName);
2988				if($dhcp->handle) {
2989					$dhcpserver = array(
2990						'enabled' => $dhcp->enabled,
2991						'IPAddress' => $dhcp->IPAddress,
2992						'networkMask' => $dhcp->networkMask,
2993						'networkName' => $dhcp->networkName,
2994						'lowerIP' => $dhcp->lowerIP,
2995						'upperIP' => $dhcp->upperIP
2996					);
2997					$dhcp->releaseRemote();
2998				} else {
2999					$dhcpserver = array();
3000				}
3001			} catch (Exception $e) {
3002				$dhcpserver = array();
3003			}
3004
3005			$response['networkInterfaces'][] = array(
3006				'id' => $d->id,
3007				'IPV6Supported' => $d->IPV6Supported,
3008				'name' => $d->name,
3009				'IPAddress' => $d->IPAddress,
3010				'networkMask' => $d->networkMask,
3011				'IPV6Address' => $d->IPV6Address,
3012				'IPV6NetworkMaskPrefixLength' => $d->IPV6NetworkMaskPrefixLength,
3013				'DHCPEnabled' => $d->DHCPEnabled,
3014				'networkName' => $d->networkName,
3015				'dhcpServer' => $dhcpserver
3016			);
3017			$d->releaseRemote();
3018		}
3019
3020		return $response;
3021	}
3022
3023
3024	/**
3025	 * Save host-only interface information
3026	 *
3027	 * @param array $args array of arguments. See function body for details.
3028	 * @return boolean true on success
3029	 */
3030	public function remote_hostOnlyInterfacesSave($args) {
3031
3032		// Connect to vboxwebsrv
3033		$this->connect();
3034
3035		$nics = $args['networkInterfaces'];
3036
3037		for($i = 0; $i < count($nics); $i++) {
3038
3039			/* @var $nic IHostNetworkInterface */
3040			$nic = $this->vbox->host->findHostNetworkInterfaceById($nics[$i]['id']);
3041
3042			// Common settings
3043			if($nic->IPAddress != $nics[$i]['IPAddress'] || $nic->networkMask != $nics[$i]['networkMask']) {
3044				$nic->enableStaticIPConfig($nics[$i]['IPAddress'],$nics[$i]['networkMask']);
3045			}
3046			if($nics[$i]['IPV6Supported'] &&
3047				($nic->IPV6Address != $nics[$i]['IPV6Address'] || $nic->IPV6NetworkMaskPrefixLength != $nics[$i]['IPV6NetworkMaskPrefixLength'])) {
3048				$nic->enableStaticIPConfigV6($nics[$i]['IPV6Address'],intval($nics[$i]['IPV6NetworkMaskPrefixLength']));
3049			}
3050
3051			// Get DHCP Info
3052			try {
3053				$dhcp = $this->vbox->findDHCPServerByNetworkName($nic->networkName);
3054			} catch (Exception $e) {$dhcp = null;};
3055
3056			// Create DHCP server?
3057			if((bool)@$nics[$i]['dhcpServer']['enabled'] && !$dhcp) {
3058				$dhcp = $this->vbox->createDHCPServer($nic->networkName);
3059			}
3060			if($dhcp->handle) {
3061				$dhcp->enabled = @$nics[$i]['dhcpServer']['enabled'];
3062				$dhcp->setConfiguration($nics[$i]['dhcpServer']['IPAddress'],$nics[$i]['dhcpServer']['networkMask'],$nics[$i]['dhcpServer']['lowerIP'],$nics[$i]['dhcpServer']['upperIP']);
3063				$dhcp->releaseRemote();
3064			}
3065			$nic->releaseRemote();
3066
3067		}
3068
3069		return true;
3070
3071	}
3072
3073	/**
3074	 * Add Host-only interface
3075	 *
3076	 * @param array $args array of arguments. See function body for details.
3077	 * @return array response data
3078	 */
3079	public function remote_hostOnlyInterfaceCreate($args) {
3080
3081		// Connect to vboxwebsrv
3082		$this->connect();
3083
3084		/* @var $progress IProgress */
3085		list($int,$progress) = $this->vbox->host->createHostOnlyNetworkInterface();
3086		$int->releaseRemote();
3087
3088		// Does an exception exist?
3089		try {
3090			if($progress->errorInfo->handle) {
3091				$this->errors[] = new Exception($progress->errorInfo->text);
3092				$progress->releaseRemote();
3093				return false;
3094			}
3095		} catch (Exception $null) {}
3096
3097		// Save progress
3098		$this->_util_progressStore($progress);
3099
3100		return array('progress' => $progress->handle);
3101
3102
3103	}
3104
3105
3106	/**
3107	 * Remove a host-only interface
3108	 *
3109	 * @param array $args array of arguments. See function body for details.
3110	 * @return array response data
3111	 */
3112	public function remote_hostOnlyInterfaceRemove($args) {
3113
3114		// Connect to vboxwebsrv
3115		$this->connect();
3116
3117		/* @var $progress IProgress */
3118		$progress = $this->vbox->host->removeHostOnlyNetworkInterface($args['id']);
3119
3120		if(!$progress->handle) return false;
3121
3122		// Does an exception exist?
3123		try {
3124			if($progress->errorInfo->handle) {
3125				$this->errors[] = new Exception($progress->errorInfo->text);
3126				$progress->releaseRemote();
3127				return false;
3128			}
3129		} catch (Exception $null) {}
3130
3131		// Save progress
3132		$this->_util_progressStore($progress);
3133
3134		return array('progress' => $progress->handle);
3135
3136	}
3137
3138	/**
3139	 * Get a list of Guest OS Types supported by this VirtualBox installation
3140	 *
3141	 * @param unused $args
3142	 * @return array of os types
3143	 */
3144	public function remote_vboxGetGuestOSTypes($args) {
3145
3146		// Connect to vboxwebsrv
3147		$this->connect();
3148
3149		$response = array();
3150
3151		$ts = $this->vbox->getGuestOSTypes();
3152
3153		$supp64 = ($this->vbox->host->getProcessorFeature('LongMode') && $this->vbox->host->getProcessorFeature('HWVirtEx'));
3154
3155		foreach($ts as $g) { /* @var $g IGuestOSType */
3156
3157			// Avoid multiple calls
3158			$bit64 = $g->is64Bit;
3159			$response[] = array(
3160				'familyId' => $g->familyId,
3161				'familyDescription' => $g->familyDescription,
3162				'id' => $g->id,
3163				'description' => $g->description,
3164				'is64Bit' => $bit64,
3165				'recommendedRAM' => $g->recommendedRAM,
3166				'recommendedHDD' => ($g->recommendedHDD/1024)/1024,
3167				'supported' => (bool)(!$bit64 || $supp64)
3168			);
3169		}
3170
3171		return $response;
3172	}
3173
3174	/**
3175	 * Set virtual machine state. Running, power off, save state, pause, etc..
3176	 *
3177	 * @param array $args array of arguments. See function body for details.
3178	 * @return array response data or boolean true on success
3179	 */
3180	public function remote_machineSetState($args) {
3181
3182		$vm = $args['vm'];
3183		$state = $args['state'];
3184
3185		$states = array(
3186			'powerDown' => array('result'=>'PoweredOff','progress'=>2),
3187			'reset' => array(),
3188			'saveState' => array('result'=>'Saved','progress'=>2),
3189			'powerButton' => array('acpi'=>true),
3190			'sleepButton' => array('acpi'=>true),
3191			'pause' => array('result'=>'Paused','progress'=>false),
3192			'resume' => array('result'=>'Running','progress'=>false),
3193			'powerUp' => array('result'=>'Running'),
3194			'discardSavedState' => array('result'=>'poweredOff','lock'=>'shared','force'=>true)
3195		);
3196
3197		// Check for valid state
3198		if(!is_array($states[$state])) {
3199			throw new Exception('Invalid state: ' . $state);
3200		}
3201
3202		// Connect to vboxwebsrv
3203		$this->connect();
3204
3205		// Machine state
3206		/* @var $machine IMachine */
3207		$machine = $this->vbox->findMachine($vm);
3208		$mstate = (string)$machine->state;
3209
3210		if (@$this->settings->enforceVMOwnership && !$this->skipSessionCheck && ($owner = $machine->getExtraData("phpvb/sso/owner")) && $owner !== $_SESSION['user'] && !$_SESSION['admin'] )
3211		{
3212			// skip this VM as it is not owned by the user we're logged in as
3213			throw new Exception("Not authorized to change state of this VM");
3214		}
3215
3216		// If state has an expected result, check
3217		// that we are not already in it
3218		if($states[$state]['result']) {
3219			if($mstate == $states[$state]['result']) {
3220				$machine->releaseRemote();
3221				return false;
3222			}
3223		}
3224
3225		// Special case for power up
3226		if($state == 'powerUp' && $mstate == 'Paused')
3227			$state = 'resume';
3228
3229		if($state == 'powerUp') {
3230
3231
3232			# Try opening session for VM
3233			try {
3234
3235				// create session
3236				$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
3237
3238				// set first run
3239				if($machine->getExtraData('GUI/FirstRun') == 'yes') {
3240					$machine->lockMachine($this->session->handle, 'Write');
3241					$this->session->machine->setExtraData('GUI/FirstRun', 'no');
3242					$this->session->unlockMachine();
3243				}
3244
3245				/* @var $progress IProgress */
3246				$progress = $machine->launchVMProcess($this->session->handle, "headless", "");
3247
3248			} catch (Exception $e) {
3249				// Error opening session
3250				$this->errors[] = $e;
3251				return false;
3252			}
3253
3254			// Does an exception exist?
3255			try {
3256				if($progress->errorInfo->handle) {
3257					$this->errors[] = new Exception($progress->errorInfo->text);
3258					$progress->releaseRemote();
3259					return false;
3260				}
3261			} catch (Exception $null) {
3262			}
3263
3264			$this->_util_progressStore($progress);
3265
3266			return array('progress' => $progress->handle);
3267
3268
3269		}
3270
3271		// Open session to machine
3272		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
3273
3274		// Lock machine
3275		$machine->lockMachine($this->session->handle,($states[$state]['lock'] == 'write' ? 'Write' : 'Shared'));
3276
3277		// If this operation returns a progress object save progress
3278		$progress = null;
3279		if($states[$state]['progress']) {
3280
3281			/* @var $progress IProgress */
3282			if($state == 'saveState') {
3283				$progress = $this->session->machine->saveState();
3284			} else {
3285				$progress = $this->session->console->$state();
3286			}
3287
3288			if(!$progress->handle) {
3289
3290				// should never get here
3291				try {
3292					$this->session->unlockMachine();
3293					$this->session = null;
3294				} catch (Exception $e) {};
3295
3296				$machine->releaseRemote();
3297
3298				throw new Exception('Unknown error settings machine to requested state.');
3299			}
3300
3301			// Does an exception exist?
3302			try {
3303				if($progress->errorInfo->handle) {
3304					$this->errors[] = new Exception($progress->errorInfo->text);
3305					$progress->releaseRemote();
3306					return false;
3307				}
3308			} catch (Exception $null) {}
3309
3310			// Save progress
3311			$this->_util_progressStore($progress);
3312
3313			return array('progress' => $progress->handle);
3314
3315		// Operation does not return a progress object
3316		// Just call the function
3317		} else {
3318
3319			if($state == 'discardSavedState') {
3320				$this->session->machine->$state(($states[$state]['force'] ? true : null));
3321			} else {
3322				$this->session->console->$state(($states[$state]['force'] ? true : null));
3323			}
3324
3325		}
3326
3327		$vmname = $machine->name;
3328		$machine->releaseRemote();
3329
3330		// Check for ACPI button
3331		if($states[$state]['acpi'] && !$this->session->console->getPowerButtonHandled()) {
3332			$this->session->console->releaseRemote();
3333			$this->session->unlockMachine();
3334			$this->session = null;
3335			return false;
3336		}
3337
3338
3339		if(!$progress->handle) {
3340			$this->session->console->releaseRemote();
3341			$this->session->unlockMachine();
3342			unset($this->session);
3343		}
3344
3345		return true;
3346
3347	}
3348
3349
3350	/**
3351	 * Get VirtualBox host memory usage information
3352	 *
3353	 * @param unused $args
3354	 * @return array response data
3355	 */
3356	public function remote_hostGetMeminfo($args) {
3357
3358		// Connect to vboxwebsrv
3359		$this->connect();
3360
3361		return $this->vbox->host->memoryAvailable;
3362
3363	}
3364
3365	/**
3366	 * Get VirtualBox host details
3367	 *
3368	 * @param unused $args
3369	 * @return array response data
3370	 */
3371	public function remote_hostGetDetails($args) {
3372
3373		// Connect to vboxwebsrv
3374		$this->connect();
3375
3376		/* @var $host IHost */
3377		$host = &$this->vbox->host;
3378		$response = array(
3379			'id' => 'host',
3380			'operatingSystem' => $host->operatingSystem,
3381			'OSVersion' => $host->OSVersion,
3382			'memorySize' => $host->memorySize,
3383			'acceleration3DAvailable' => $host->acceleration3DAvailable,
3384			'cpus' => array(),
3385			'networkInterfaces' => array(),
3386			'DVDDrives' => array(),
3387			'floppyDrives' => array()
3388		);
3389
3390		/*
3391		 * Processors
3392		 */
3393		// TODO https://github.com/phpvirtualbox/phpvirtualbox/issues/53
3394		$response['cpus'][0] = $host->getProcessorDescription(0);
3395		for($i = 1; $i < $host->processorCount; $i++) {
3396			$response['cpus'][$i] = $response['cpus'][0];
3397		}
3398
3399		/*
3400		 * Supported CPU features?
3401		 */
3402		$response['cpuFeatures'] = array();
3403		foreach(array('HWVirtEx'=>'HWVirtEx','PAE'=>'PAE','NestedPaging'=>'Nested Paging','LongMode'=>'Long Mode (64-bit)') as $k=>$v) {
3404			$response['cpuFeatures'][$v] = $host->getProcessorFeature($k);
3405		}
3406
3407		/*
3408		 * NICs
3409		 */
3410		foreach($host->networkInterfaces as $d) { /* @var $d IHostNetworkInterface */
3411			$response['networkInterfaces'][] = array(
3412				'name' => $d->name,
3413				'IPAddress' => $d->IPAddress,
3414				'networkMask' => $d->networkMask,
3415				'IPV6Supported' => $d->IPV6Supported,
3416				'IPV6Address' => $d->IPV6Address,
3417				'IPV6NetworkMaskPrefixLength' => $d->IPV6NetworkMaskPrefixLength,
3418				'status' => (string)$d->status,
3419				'mediumType' => (string)$d->mediumType,
3420				'interfaceType' => (string)$d->interfaceType,
3421				'hardwareAddress' => $d->hardwareAddress,
3422				'networkName' => $d->networkName,
3423			);
3424			$d->releaseRemote();
3425		}
3426
3427		/*
3428		 * Medium types (DVD and Floppy)
3429		 */
3430		foreach($host->DVDDrives as $d) { /* @var $d IMedium */
3431
3432			$response['DVDDrives'][] = array(
3433				'id' => $d->id,
3434				'name' => $d->name,
3435				'location' => $d->location,
3436				'description' => $d->description,
3437				'deviceType' => 'DVD',
3438				'hostDrive' => true
3439			);
3440			$d->releaseRemote();
3441		}
3442
3443		foreach($host->floppyDrives as $d) { /* @var $d IMedium */
3444
3445			$response['floppyDrives'][] = array(
3446				'id' => $d->id,
3447				'name' => $d->name,
3448				'location' => $d->location,
3449				'description' => $d->description,
3450				'deviceType' => 'Floppy',
3451				'hostDrive' => true,
3452			);
3453			$d->releaseRemote();
3454		}
3455		$host->releaseRemote();
3456
3457		return $response;
3458	}
3459
3460	/**
3461	 * Get a list of USB devices attached to the VirtualBox host
3462	 *
3463	 * @param unused $args
3464	 * @return array of USB devices
3465	 */
3466	public function remote_hostGetUSBDevices($args) {
3467
3468		// Connect to vboxwebsrv
3469		$this->connect();
3470
3471		$response = array();
3472
3473		foreach($this->vbox->host->USBDevices as $d) { /* @var $d IUSBDevice */
3474
3475			$response[] = array(
3476				'id' => $d->id,
3477				'vendorId' => sprintf('%04s',dechex($d->vendorId)),
3478				'productId' => sprintf('%04s',dechex($d->productId)),
3479				'revision' => sprintf('%04s',dechex($d->revision)),
3480				'manufacturer' => $d->manufacturer,
3481				'product' => $d->product,
3482				'serialNumber' => $d->serialNumber,
3483				'address' => $d->address,
3484				'port' => $d->port,
3485				'version' => $d->version,
3486				'portVersion' => $d->portVersion,
3487				'remote' => $d->remote,
3488				'state' => (string)$d->state,
3489				);
3490			$d->releaseRemote();
3491		}
3492
3493		return $response;
3494	}
3495
3496
3497	/**
3498	 * Get virtual machine or virtualbox host details
3499	 *
3500	 * @param array $args array of arguments. See function body for details.
3501	 * @param ISnapshot $snapshot snapshot instance to use if obtaining snapshot details.
3502	 * @see hostGetDetails()
3503	 * @return array machine details
3504	 */
3505	public function remote_machineGetDetails($args, $snapshot=null) {
3506
3507		// Host instead of vm info
3508		if($args['vm'] == 'host') {
3509
3510			$response = $this->remote_hostGetDetails($args);
3511
3512			return $response;
3513		}
3514
3515
3516		// Connect to vboxwebsrv
3517		$this->connect();
3518
3519		//Get registered machine or snapshot machine
3520		if($snapshot) {
3521
3522			/* @var $machine ISnapshot */
3523			$machine = &$snapshot;
3524
3525		} else {
3526
3527			/* @var $machine IMachine */
3528			$machine = $this->vbox->findMachine($args['vm']);
3529
3530
3531			// For correct caching, always use id even if a name was passed
3532			$args['vm'] = $machine->id;
3533
3534			// Check for accessibility
3535			if(!$machine->accessible) {
3536
3537				return array(
3538					'name' => $machine->id,
3539					'state' => 'Inaccessible',
3540					'OSTypeId' => 'Other',
3541					'id' => $machine->id,
3542					'sessionState' => 'Inaccessible',
3543					'accessible' => 0,
3544					'accessError' => array(
3545						'resultCode' => $this->_util_resultCodeText($machine->accessError->resultCode),
3546						'component' => $machine->accessError->component,
3547						'text' => $machine->accessError->text)
3548				);
3549			}
3550
3551		}
3552
3553		// Basic data
3554		$data = $this->_machineGetDetails($machine);
3555
3556		// Network Adapters
3557		$data['networkAdapters'] = $this->_machineGetNetworkAdapters($machine);
3558
3559		// Storage Controllers
3560		$data['storageControllers'] = $this->_machineGetStorageControllers($machine);
3561
3562		// Serial Ports
3563		$data['serialPorts'] = $this->_machineGetSerialPorts($machine);
3564
3565		// LPT Ports
3566		$data['parallelPorts'] = $this->_machineGetParallelPorts($machine);
3567
3568		// Shared Folders
3569		$data['sharedFolders'] = $this->_machineGetSharedFolders($machine);
3570
3571		// USB Controllers
3572		$data['USBControllers'] = $this->_machineGetUSBControllers($machine);
3573		$data['USBDeviceFilters'] = $this->_machineGetUSBDeviceFilters($machine);
3574
3575
3576		if (@$this->settings->enforceVMOwnership )
3577		{
3578			$data['name'] = preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $data['name']);
3579		}
3580
3581		// Items when not obtaining snapshot machine info
3582		if(!$snapshot) {
3583
3584			$data['currentSnapshot'] = ($machine->currentSnapshot->handle ? array('id'=>$machine->currentSnapshot->id,'name'=>$machine->currentSnapshot->name) : null);
3585			$data['snapshotCount'] = $machine->snapshotCount;
3586
3587			// Start / stop config
3588			if(@$this->settings->startStopConfig) {
3589				$data['startupMode'] = $machine->getExtraData('pvbx/startupMode');
3590			}
3591
3592
3593		}
3594
3595		$machine->releaseRemote();
3596
3597		$data['accessible'] = 1;
3598		return $data;
3599	}
3600
3601	/**
3602	 * Get runtime data of machine.
3603	 *
3604	 * @param array $args array of arguments. See function body for details.
3605	 * @return array of machine runtime data
3606	 */
3607	public function remote_machineGetRuntimeData($args) {
3608
3609		$this->connect();
3610
3611		/* @var $machine IMachine */
3612		$machine = $this->vbox->findMachine($args['vm']);
3613		$data = array(
3614			'id' => $args['vm'],
3615			'state' => (string)$machine->state
3616		);
3617
3618		/*
3619		 * TODO:
3620		 *
3621		 * 5.13.13 getGuestEnteredACPIMode
3622		boolean IConsole::getGuestEnteredACPIMode()
3623		Checks if the guest entered the ACPI mode G0 (working) or G1 (sleeping). If this method
3624		returns false, the guest will most likely not respond to external ACPI events.
3625		If this method fails, the following error codes may be reported:
3626		 VBOX_E_INVALID_VM_STATE: Virtual machine not in Running state.
3627		*/
3628
3629		// Get current console port
3630		if($data['state'] == 'Running' || $data['state'] == 'Paused') {
3631
3632			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
3633			$machine->lockMachine($this->session->handle, 'Shared');
3634			$console = $this->session->console;
3635
3636			// Get guest additions version
3637			if(@$this->settings->enableGuestAdditionsVersionDisplay) {
3638				$data['guestAdditionsVersion'] = $console->guest->additionsVersion;
3639			}
3640
3641			$smachine = $this->session->machine;
3642
3643			$data['CPUExecutionCap'] = $smachine->CPUExecutionCap;
3644			$data['VRDEServerInfo'] = array('port' => $console->VRDEServerInfo->port);
3645
3646			$vrde = $smachine->VRDEServer;
3647
3648			$data['VRDEServer'] = (!$vrde ? null : array(
3649					'enabled' => $vrde->enabled,
3650					'ports' => $vrde->getVRDEProperty('TCP/Ports'),
3651					'netAddress' => $vrde->getVRDEProperty('TCP/Address'),
3652					'VNCPassword' => $vrde->getVRDEProperty('VNCPassword'),
3653					'authType' => (string)$vrde->authType,
3654					'authTimeout' => $vrde->authTimeout,
3655					'VRDEExtPack' => (string)$vrde->VRDEExtPack
3656			));
3657
3658			// Get removable media
3659			$data['storageControllers'] = $this->_machineGetStorageControllers($smachine);
3660
3661			// Get network adapters
3662			$data['networkAdapters'] = $this->_machineGetNetworkAdapters($smachine);
3663
3664			$machine->releaseRemote();
3665
3666			// Close session and unlock machine
3667			$this->session->unlockMachine();
3668			unset($this->session);
3669
3670		}
3671
3672
3673		return $data;
3674
3675	}
3676
3677	/**
3678	 * Remove a virtual machine
3679	 *
3680	 * @param array $args array of arguments. See function body for details.
3681	 * @return boolean true on success or array of response data
3682	 */
3683	public function remote_machineRemove($args) {
3684
3685		// Connect to vboxwebsrv
3686		$this->connect();
3687
3688		/* @var $machine IMachine */
3689		$machine = $this->vbox->findMachine($args['vm']);
3690
3691		// Only unregister or delete?
3692		if(!$args['delete']) {
3693
3694			$machine->unregister('DetachAllReturnNone');
3695			$machine->releaseRemote();
3696
3697		} else {
3698
3699			$hds = array();
3700			$delete = $machine->unregister('DetachAllReturnHardDisksOnly');
3701			foreach($delete as $hd) {
3702				$hds[] = $this->vbox->openMedium($hd->location,'HardDisk','ReadWrite',false)->handle;
3703			}
3704
3705			/* @var $progress IProgress */
3706			$progress = $machine->deleteConfig($hds);
3707
3708			$machine->releaseRemote();
3709
3710			// Does an exception exist?
3711			if($progress) {
3712				try {
3713					if($progress->errorInfo->handle) {
3714						$this->errors[] = new Exception($progress->errorInfo->text);
3715						$progress->releaseRemote();
3716						return false;
3717					}
3718				} catch (Exception $null) {}
3719
3720				$this->_util_progressStore($progress);
3721
3722				return array('progress' => $progress->handle);
3723
3724			}
3725
3726
3727		}
3728
3729		return true;
3730
3731
3732	}
3733
3734
3735	/**
3736	 * Create a new Virtual Machine
3737	 *
3738	 * @param array $args array of arguments. See function body for details.
3739	 * @return boolean true on success
3740	 */
3741	public function remote_machineCreate($args) {
3742
3743		// Connect to vboxwebsrv
3744		$this->connect();
3745
3746		$response = array();
3747
3748		// quota enforcement
3749		if ( isset($_SESSION['user']) )
3750		{
3751			if ( @isset($this->settings->vmQuotaPerUser) && @$this->settings->vmQuotaPerUser > 0 && !$_SESSION['admin'] )
3752			{
3753				$newresp = array('data' => array());
3754				$this->vboxGetMachines(array(), array(&$newresp));
3755				if ( count($newresp['data']['responseData']) >= $this->settings->vmQuotaPerUser )
3756				{
3757					// we're over quota!
3758					// delete the disk we just created
3759					if ( isset($args['disk']) )
3760					{
3761						$this->mediumRemove(array(
3762								'medium' => $args['disk'],
3763								'type' => 'HardDisk',
3764								'delete' => true
3765							), $newresp);
3766					}
3767					throw new Exception("Sorry, you're over quota. You can only create up to {$this->settings->vmQuotaPerUser} VMs.");
3768				}
3769			}
3770		}
3771
3772		// create machine
3773		if (@$this->settings->enforceVMOwnership )
3774			$args['name'] = $_SESSION['user'] . '_' . $args['name'];
3775
3776		/* Check if file exists */
3777		$filename = $this->vbox->composeMachineFilename($args['name'],($this->settings->phpVboxGroups ? '' : $args['group']),$this->vbox->systemProperties->defaultMachineFolder,null);
3778
3779		if($this->remote_fileExists(array('file'=>$filename))) {
3780			return array('exists' => $filename);
3781		}
3782
3783
3784		/* @var $m IMachine */
3785		$m = $this->vbox->createMachine(null,$args['name'],($this->settings->phpVboxGroups ? '' : $args['group']),$args['ostype'],null,null);
3786
3787		/* Check for phpVirtualBox groups */
3788		if($this->settings->phpVboxGroups && $args['group']) {
3789			$m->setExtraData(vboxconnector::phpVboxGroupKey, $args['group']);
3790		}
3791
3792		// Set memory
3793		$m->memorySize = intval($args['memory']);
3794
3795
3796		// Save and register
3797		$m->saveSettings();
3798		$this->vbox->registerMachine($m->handle);
3799		$vm = $m->id;
3800		$m->releaseRemote();
3801
3802		try {
3803
3804			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
3805
3806			// Lock VM
3807			/* @var $machine IMachine */
3808			$machine = $this->vbox->findMachine($vm);
3809			$machine->lockMachine($this->session->handle,'Write');
3810
3811			// OS defaults
3812			$defaults = $this->vbox->getGuestOSType($args['ostype']);
3813
3814			// Ownership enforcement
3815			if ( isset($_SESSION['user']) )
3816			{
3817				$this->session->machine->setExtraData('phpvb/sso/owner', $_SESSION['user']);
3818			}
3819
3820			// set the vboxauthsimple in VM config
3821			$this->session->machine->setExtraData('VBoxAuthSimple/users/'.$_SESSION['user'].'', $_SESSION['uHash']);
3822
3823			// Always set
3824			$this->session->machine->setExtraData('GUI/FirstRun', 'yes');
3825
3826			try {
3827				if($this->session->machine->VRDEServer && $this->vbox->systemProperties->defaultVRDEExtPack) {
3828					$this->session->machine->VRDEServer->enabled = 1;
3829					$this->session->machine->VRDEServer->authTimeout = 5000;
3830					$this->session->machine->VRDEServer->setVRDEProperty('TCP/Ports',($this->settings->vrdeports ? $this->settings->vrdeports : '3390-5000'));
3831					$this->session->machine->VRDEServer->setVRDEProperty('TCP/Address',($this->settings->vrdeaddress ? $this->settings->vrdeaddress : '127.0.0.1'));
3832				}
3833			} catch (Exception $e) {
3834				//Ignore
3835			}
3836
3837			// Other defaults
3838			$this->session->machine->BIOSSettings->IOAPICEnabled = $defaults->recommendedIOAPIC;
3839			$this->session->machine->RTCUseUTC = $defaults->recommendedRTCUseUTC;
3840			$this->session->machine->firmwareType = (string)$defaults->recommendedFirmware;
3841			$this->session->machine->chipsetType = (string)$defaults->recommendedChipset;
3842			if(intval($defaults->recommendedVRAM) > 0) $this->session->machine->VRAMSize = intval($defaults->recommendedVRAM);
3843			$this->session->machine->setCpuProperty('PAE',$defaults->recommendedPAE);
3844
3845			// USB input devices
3846			if($defaults->recommendedUSBHid) {
3847				$this->session->machine->pointingHIDType = 'USBMouse';
3848				$this->session->machine->keyboardHIDType = 'USBKeyboard';
3849			}
3850
3851			/* Only if acceleration configuration is available */
3852			if($this->vbox->host->getProcessorFeature('HWVirtEx')) {
3853				$this->session->machine->setHWVirtExProperty('Enabled',$defaults->recommendedVirtEx);
3854			}
3855
3856			/*
3857			 * Hard Disk and DVD/CD Drive
3858			 */
3859			$DVDbusType = (string)$defaults->recommendedDVDStorageBus;
3860			$DVDconType = (string)$defaults->recommendedDVDStorageController;
3861
3862			// Attach harddisk?
3863			if($args['disk']) {
3864
3865				$HDbusType = (string)$defaults->recommendedHDStorageBus;
3866				$HDconType = (string)$defaults->recommendedHDStorageController;
3867
3868				$bus = new StorageBus(null,$HDbusType);
3869				$sc = $this->session->machine->addStorageController(trans($HDbusType,'UIMachineSettingsStorage'),(string)$bus);
3870				$sc->controllerType = $HDconType;
3871				$sc->useHostIOCache = (bool)$this->vbox->systemProperties->getDefaultIoCacheSettingForStorageController($HDconType);
3872
3873				// Set port count?
3874				if($HDbusType == 'SATA') {
3875					$sc->portCount = (($HDbusType == $DVDbusType) ? 2 : 1);
3876				}
3877
3878				$sc->releaseRemote();
3879
3880				$m = $this->vbox->openMedium($args['disk'],'HardDisk','ReadWrite',false);
3881
3882				$this->session->machine->attachDevice(trans($HDbusType,'UIMachineSettingsStorage'),0,0,'HardDisk',$m->handle);
3883
3884				$m->releaseRemote();
3885
3886			}
3887
3888			// Attach DVD/CDROM
3889			if($DVDbusType) {
3890
3891				if(!$args['disk'] || ($HDbusType != $DVDbusType)) {
3892
3893					$bus = new StorageBus(null,$DVDbusType);
3894					$sc = $this->session->machine->addStorageController(trans($DVDbusType,'UIMachineSettingsStorage'),(string)$bus);
3895					$sc->controllerType = $DVDconType;
3896					$sc->useHostIOCache = (bool)$this->vbox->systemProperties->getDefaultIoCacheSettingForStorageController($DVDconType);
3897
3898					// Set port count?
3899					if($DVDbusType == 'SATA') {
3900						$sc->portCount = ($args['disk'] ? 1 : 2);
3901					}
3902
3903					$sc->releaseRemote();
3904				}
3905
3906				$this->session->machine->attachDevice(trans($DVDbusType,'UIMachineSettingsStorage'),1,0,'DVD',null);
3907
3908			}
3909
3910			$this->session->machine->saveSettings();
3911			$this->session->unlockMachine();
3912			$this->session = null;
3913
3914			$machine->releaseRemote();
3915
3916		} catch (Exception $e) {
3917			$this->errors[] = $e;
3918			return false;
3919		}
3920
3921		return true;
3922
3923	}
3924
3925
3926	/**
3927	 * Return a list of network adapters attached to machine $m
3928	 *
3929	 * @param IMachine $m virtual machine instance
3930	 * @param int $slot optional slot of single network adapter to get
3931	 * @return array of network adapter information
3932	 */
3933	private function _machineGetNetworkAdapters(&$m, $slot=false) {
3934
3935		$adapters = array();
3936
3937		for($i = ($slot === false ? 0 : $slot); $i < ($slot === false ? $this->settings->nicMax : ($slot+1)); $i++) {
3938
3939			/* @var $n INetworkAdapter */
3940			$n = $m->getNetworkAdapter($i);
3941
3942			// Avoid duplicate calls
3943			$at = (string)$n->attachmentType;
3944			if($at == 'NAT') $nd = $n->NATEngine; /* @var $nd INATEngine */
3945			else $nd = null;
3946
3947			$props = $n->getProperties(null);
3948			$props = implode("\n",array_map(function($a,$b){return "$a=$b";},$props[1],$props[0]));
3949
3950			$adapters[] = array(
3951				'adapterType' => (string)$n->adapterType,
3952				'slot' => $n->slot,
3953				'enabled' => $n->enabled,
3954				'MACAddress' => $n->MACAddress,
3955				'attachmentType' => $at,
3956				'genericDriver' => $n->genericDriver,
3957				'hostOnlyInterface' => $n->hostOnlyInterface,
3958				'bridgedInterface' => $n->bridgedInterface,
3959				'properties' => $props,
3960				'internalNetwork' => $n->internalNetwork,
3961				'NATNetwork' => $n->NATNetwork,
3962				'promiscModePolicy' => (string)$n->promiscModePolicy,
3963				'VDENetwork' => ($this->settings->enableVDE ? $n->VDENetwork : ''),
3964				'cableConnected' => $n->cableConnected,
3965				'NATEngine' => ($at == 'NAT' ?
3966					array('aliasMode' => intval($nd->aliasMode),'DNSPassDomain' => $nd->DNSPassDomain, 'DNSProxy' => $nd->DNSProxy, 'DNSUseHostResolver' => $nd->DNSUseHostResolver, 'hostIP' => $nd->hostIP)
3967					: array('aliasMode' => 0,'DNSPassDomain' => 0, 'DNSProxy' => 0, 'DNSUseHostResolver' => 0, 'hostIP' => '')),
3968				'lineSpeed' => $n->lineSpeed,
3969				'redirects' => (
3970					$at == 'NAT' ?
3971					$nd->getRedirects()
3972					: array()
3973				)
3974			);
3975
3976			$n->releaseRemote();
3977		}
3978
3979		return $adapters;
3980
3981	}
3982
3983
3984	/**
3985	 * Return a list of virtual machines along with their states and other basic info
3986	 *
3987	 * @param array $args array of arguments. See function body for details.
3988	 * @return array list of machines
3989	 */
3990	public function remote_vboxGetMachines($args) {
3991
3992		// Connect to vboxwebsrv
3993		$this->connect();
3994
3995		$vmlist = array();
3996
3997		// Look for a request for a single vm
3998		if($args['vm']) {
3999
4000			$machines = array($this->vbox->findMachine($args['vm']));
4001
4002		// Full list
4003		} else {
4004			//Get a list of registered machines
4005			$machines = $this->vbox->machines;
4006
4007		}
4008
4009
4010
4011		foreach ($machines as $machine) { /* @var $machine IMachine */
4012
4013
4014			try {
4015
4016				if(!$machine->accessible) {
4017
4018					$vmlist[] = array(
4019						'name' => $machine->id,
4020						'state' => 'Inaccessible',
4021						'OSTypeId' => 'Other',
4022						'id' => $machine->id,
4023						'sessionState' => 'Inaccessible',
4024						'accessible' => 0,
4025						'accessError' => array(
4026							'resultCode' => $this->_util_resultCodeText($machine->accessError->resultCode),
4027							'component' => $machine->accessError->component,
4028							'text' => $machine->accessError->text),
4029						'lastStateChange' => 0,
4030						'groups' => array(),
4031						'currentSnapshot' => ''
4032
4033					);
4034
4035					continue;
4036				}
4037
4038				if($this->settings->phpVboxGroups) {
4039					$groups = explode(',',$machine->getExtraData(vboxconnector::phpVboxGroupKey));
4040					if(!is_array($groups) || (count($groups) == 1 && !$groups[0])) $groups = array("/");
4041				} else {
4042					$groups = $machine->groups;
4043				}
4044
4045				usort($groups, 'strnatcasecmp');
4046
4047				$vmlist[] = array(
4048					'name' => @$this->settings->enforceVMOwnership ? preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $machine->name) : $machine->name,
4049					'state' => (string)$machine->state,
4050					'OSTypeId' => $machine->getOSTypeId(),
4051					'owner' => (@$this->settings->enforceVMOwnership ? $machine->getExtraData("phpvb/sso/owner") : ''),
4052					'groups' => $groups,
4053					'lastStateChange' => (string)($machine->lastStateChange/1000),
4054					'id' => $machine->id,
4055					'currentStateModified' => $machine->currentStateModified,
4056					'sessionState' => (string)$machine->sessionState,
4057					'currentSnapshotName' => ($machine->currentSnapshot->handle ? $machine->currentSnapshot->name : ''),
4058					'customIcon' => (@$this->settings->enableCustomIcons ? $machine->getExtraData('phpvb/icon') : '')
4059				);
4060				if($machine->currentSnapshot->handle) $machine->currentSnapshot->releaseRemote();
4061
4062
4063			} catch (Exception $e) {
4064
4065				if($machine) {
4066
4067					$vmlist[] = array(
4068						'name' => $machine->id,
4069						'state' => 'Inaccessible',
4070						'OSTypeId' => 'Other',
4071						'id' => $machine->id,
4072						'sessionState' => 'Inaccessible',
4073						'lastStateChange' => 0,
4074						'groups' => array(),
4075						'currentSnapshot' => ''
4076					);
4077
4078				} else {
4079					$this->errors[] = $e;
4080				}
4081			}
4082
4083			try {
4084				$machine->releaseRemote();
4085			} catch (Exception $e) { }
4086		}
4087
4088		return $vmlist;
4089
4090	}
4091
4092	/**
4093	 * Creates a new exception so that input can be debugged.
4094	 *
4095	 * @param array $args array of arguments. See function body for details.
4096	 * @return boolean true on success
4097	 */
4098	public function debugInput($args) {
4099		$this->errors[] = new Exception('debug');
4100		return true;
4101	}
4102
4103	/**
4104	 * Get a list of media registered with VirtualBox
4105	 *
4106	 * @param unused $args
4107	 * @param array $response response data passed byref populated by the function
4108	 * @return array of media
4109	 */
4110	public function remote_vboxGetMedia($args) {
4111
4112		// Connect to vboxwebsrv
4113		$this->connect();
4114
4115		$response = array();
4116		$mds = array($this->vbox->hardDisks,$this->vbox->DVDImages,$this->vbox->floppyImages);
4117		for($i=0;$i<3;$i++) {
4118			foreach($mds[$i] as $m) {
4119				/* @var $m IMedium */
4120				$response[] = $this->_mediumGetDetails($m);
4121				$m->releaseRemote();
4122			}
4123		}
4124		return $response;
4125	}
4126
4127	/**
4128	 * Get USB controller information
4129	 *
4130	 * @param IMachine $m virtual machine instance
4131	 * @return array USB controller info
4132	 */
4133	private function _machineGetUSBControllers(&$m) {
4134
4135		/* @var $u IUSBController */
4136		$controllers = &$m->USBControllers;
4137
4138		$rcons = array();
4139		foreach($controllers as $c) {
4140			$rcons[] = array(
4141				'name' => $c->name,
4142				'type' => (string)$c->type
4143			);
4144			$c->releaseRemote();
4145		}
4146
4147		return $rcons;
4148	}
4149
4150	/**
4151	 * Get USB device filters
4152	 *
4153	 * @param IMachine $m virtual machine instance
4154	 * @return array USB device filters
4155	 */
4156	private function _machineGetUSBDeviceFilters(&$m) {
4157
4158		$deviceFilters = array();
4159		foreach($m->USBDeviceFilters->deviceFilters as $df) { /* @var $df IUSBDeviceFilter */
4160
4161			$deviceFilters[] = array(
4162				'name' => $df->name,
4163				'active' => $df->active,
4164				'vendorId' => $df->vendorId,
4165				'productId' => $df->productId,
4166				'revision' => $df->revision,
4167				'manufacturer' => $df->manufacturer,
4168				'product' => $df->product,
4169				'serialNumber' => $df->serialNumber,
4170				'port' => $df->port,
4171				'remote' => $df->remote
4172				);
4173			$df->releaseRemote();
4174		}
4175		return $deviceFilters;
4176	}
4177
4178	/**
4179	 * Return top-level virtual machine or snapshot information
4180	 *
4181	 * @param IMachine $m virtual machine instance
4182	 * @return array vm or snapshot data
4183	 */
4184	private function _machineGetDetails(&$m) {
4185
4186		if($this->settings->phpVboxGroups) {
4187			$groups = explode(',',$m->getExtraData(vboxconnector::phpVboxGroupKey));
4188			if(!is_array($groups) || (count($groups) == 1 && !$groups[0])) $groups = array("/");
4189		} else {
4190			$groups = $m->groups;
4191		}
4192
4193		usort($groups, 'strnatcasecmp');
4194
4195		return array(
4196			'name' => @$this->settings->enforceVMOwnership ? preg_replace('/^' . preg_quote($_SESSION['user']) . '_/', '', $m->name) : $m->name,
4197			'description' => $m->description,
4198			'groups' => $groups,
4199			'id' => $m->id,
4200			'autostopType' => ($this->settings->vboxAutostartConfig ? (string)$m->autostopType : ''),
4201			'autostartEnabled' => ($this->settings->vboxAutostartConfig && $m->autostartEnabled),
4202			'autostartDelay' => ($this->settings->vboxAutostartConfig ? intval($m->autostartDelay) : '0'),
4203			'settingsFilePath' => $m->settingsFilePath,
4204		    'paravirtProvider' => (string)$m->paravirtProvider,
4205			'OSTypeId' => $m->OSTypeId,
4206			'OSTypeDesc' => $this->vbox->getGuestOSType($m->OSTypeId)->description,
4207			'CPUCount' => $m->CPUCount,
4208			'HPETEnabled' => $m->HPETEnabled,
4209			'memorySize' => $m->memorySize,
4210			'VRAMSize' => $m->VRAMSize,
4211			'pointingHIDType' => (string)$m->pointingHIDType,
4212			'keyboardHIDType' => (string)$m->keyboardHIDType,
4213			'accelerate3DEnabled' => $m->accelerate3DEnabled,
4214			'accelerate2DVideoEnabled' => $m->accelerate2DVideoEnabled,
4215			'BIOSSettings' => array(
4216				'ACPIEnabled' => $m->BIOSSettings->ACPIEnabled,
4217				'IOAPICEnabled' => $m->BIOSSettings->IOAPICEnabled,
4218				'timeOffset' => $m->BIOSSettings->timeOffset
4219				),
4220			'firmwareType' => (string)$m->firmwareType,
4221			'snapshotFolder' => $m->snapshotFolder,
4222			'monitorCount' => $m->monitorCount,
4223			'pageFusionEnabled' => $m->pageFusionEnabled,
4224			'VRDEServer' => (!$m->VRDEServer ? null : array(
4225				'enabled' => $m->VRDEServer->enabled,
4226				'ports' => $m->VRDEServer->getVRDEProperty('TCP/Ports'),
4227				'netAddress' => $m->VRDEServer->getVRDEProperty('TCP/Address'),
4228				'VNCPassword' => $m->VRDEServer->getVRDEProperty('VNCPassword'),
4229				'authType' => (string)$m->VRDEServer->authType,
4230				'authTimeout' => $m->VRDEServer->authTimeout,
4231				'allowMultiConnection' => $m->VRDEServer->allowMultiConnection,
4232				'VRDEExtPack' => (string)$m->VRDEServer->VRDEExtPack
4233				)),
4234			'audioAdapter' => array(
4235				'enabled' => $m->audioAdapter->enabled,
4236				'audioController' => (string)$m->audioAdapter->audioController,
4237				'audioDriver' => (string)$m->audioAdapter->audioDriver,
4238				),
4239			'RTCUseUTC' => $m->RTCUseUTC,
4240		    'EffectiveParavirtProvider' => (string)$m->getEffectiveParavirtProvider(),
4241			'HWVirtExProperties' => array(
4242				'Enabled' => $m->getHWVirtExProperty('Enabled'),
4243				'NestedPaging' => $m->getHWVirtExProperty('NestedPaging'),
4244				'LargePages' => $m->getHWVirtExProperty('LargePages'),
4245				'UnrestrictedExecution' => $m->getHWVirtExProperty('UnrestrictedExecution'),
4246				'VPID' => $m->getHWVirtExProperty('VPID')
4247				),
4248			'CpuProperties' => array(
4249				'PAE' => $m->getCpuProperty('PAE')
4250				),
4251			'bootOrder' => $this->_machineGetBootOrder($m),
4252			'chipsetType' => (string)$m->chipsetType,
4253			'GUI' => array(
4254				'FirstRun' => $m->getExtraData('GUI/FirstRun'),
4255			),
4256			'customIcon' => (@$this->settings->enableCustomIcons ? $m->getExtraData('phpvb/icon') : ''),
4257			'disableHostTimeSync' => intval($m->getExtraData("VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled")),
4258			'CPUExecutionCap' => $m->CPUExecutionCap
4259		);
4260
4261	}
4262
4263	/**
4264	 * Get virtual machine boot order
4265	 *
4266	 * @param IMachine $m virtual machine instance
4267	 * @return array boot order
4268	 */
4269	private function _machineGetBootOrder(&$m) {
4270		$return = array();
4271		$mbp = $this->vbox->systemProperties->maxBootPosition;
4272		for($i = 0; $i < $mbp; $i ++) {
4273			if(($b = (string)$m->getBootOrder($i + 1)) == 'Null') continue;
4274			$return[] = $b;
4275		}
4276		return $return;
4277	}
4278
4279	/**
4280	 * Get serial port configuration for a virtual machine or snapshot
4281	 *
4282	 * @param IMachine $m virtual machine instance
4283	 * @return array serial port info
4284	 */
4285	private function _machineGetSerialPorts(&$m) {
4286		$ports = array();
4287		$max = $this->vbox->systemProperties->serialPortCount;
4288		for($i = 0; $i < $max; $i++) {
4289			try {
4290				/* @var $p ISerialPort */
4291				$p = $m->getSerialPort($i);
4292				$ports[] = array(
4293					'slot' => $p->slot,
4294					'enabled' => $p->enabled,
4295					'IOBase' => '0x'.strtoupper(sprintf('%3s',dechex($p->IOBase))),
4296					'IRQ' => $p->IRQ,
4297					'hostMode' => (string)$p->hostMode,
4298					'server' => $p->server,
4299					'path' => $p->path
4300				);
4301				$p->releaseRemote();
4302			} catch (Exception $e) {
4303				// Ignore
4304			}
4305		}
4306		return $ports;
4307	}
4308
4309	/**
4310	 * Get parallel port configuration for a virtual machine or snapshot
4311	 *
4312	 * @param IMachine $m virtual machine instance
4313	 * @return array parallel port info
4314	 */
4315	private function _machineGetParallelPorts(&$m) {
4316		if(!@$this->settings->enableLPTConfig) return array();
4317		$ports = array();
4318		$max = $this->vbox->systemProperties->parallelPortCount;
4319		for($i = 0; $i < $max; $i++) {
4320			try {
4321				/* @var $p IParallelPort */
4322				$p = $m->getParallelPort($i);
4323				$ports[] = array(
4324					'slot' => $p->slot,
4325					'enabled' => $p->enabled,
4326					'IOBase' => '0x'.strtoupper(sprintf('%3s',dechex($p->IOBase))),
4327					'IRQ' => $p->IRQ,
4328					'path' => $p->path
4329				);
4330				$p->releaseRemote();
4331			} catch (Exception $e) {
4332				// Ignore
4333			}
4334		}
4335		return $ports;
4336	}
4337
4338	/**
4339	 * Get shared folder configuration for a virtual machine or snapshot
4340	 *
4341	 * @param IMachine $m virtual machine instance
4342	 * @return array shared folder info
4343	 */
4344	private function _machineGetSharedFolders(&$m) {
4345		$sfs = &$m->sharedFolders;
4346		$return = array();
4347		foreach($sfs as $sf) { /* @var $sf ISharedFolder */
4348			$return[] = array(
4349				'name' => $sf->name,
4350				'hostPath' => $sf->hostPath,
4351				'accessible' => $sf->accessible,
4352				'writable' => $sf->writable,
4353				'autoMount' => $sf->autoMount,
4354				'lastAccessError' => $sf->lastAccessError,
4355				'type' => 'machine'
4356			);
4357		}
4358		return $return;
4359	}
4360
4361	/**
4362	 * Add encryption password to VM console
4363	 *
4364	 * @param array $args array of arguments. See function body for details.
4365	 * @return true on success
4366	 */
4367	public function remote_consoleAddDiskEncryptionPasswords($args) {
4368
4369	    $this->connect();
4370
4371	    /* @var $machine IMachine */
4372	    $machine = $this->vbox->findMachine($args['vm']);
4373
4374	    $this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
4375	    $machine->lockMachine($this->session->handle,'Shared');
4376
4377	    $response = array('accepted'=>array(),'failed'=>array(),'errors'=>array());
4378
4379	    foreach($args['passwords'] as $creds) {
4380	        try {
4381	            $this->session->console->removeDiskEncryptionPassword($creds['id']);
4382	        } catch(Exception $e) {
4383	            // It may not exist yet
4384	        }
4385
4386    	    try {
4387    	        $this->session->console->addDiskEncryptionPassword($creds['id'], $creds['password'], (bool)@$args['clearOnSuspend']);
4388    	        $response['accepted'][] = $creds['id'];
4389    		} catch (Exception $e) {
4390    		    $response['failed'][] = $creds['id'];
4391    		    $response['errors'][] = $e->getMessage();
4392    		}
4393	    }
4394
4395		$this->session->unlockMachine();
4396		unset($this->session);
4397		$machine->releaseRemote();
4398
4399		return $response;
4400	}
4401
4402	/**
4403	 * Get a list of transient (temporary) shared folders
4404	 *
4405	 * @param array $args array of arguments. See function body for details.
4406	 * @return array of shared folders
4407	 */
4408	public function remote_consoleGetSharedFolders($args) {
4409
4410		$this->connect();
4411
4412		/* @var $machine IMachine */
4413		$machine = $this->vbox->findMachine($args['vm']);
4414
4415		// No need to continue if machine is not running
4416		if((string)$machine->state != 'Running') {
4417			$machine->releaseRemote();
4418			return true;
4419		}
4420
4421		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
4422		$machine->lockMachine($this->session->handle,'Shared');
4423
4424		$sfs = $this->session->console->sharedFolders;
4425
4426		$response = array();
4427
4428		foreach($sfs as $sf) { /* @var $sf ISharedFolder */
4429
4430			$response[] = array(
4431				'name' => $sf->name,
4432				'hostPath' => $sf->hostPath,
4433				'accessible' => $sf->accessible,
4434				'writable' => $sf->writable,
4435				'autoMount' => $sf->autoMount,
4436				'lastAccessError' => $sf->lastAccessError,
4437				'type' => 'transient'
4438			);
4439		}
4440
4441		$this->session->unlockMachine();
4442		unset($this->session);
4443		$machine->releaseRemote();
4444
4445		return $response;
4446	}
4447
4448	/**
4449	 * Get VirtualBox Host OS specific directory separator
4450	 *
4451	 * @return string directory separator string
4452	 */
4453	public function getDsep() {
4454
4455		if(!$this->dsep) {
4456
4457			/* No need to go through vbox if local browser is true */
4458			if($this->settings->browserLocal) {
4459
4460				$this->dsep = DIRECTORY_SEPARATOR;
4461
4462			} else {
4463
4464				$this->connect();
4465
4466			    if(stripos($this->vbox->host->operatingSystem,'windows') !== false) {
4467					$this->dsep = '\\';
4468			    } else {
4469					$this->dsep = '/';
4470			    }
4471			}
4472
4473
4474		}
4475
4476		return $this->dsep;
4477	}
4478
4479	/**
4480	 * Get medium attachment information for all medium attachments in $mas
4481	 *
4482	 * @param IMediumAttachment[] $mas list of IMediumAttachment instances
4483	 * @return array medium attachment info
4484	 */
4485	private function _machineGetMediumAttachments(&$mas) {
4486
4487		$return = array();
4488
4489		foreach($mas as $ma) { /** @var $ma IMediumAttachment */
4490			$return[] = array(
4491				'medium' => ($ma->medium->handle ? array('id'=>$ma->medium->id) : null),
4492				'controller' => $ma->controller,
4493				'port' => $ma->port,
4494				'device' => $ma->device,
4495				'type' => (string)$ma->type,
4496				'passthrough' => $ma->passthrough,
4497				'temporaryEject' => $ma->temporaryEject,
4498				'nonRotational' => $ma->nonRotational,
4499				'hotPluggable' => $ma->hotPluggable,
4500			);
4501		}
4502
4503		// sort by port then device
4504		usort($return,function($a,$b){if($a["port"] == $b["port"]) { if($a["device"] < $b["device"]) { return -1; } if($a["device"] > $b["device"]) { return 1; } return 0; } if($a["port"] < $b["port"]) { return -1; } return 1;});
4505
4506		return $return;
4507	}
4508
4509	/**
4510	 * Save snapshot details ( description or name)
4511	 *
4512	 * @param array $args array of arguments. See function body for details.
4513	 * @return boolean true on success
4514	 */
4515	public function remote_snapshotSave($args) {
4516
4517		// Connect to vboxwebsrv
4518		$this->connect();
4519
4520		/* @var $vm IMachine */
4521		$vm = $this->vbox->findMachine($args['vm']);
4522
4523		/* @var $snapshot ISnapshot */
4524		$snapshot = $vm->findSnapshot($args['snapshot']);
4525		$snapshot->name = $args['name'];
4526		$snapshot->description = $args['description'];
4527
4528		// cleanup
4529		$snapshot->releaseRemote();
4530		$vm->releaseRemote();
4531
4532		return true;
4533	}
4534
4535	/**
4536	 * Get snapshot details
4537	 *
4538	 * @param array $args array of arguments. See function body for details.
4539	 * @return array containing snapshot details
4540	 */
4541	public function remote_snapshotGetDetails($args) {
4542
4543		// Connect to vboxwebsrv
4544		$this->connect();
4545
4546		/* @var $vm IMachine */
4547		$vm = $this->vbox->findMachine($args['vm']);
4548
4549		/* @var $snapshot ISnapshot */
4550		$snapshot = $vm->findSnapshot($args['snapshot']);
4551
4552		$response = $this->_snapshotGetDetails($snapshot,false);
4553		$response['machine'] = $this->remote_machineGetDetails(array(),$snapshot->machine);
4554
4555		// cleanup
4556		$snapshot->releaseRemote();
4557		$vm->releaseRemote();
4558
4559		return $response;
4560
4561	}
4562
4563	/**
4564	 * Restore a snapshot
4565	 *
4566	 * @param array $args array of arguments. See function body for details.
4567	 * @return array response data containing progress operation id
4568	 */
4569	public function remote_snapshotRestore($args) {
4570
4571		// Connect to vboxwebsrv
4572		$this->connect();
4573
4574		$progress = $this->session = null;
4575
4576		try {
4577
4578			// Open session to machine
4579			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
4580
4581			/* @var $machine IMachine */
4582			$machine = $this->vbox->findMachine($args['vm']);
4583			$machine->lockMachine($this->session->handle, 'Write');
4584
4585			/* @var $snapshot ISnapshot */
4586			$snapshot = $this->session->machine->findSnapshot($args['snapshot']);
4587
4588			/* @var $progress IProgress */
4589			$progress = $this->session->machine->restoreSnapshot($snapshot->handle);
4590
4591			$snapshot->releaseRemote();
4592			$machine->releaseRemote();
4593
4594			// Does an exception exist?
4595			try {
4596				if($progress->errorInfo->handle) {
4597					$this->errors[] = new Exception($progress->errorInfo->text);
4598					$progress->releaseRemote();
4599					return false;
4600				}
4601			} catch (Exception $null) {}
4602
4603			$this->_util_progressStore($progress);
4604
4605		} catch (Exception $e) {
4606
4607			$this->errors[] = $e;
4608
4609			if($this->session->handle) {
4610				try{$this->session->unlockMachine();}catch(Exception $e){}
4611			}
4612			return false;
4613		}
4614
4615		return array('progress' => $progress->handle);
4616
4617	}
4618
4619	/**
4620	 * Delete a snapshot
4621	 *
4622	 * @param array $args array of arguments. See function body for details.
4623	 * @return array response data containing progress operation id
4624	 */
4625	public function remote_snapshotDelete($args) {
4626
4627		// Connect to vboxwebsrv
4628		$this->connect();
4629
4630		$progress = $this->session = null;
4631
4632		try {
4633
4634			// Open session to machine
4635			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
4636
4637			/* @var $machine IMachine */
4638			$machine = $this->vbox->findMachine($args['vm']);
4639			$machine->lockMachine($this->session->handle, 'Shared');
4640
4641			/* @var $progress IProgress */
4642			$progress = $this->session->machine->deleteSnapshot($args['snapshot']);
4643
4644			$machine->releaseRemote();
4645
4646			// Does an exception exist?
4647			try {
4648				if($progress->errorInfo->handle) {
4649					$this->errors[] = new Exception($progress->errorInfo->text);
4650					$progress->releaseRemote();
4651					return false;
4652				}
4653			} catch (Exception $null) {}
4654
4655			$this->_util_progressStore($progress);
4656
4657
4658		} catch (Exception $e) {
4659
4660			$this->errors[] = $e;
4661
4662			if($this->session->handle) {
4663				try{$this->session->unlockMachine();$this->session=null;}catch(Exception $e){}
4664			}
4665
4666			return false;
4667		}
4668
4669		return array('progress' => $progress->handle);
4670
4671	}
4672
4673	/**
4674	 * Take a snapshot
4675	 *
4676	 * @param array $args array of arguments. See function body for details.
4677	 * @return array response data containing progress operation id
4678	 */
4679	public function remote_snapshotTake($args) {
4680
4681		// Connect to vboxwebsrv
4682		$this->connect();
4683
4684		/* @var $machine IMachine */
4685		$machine = $this->vbox->findMachine($args['vm']);
4686
4687		$progress = $this->session = null;
4688
4689		try {
4690
4691			// Open session to machine
4692			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
4693			$machine->lockMachine($this->session->handle, ((string)$machine->sessionState == 'Unlocked' ? 'Write' : 'Shared'));
4694
4695			/* @var $progress IProgress */
4696			list($progress, $snapshotId) = $this->session->machine->takeSnapshot($args['name'], $args['description'], null);
4697
4698			// Does an exception exist?
4699			try {
4700				if($progress->errorInfo->handle) {
4701					$this->errors[] = new Exception($progress->errorInfo->text);
4702					$progress->releaseRemote();
4703					try{$this->session->unlockMachine(); $this->session=null;}catch(Exception $ed){}
4704					return false;
4705				}
4706			} catch (Exception $null) {}
4707
4708
4709			$this->_util_progressStore($progress);
4710
4711		} catch (Exception $e) {
4712
4713			if(!$progress->handle && $this->session->handle) {
4714				try{$this->session->unlockMachine();$this->session=null;}catch(Exception $e){}
4715			}
4716
4717			return false;
4718		}
4719
4720		return array('progress' => $progress->handle);
4721
4722	}
4723
4724	/**
4725	 * Get a list of snapshots for a machine
4726	 *
4727	 * @param array $args array of arguments. See function body for details.
4728	 * @return array list of snapshots
4729	 */
4730	public function remote_machineGetSnapshots($args) {
4731
4732		// Connect to vboxwebsrv
4733		$this->connect();
4734
4735		/* @var $machine IMachine */
4736		$machine = $this->vbox->findMachine($args['vm']);
4737
4738		$response = array('vm' => $args['vm'],
4739			'snapshot' => array(),
4740			'currentSnapshotId' => null);
4741
4742		/* No snapshots? Empty array */
4743		if($machine->snapshotCount < 1) {
4744			return $response;
4745		} else {
4746
4747			/* @var $s ISnapshot */
4748			$s = $machine->findSnapshot(null);
4749			$response['snapshot'] = $this->_snapshotGetDetails($s,true);
4750			$s->releaseRemote();
4751		}
4752
4753		$response['currentSnapshotId'] = ($machine->currentSnapshot->handle ? $machine->currentSnapshot->id : '');
4754		if($machine->currentSnapshot->handle) $machine->currentSnapshot->releaseRemote();
4755		$machine->releaseRemote();
4756
4757		return $response;
4758	}
4759
4760
4761	/**
4762	 * Return details about snapshot $s
4763	 *
4764	 * @param ISnapshot $s snapshot instance
4765	 * @param boolean $sninfo traverse child snapshots
4766	 * @return array snapshot info
4767	 */
4768	private function _snapshotGetDetails(&$s,$sninfo=false) {
4769
4770		$children = array();
4771
4772		if($sninfo)
4773			foreach($s->children as $c) { /* @var $c ISnapshot */
4774				$children[] = $this->_snapshotGetDetails($c, true);
4775				$c->releaseRemote();
4776			}
4777
4778		// Avoid multiple soap calls
4779		$timestamp = (string)$s->timeStamp;
4780
4781		return array(
4782			'id' => $s->id,
4783			'name' => $s->name,
4784			'description' => $s->description,
4785			'timeStamp' => floor($timestamp/1000),
4786			'timeStampSplit' => $this->_util_splitTime(time() - floor($timestamp/1000)),
4787			'online' => $s->online
4788		) + (
4789			($sninfo ? array('children' => $children) : array())
4790		);
4791	}
4792
4793	/**
4794	 * Return details about storage controllers for machine $m
4795	 *
4796	 * @param IMachine $m virtual machine instance
4797	 * @return array storage controllers' details
4798	 */
4799	private function _machineGetStorageControllers(&$m) {
4800
4801		$sc = array();
4802		$scs = $m->storageControllers;
4803
4804		foreach($scs as $c) { /* @var $c IStorageController */
4805			$sc[] = array(
4806				'name' => $c->name,
4807				'maxDevicesPerPortCount' => $c->maxDevicesPerPortCount,
4808				'useHostIOCache' => $c->useHostIOCache,
4809				'minPortCount' => $c->minPortCount,
4810				'maxPortCount' => $c->maxPortCount,
4811				'portCount' => $c->portCount,
4812				'bus' => (string)$c->bus,
4813				'controllerType' => (string)$c->controllerType,
4814				'mediumAttachments' => $this->_machineGetMediumAttachments($m->getMediumAttachmentsOfController($c->name), $m->id)
4815			);
4816			$c->releaseRemote();
4817		}
4818
4819		for($i = 0; $i < count($sc); $i++) {
4820
4821			for($a = 0; $a < count($sc[$i]['mediumAttachments']); $a++) {
4822
4823				// Value of '' means it is not applicable
4824				$sc[$i]['mediumAttachments'][$a]['ignoreFlush'] = '';
4825
4826				// Only valid for HardDisks
4827				if($sc[$i]['mediumAttachments'][$a]['type'] != 'HardDisk') continue;
4828
4829				// Get appropriate key
4830				$xtra = $this->_util_getIgnoreFlushKey($sc[$i]['mediumAttachments'][$a]['port'], $sc[$i]['mediumAttachments'][$a]['device'], $sc[$i]['controllerType']);
4831
4832				// No such setting for this bus type
4833				if(!$xtra) continue;
4834
4835				$sc[$i]['mediumAttachments'][$a]['ignoreFlush'] = $m->getExtraData($xtra);
4836
4837				if(trim($sc[$i]['mediumAttachments'][$a]['ignoreFlush']) === '')
4838					$sc[$i]['mediumAttachments'][$a]['ignoreFlush'] = 1;
4839				else
4840					$sc[$i]['mediumAttachments'][$a]['ignoreFlush'] = $sc[$i]['mediumAttachments'][$a]['ignoreFlush'];
4841
4842			}
4843		}
4844
4845		return $sc;
4846	}
4847
4848	/**
4849	 * Check medium encryption password
4850	 *
4851	 * @param array $args array of arguments. See function body for details.
4852	 * @return array response data
4853	 */
4854	public function remote_mediumCheckEncryptionPassword($args) {
4855
4856	    // Connect to vboxwebsrv
4857	    $this->connect();
4858
4859	    $m = $this->vbox->openMedium($args['medium'],'HardDisk','ReadWrite',false);
4860
4861	    $retval = $m->checkEncryptionPassword($args['password']);
4862
4863	    $m->releaseRemote();
4864
4865	    return $retval;
4866
4867	}
4868
4869	/**
4870	 * Change medium encryption
4871	 *
4872	 * @param array $args array of arguments. See function body for details.
4873	 * @return array response data containing progress id or true
4874	 */
4875	public function remote_mediumChangeEncryption($args) {
4876
4877	    // Connect to vboxwebsrv
4878	    $this->connect();
4879
4880	    $m = $this->vbox->openMedium($args['medium'], 'HardDisk', 'ReadWrite', false);
4881
4882	    /* @var $progress IProgress */
4883	    $progress = $m->changeEncryption($args['old_password'],
4884	            $args['cipher'], $args['password'], $args['id']);
4885
4886	    // Does an exception exist?
4887	    try {
4888	        if($progress->errorInfo->handle) {
4889	            $this->errors[] = new Exception($progress->errorInfo->text);
4890	            $progress->releaseRemote();
4891	            $m->releaseRemote();
4892	            return false;
4893	        }
4894	    } catch (Exception $null) {
4895	    }
4896
4897	    if($args['waitForCompletion']) {
4898	        $progress->waitForCompletion(-1);
4899	        $progress->releaseRemote();
4900	        $m->releaseRemote();
4901	        return true;
4902	    }
4903
4904	    $this->_util_progressStore($progress);
4905
4906	    return array('progress' => $progress->handle);
4907
4908	}
4909
4910	/**
4911	 * Resize a medium. Currently unimplemented in GUI.
4912	 *
4913	 * @param array $args array of arguments. See function body for details.
4914	 * @return array response data containing progress id
4915	 */
4916	public function remote_mediumResize($args) {
4917
4918		// Connect to vboxwebsrv
4919		$this->connect();
4920
4921		$m = $this->vbox->openMedium($args['medium'], 'HardDisk', 'ReadWrite', false);
4922
4923		/* @var $progress IProgress */
4924		$progress = $m->resize($args['bytes']);
4925
4926		// Does an exception exist?
4927		try {
4928			if($progress->errorInfo->handle) {
4929				$this->errors[] = new Exception($progress->errorInfo->text);
4930				$progress->releaseRemote();
4931				return false;
4932			}
4933		} catch (Exception $null) {
4934		}
4935
4936		$this->_util_progressStore($progress);
4937
4938		return array('progress' => $progress->handle);
4939
4940	}
4941
4942	/**
4943	 * Clone a medium
4944	 *
4945	 * @param array $args array of arguments. See function body for details.
4946	 * @return array response data containing progress id
4947	 */
4948	public function remote_mediumCloneTo($args) {
4949
4950		// Connect to vboxwebsrv
4951		$this->connect();
4952
4953		$format = strtoupper($args['format']);
4954		/* @var $target IMedium */
4955		$target = $this->vbox->createMedium($format, $args['location'], 'ReadWrite', 'HardDisk');
4956		$mid = $target->id;
4957
4958		/* @var $src IMedium */
4959		$src = $this->vbox->openMedium($args['src'], 'HardDisk', 'ReadWrite', false);
4960
4961		$type = array(($args['type'] == 'fixed' ? 'Fixed' : 'Standard'));
4962		if($args['split']) $type[] = 'VmdkSplit2G';
4963
4964		/* @var $progress IProgress */
4965		$progress = $src->cloneTo($target->handle,$type,null);
4966
4967		$src->releaseRemote();
4968		$target->releaseRemote();
4969
4970		// Does an exception exist?
4971		try {
4972			if($progress->errorInfo->handle) {
4973				$this->errors[] = new Exception($progress->errorInfo->text);
4974				$progress->releaseRemote();
4975				return false;
4976			}
4977		} catch (Exception $null) {}
4978
4979		$this->_util_progressStore($progress);
4980
4981		return array('progress' => $progress->handle, 'id' => $mid);
4982
4983	}
4984
4985	/**
4986	 * Set medium to a specific type
4987	 *
4988	 * @param array $args array of arguments. See function body for details.
4989	 * @return boolean true on success
4990	 */
4991	public function remote_mediumSetType($args) {
4992
4993		// Connect to vboxwebsrv
4994		$this->connect();
4995
4996		/* @var $m IMedium */
4997		$m = $this->vbox->openMedium($args['medium'], 'HardDisk', 'ReadWrite', false);
4998		$m->type = $args['type'];
4999		$m->releaseRemote();
5000
5001		return true;
5002	}
5003
5004	/**
5005	 * Add iSCSI medium
5006	 *
5007	 * @param array $args array of arguments. See function body for details.
5008	 * @return response data
5009	 */
5010	public function remote_mediumAddISCSI($args) {
5011
5012		// Connect to vboxwebsrv
5013		$this->connect();
5014
5015		// {'server':server,'port':port,'intnet':intnet,'target':target,'lun':lun,'enclun':enclun,'targetUser':user,'targetPass':pass}
5016
5017		// Fix LUN
5018		$args['lun'] = intval($args['lun']);
5019		if($args['enclun']) $args['lun'] = 'enc'.$args['lun'];
5020
5021		// Compose name
5022		$name = $args['server'].'|'.$args['target'];
5023		if($args['lun'] != 0 && $args['lun'] != 'enc0')
5024			$name .= '|'.$args['lun'];
5025
5026		// Create disk
5027		/* @var $hd IMedium */
5028		$hd = $this->vbox->createMedium('iSCSI',$name, 'ReadWrite', 'HardDisk');
5029
5030		if($args['port']) $args['server'] .= ':'.intval($args['port']);
5031
5032		$arrProps = array();
5033
5034		$arrProps["TargetAddress"] = $args['server'];
5035		$arrProps["TargetName"] = $args['target'];
5036		$arrProps["LUN"] = $args['lun'];
5037		if($args['targetUser']) $arrProps["InitiatorUsername"] = $args['targetUser'];
5038		if($args['targetPass']) $arrProps["InitiatorSecret"] = $args['targetPass'];
5039		if($args['intnet']) $arrProps["HostIPStack"] = '0';
5040
5041		$hd->setProperties(array_keys($arrProps),array_values($arrProps));
5042
5043		$hdid = $hd->id;
5044		$hd->releaseRemote();
5045
5046		return array('id' => $hdid);
5047	}
5048
5049	/**
5050	 * Add existing medium by file location
5051	 *
5052	 * @param array $args array of arguments. See function body for details.
5053	 * @return resposne data containing new medium's id
5054	 */
5055	public function remote_mediumAdd($args) {
5056
5057		// Connect to vboxwebsrv
5058		$this->connect();
5059
5060		/* @var $m IMedium */
5061		$m = $this->vbox->openMedium($args['path'], $args['type'], 'ReadWrite', false);
5062
5063		$mid = $m->id;
5064		$m->releaseRemote();
5065
5066		return array('id'=>$mid);
5067	}
5068
5069	/**
5070	 * Get VirtualBox generated machine configuration file name
5071	 *
5072	 * @param array $args array of arguments. See function body for details.
5073	 * @return string filename
5074	 */
5075	public function remote_vboxGetComposedMachineFilename($args) {
5076
5077		// Connect to vboxwebsrv
5078		$this->connect();
5079
5080		return $this->vbox->composeMachineFilename($args['name'],($this->settings->phpVboxGroups ? '' : $args['group']),$this->vbox->systemProperties->defaultMachineFolder,null);
5081
5082	}
5083
5084	/**
5085	 * Create base storage medium (virtual hard disk)
5086	 *
5087	 * @param array $args array of arguments. See function body for details.
5088	 * @return response data containing progress id
5089	 */
5090	public function remote_mediumCreateBaseStorage($args) {
5091
5092		// Connect to vboxwebsrv
5093		$this->connect();
5094
5095		$format = strtoupper($args['format']);
5096
5097		$type = array(($args['type'] == 'fixed' ? 'Fixed' : 'Standard'));
5098		if($args['split']) $type[] = 'VmdkSplit2G';
5099
5100		/* @var $hd IMedium */
5101		$hd = $this->vbox->createMedium($format, $args['file'], 'ReadWrite', 'HardDisk');
5102
5103		/* @var $progress IProgress */
5104		$progress = $hd->createBaseStorage(intval($args['size'])*1024*1024,$type);
5105
5106		// Does an exception exist?
5107		try {
5108			if($progress->errorInfo->handle) {
5109				$this->errors[] = new Exception($progress->errorInfo->text);
5110				$progress->releaseRemote();
5111				return false;
5112			}
5113		} catch (Exception $null) {}
5114
5115		$this->_util_progressStore($progress);
5116
5117		$hd->releaseRemote();
5118
5119		return array('progress' => $progress->handle);
5120
5121	}
5122
5123	/**
5124	 * Release medium from all attachments
5125	 *
5126	 * @param array $args array of arguments. See function body for details.
5127	 * @return boolean true
5128	 */
5129	public function remote_mediumRelease($args) {
5130
5131		// Connect to vboxwebsrv
5132		$this->connect();
5133
5134		/* @var $m IMedium */
5135		$m = $this->vbox->openMedium($args['medium'],$args['type'], 'ReadWrite', false);
5136		$mediumid = $m->id;
5137
5138		// connected to...
5139		$machines = $m->machineIds;
5140		$released = array();
5141		foreach($machines as $uuid) {
5142
5143			// Find medium attachment
5144			try {
5145				/* @var $mach IMachine */
5146				$mach = $this->vbox->findMachine($uuid);
5147			} catch (Exception $e) {
5148				$this->errors[] = $e;
5149				continue;
5150			}
5151			$attach = $mach->mediumAttachments;
5152			$remove = array();
5153			foreach($attach as $a) {
5154				if($a->medium->handle && $a->medium->id == $mediumid) {
5155					$remove[] = array(
5156						'controller' => $a->controller,
5157						'port' => $a->port,
5158						'device' => $a->device);
5159					break;
5160				}
5161			}
5162			// save state
5163			$state = (string)$mach->sessionState;
5164
5165			if(!count($remove)) continue;
5166
5167			$released[] = $uuid;
5168
5169			// create session
5170			$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
5171
5172			// Hard disk requires machine to be stopped
5173			if($args['type'] == 'HardDisk' || $state == 'Unlocked') {
5174
5175				$mach->lockMachine($this->session->handle, 'Write');
5176
5177			} else {
5178
5179				$mach->lockMachine($this->session->handle, 'Shared');
5180
5181			}
5182
5183			foreach($remove as $r) {
5184				if($args['type'] == 'HardDisk') {
5185					$this->session->machine->detachDevice($r['controller'],$r['port'],$r['device']);
5186				} else {
5187					$this->session->machine->mountMedium($r['controller'],$r['port'],$r['device'],null,true);
5188				}
5189			}
5190
5191			$this->session->machine->saveSettings();
5192			$this->session->machine->releaseRemote();
5193			$this->session->unlockMachine();
5194			unset($this->session);
5195			$mach->releaseRemote();
5196
5197		}
5198		$m->releaseRemote();
5199
5200		return true;
5201	}
5202
5203	/**
5204	 * Remove a medium
5205	 *
5206	 * @param array $args array of arguments. See function body for details.
5207	 * @return response data possibly containing progress operation id
5208	 */
5209	public function remote_mediumRemove($args) {
5210
5211		// Connect to vboxwebsrv
5212		$this->connect();
5213
5214		if(!$args['type']) $args['type'] = 'HardDisk';
5215
5216		/* @var $m IMedium */
5217		$m = $this->vbox->openMedium($args['medium'],$args['type'], 'ReadWrite', false);
5218
5219		if($args['delete'] && @$this->settings->deleteOnRemove && (string)$m->deviceType == 'HardDisk') {
5220
5221			/* @var $progress IProgress */
5222			$progress = $m->deleteStorage();
5223
5224			$m->releaseRemote();
5225
5226			// Does an exception exist?
5227			try {
5228				if($progress->errorInfo->handle) {
5229					$this->errors[] = new Exception($progress->errorInfo->text);
5230					$progress->releaseRemote();
5231					return false;
5232				}
5233			} catch (Exception $null) { }
5234
5235			$this->_util_progressStore($progress);
5236			return array('progress' => $progress->handle);
5237
5238		} else {
5239			$m->close();
5240			$m->releaseRemote();
5241		}
5242
5243		return true;
5244	}
5245
5246	/**
5247	 * Get a list of recent media
5248	 *
5249	 * @param array $args array of arguments. See function body for details.
5250	 * @return array of recent media
5251	 */
5252	public function remote_vboxRecentMediaGet($args) {
5253
5254		// Connect to vboxwebsrv
5255		$this->connect();
5256
5257		$mlist = array();
5258		foreach(array(
5259			array('type'=>'HardDisk','key'=>'GUI/RecentListHD'),
5260			array('type'=>'DVD','key'=>'GUI/RecentListCD'),
5261			array('type'=>'Floppy','key'=>'GUI/RecentListFD')) as $r) {
5262			$list = $this->vbox->getExtraData($r['key']);
5263			$mlist[$r['type']] = array_filter(explode(';', trim($list,';')));
5264		}
5265		return $mlist;
5266	}
5267
5268	/**
5269	 * Get a list of recent media paths
5270	 *
5271	 * @param array $args array of arguments. See function body for details.
5272	 * @return array of recent media paths
5273	 */
5274	public function remote_vboxRecentMediaPathsGet($args) {
5275
5276		// Connect to vboxwebsrv
5277		$this->connect();
5278
5279		$mlist = array();
5280		foreach(array(
5281			array('type'=>'HardDisk','key'=>'GUI/RecentFolderHD'),
5282			array('type'=>'DVD','key'=>'GUI/RecentFolderCD'),
5283			array('type'=>'Floppy','key'=>'GUI/RecentFolderFD')) as $r) {
5284			$mlist[$r['type']] = $this->vbox->getExtraData($r['key']);
5285		}
5286		return $mlist;
5287	}
5288
5289
5290	/**
5291	 * Update recent medium path list
5292	 *
5293	 * @param array $args array of arguments. See function body for details.
5294	 * @return boolean true on success
5295	 */
5296	public function remote_vboxRecentMediaPathSave($args) {
5297
5298		// Connect to vboxwebsrv
5299		$this->connect();
5300
5301		$types = array(
5302			'HardDisk'=>'GUI/RecentFolderHD',
5303			'DVD'=>'GUI/RecentFolderCD',
5304			'Floppy'=>'GUI/RecentFolderFD'
5305		);
5306
5307		$this->vbox->setExtraData($types[$args['type']], $args['folder']);
5308
5309		return true;
5310	}
5311
5312	/**
5313	 * Update recent media list
5314	 *
5315	 * @param array $args array of arguments. See function body for details.
5316	 * @return boolean true on success
5317	 */
5318	public function remote_vboxRecentMediaSave($args) {
5319
5320		// Connect to vboxwebsrv
5321		$this->connect();
5322
5323		$types = array(
5324			'HardDisk'=>'GUI/RecentListHD',
5325			'DVD'=>'GUI/RecentListCD',
5326			'Floppy'=>'GUI/RecentListFD'
5327		);
5328
5329		$this->vbox->setExtraData($types[$args['type']], implode(';',array_unique($args['list'])).';');
5330
5331		return true;
5332
5333	}
5334
5335	/**
5336	 * Mount a medium on the VM
5337	 *
5338	 * @param array $args array of arguments. See function body for details.
5339	 * @return boolean true on success
5340	 */
5341	public function remote_mediumMount($args) {
5342
5343		// Connect to vboxwebsrv
5344		$this->connect();
5345
5346		// Find medium attachment
5347		/* @var $machine IMachine */
5348		$machine = $this->vbox->findMachine($args['vm']);
5349		$state = (string)$machine->sessionState;
5350
5351		// create session
5352		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
5353
5354		if($state == 'Unlocked') {
5355			$machine->lockMachine($this->session->handle,'Write');
5356			$save = true; // force save on closed session as it is not a "run-time" change
5357		} else {
5358
5359			$machine->lockMachine($this->session->handle, 'Shared');
5360		}
5361
5362		// Empty medium / eject
5363		if($args['medium'] == 0) {
5364			$med = null;
5365		} else {
5366			// Host drive
5367			if(strtolower($args['medium']['hostDrive']) == 'true' || $args['medium']['hostDrive'] === true) {
5368				// CD / DVD Drive
5369				if($args['medium']['deviceType'] == 'DVD') {
5370					$drives = $this->vbox->host->DVDDrives;
5371				// floppy drives
5372				} else {
5373					$drives = $this->vbox->host->floppyDrives;
5374				}
5375				foreach($drives as $m) { /* @var $m IMedium */
5376					if($m->id == $args['medium']['id']) {
5377						/* @var $med IMedium */
5378						$med = &$m;
5379						break;
5380					}
5381					$m->releaseRemote();
5382				}
5383			// Normal medium
5384			} else {
5385				/* @var $med IMedium */
5386				$med = $this->vbox->openMedium($args['medium']['location'],$args['medium']['deviceType'],'ReadWrite',false);
5387			}
5388		}
5389
5390		$this->session->machine->mountMedium($args['controller'],$args['port'],$args['device'],(is_object($med) ? $med->handle : null),true);
5391
5392		if(is_object($med)) $med->releaseRemote();
5393
5394		if($save) $this->session->machine->saveSettings();
5395
5396		$this->session->unlockMachine();
5397		$machine->releaseRemote();
5398		unset($this->session);
5399
5400		return true;
5401	}
5402
5403	/**
5404	 * Get medium details
5405	 *
5406	 * @param IMedium $m medium instance
5407	 * @return array medium details
5408	 */
5409	private function _mediumGetDetails(&$m) {
5410
5411		$children = array();
5412		$attachedTo = array();
5413		$machines = $m->machineIds;
5414		$hasSnapshots = 0;
5415
5416		foreach($m->children as $c) { /* @var $c IMedium */
5417			$children[] = $this->_mediumGetDetails($c);
5418			$c->releaseRemote();
5419		}
5420
5421		foreach($machines as $mid) {
5422			$sids = $m->getSnapshotIds($mid);
5423			try {
5424				/* @var $mid IMachine */
5425				$mid = $this->vbox->findMachine($mid);
5426			} catch (Exception $e) {
5427				$attachedTo[] = array('machine' => $mid .' ('.$e->getMessage().')', 'snapshots' => array());
5428				continue;
5429			}
5430
5431			$c = count($sids);
5432			$hasSnapshots = max($hasSnapshots,$c);
5433			for($i = 0; $i < $c; $i++) {
5434				if($sids[$i] == $mid->id) {
5435					unset($sids[$i]);
5436				} else {
5437					try {
5438						/* @var $sn ISnapshot */
5439						$sn = $mid->findSnapshot($sids[$i]);
5440						$sids[$i] = $sn->name;
5441						$sn->releaseRemote();
5442					} catch(Exception $e) { }
5443				}
5444			}
5445			$hasSnapshots = (count($sids) ? 1 : 0);
5446			$attachedTo[] = array('machine'=>$mid->name,'snapshots'=>$sids);
5447			$mid->releaseRemote();
5448		}
5449
5450		// For $fixed value
5451		$mvenum = new MediumVariant(null, null);
5452		$variant = 0;
5453
5454		foreach($m->variant as $mv) {
5455			$variant += $mvenum->ValueMap[(string)$mv];
5456		}
5457
5458		// Encryption settings
5459		$encryptionSettings = null;
5460		if((string)$m->deviceType == 'HardDisk') {
5461    		try {
5462    		    list($id, $cipher) = $m->getEncryptionSettings();
5463    		    if($id) {
5464        		    $encryptionSettings = array(
5465        		      'id' => $id,
5466        		      'cipher' => $cipher,
5467        		    );
5468    		    }
5469		    } catch (Exception $e) {
5470		        // Pass. Encryption is not configured
5471    		}
5472
5473		}
5474		return array(
5475				'id' => $m->id,
5476				'description' => $m->description,
5477				'state' => (string)$m->refreshState(),
5478				'location' => $m->location,
5479				'name' => $m->name,
5480				'deviceType' => (string)$m->deviceType,
5481				'hostDrive' => $m->hostDrive,
5482				'size' => (string)$m->size, /* (string) to support large disks. Bypass integer limit */
5483				'format' => $m->format,
5484				'type' => (string)$m->type,
5485				'parent' => (((string)$m->deviceType == 'HardDisk' && $m->parent->handle) ? $m->parent->id : null),
5486				'children' => $children,
5487				'base' => (((string)$m->deviceType == 'HardDisk' && $m->base->handle) ? $m->base->id : null),
5488				'readOnly' => $m->readOnly,
5489				'logicalSize' => ($m->logicalSize/1024)/1024,
5490				'autoReset' => $m->autoReset,
5491				'hasSnapshots' => $hasSnapshots,
5492				'lastAccessError' => $m->lastAccessError,
5493				'variant' => $variant,
5494				'machineIds' => array(),
5495				'attachedTo' => $attachedTo,
5496		        'encryptionSettings' => $encryptionSettings
5497			);
5498
5499	}
5500
5501	/**
5502	 * Store a progress operation so that its status can be polled via progressGet()
5503	 *
5504	 * @param IProgress $progress progress operation instance
5505	 * @return string progress operation handle / id
5506	 */
5507	private function _util_progressStore(&$progress) {
5508
5509		/* Store vbox and session handle */
5510		$this->persistentRequest['vboxHandle'] = $this->vbox->handle;
5511		if($this->session->handle) {
5512		    $this->persistentRequest['sessionHandle'] = $this->session->handle;
5513		}
5514
5515		/* Store server if multiple servers are configured */
5516		if(@is_array($this->settings->servers) && count($this->settings->servers) > 1)
5517			$this->persistentRequest['vboxServer'] = $this->settings->name;
5518
5519		return $progress->handle;
5520	}
5521
5522	/**
5523	 * Get VirtualBox system properties
5524	 * @param array $args array of arguments. See function body for details.
5525	 * @return array of system properties
5526	 */
5527	public function remote_vboxSystemPropertiesGet($args) {
5528
5529		// Connect to vboxwebsrv
5530		$this->connect();
5531
5532		$mediumFormats = array();
5533
5534		// Shorthand
5535		$sp = $this->vbox->systemProperties;
5536
5537		// capabilities
5538		$mfCap = new MediumFormatCapabilities(null,'');
5539		foreach($sp->mediumFormats as $mf) { /* @var $mf IMediumFormat */
5540			$exts = $mf->describeFileExtensions();
5541			$dtypes = array();
5542			foreach($exts[1] as $t) $dtypes[] = (string)$t;
5543			$caps = array();
5544			foreach($mf->capabilities as $c) {
5545				$caps[] = (string)$c;
5546			}
5547
5548			$mediumFormats[] = array('id'=>$mf->id,'name'=>$mf->name,'extensions'=>array_map('strtolower',$exts[0]),'deviceTypes'=>$dtypes,'capabilities'=>$caps);
5549
5550		}
5551
5552		$scs = array();
5553
5554		$scts = array('LsiLogic',
5555                    'BusLogic',
5556                    'IntelAhci',
5557                    'PIIX4',
5558                    'ICH6',
5559                    'I82078',
5560                    'USB');
5561
5562		foreach($scts as $t) {
5563		    $scs[$t] = $sp->getStorageControllerHotplugCapable($t);
5564		}
5565
5566		return array(
5567			'minGuestRAM' => (string)$sp->minGuestRAM,
5568			'maxGuestRAM' => (string)$sp->maxGuestRAM,
5569			'minGuestVRAM' => (string)$sp->minGuestVRAM,
5570			'maxGuestVRAM' => (string)$sp->maxGuestVRAM,
5571			'minGuestCPUCount' => (string)$sp->minGuestCPUCount,
5572			'maxGuestCPUCount' => (string)$sp->maxGuestCPUCount,
5573			'autostartDatabasePath' => (@$this->settings->vboxAutostartConfig ? $sp->autostartDatabasePath : ''),
5574			'infoVDSize' => (string)$sp->infoVDSize,
5575			'networkAdapterCount' => 8, // static value for now
5576			'maxBootPosition' => (string)$sp->maxBootPosition,
5577			'defaultMachineFolder' => (string)$sp->defaultMachineFolder,
5578			'defaultHardDiskFormat' => (string)$sp->defaultHardDiskFormat,
5579			'homeFolder' => $this->vbox->homeFolder,
5580			'VRDEAuthLibrary' => (string)$sp->VRDEAuthLibrary,
5581			'defaultAudioDriver' => (string)$sp->defaultAudioDriver,
5582			'defaultVRDEExtPack' => $sp->defaultVRDEExtPack,
5583			'serialPortCount' => $sp->serialPortCount,
5584			'parallelPortCount' => $sp->parallelPortCount,
5585			'mediumFormats' => $mediumFormats,
5586		    'scs' => $scs
5587		);
5588	}
5589
5590	/**
5591	 * Get a list of VM log file names
5592	 *
5593	 * @param array $args array of arguments. See function body for details.
5594	 * @return array of log file names
5595	 */
5596	public function remote_machineGetLogFilesList($args) {
5597
5598		// Connect to vboxwebsrv
5599		$this->connect();
5600
5601		/* @var $m IMachine */
5602		$m = $this->vbox->findMachine($args['vm']);
5603
5604		$logs = array();
5605
5606		try { $i = 0; while($l = $m->queryLogFilename($i++)) $logs[] = $l;
5607		} catch (Exception $null) {}
5608
5609		$lf = $m->logFolder;
5610		$m->releaseRemote();
5611
5612		return array('path' => $lf, 'logs' => $logs);
5613
5614	}
5615
5616	/**
5617	 * Get VM log file contents
5618	 *
5619	 * @param array $args array of arguments. See function body for details.
5620	 * @return string log file contents
5621	 */
5622	public function remote_machineGetLogFile($args) {
5623
5624		// Connect to vboxwebsrv
5625		$this->connect();
5626
5627		/* @var $m IMachine */
5628		$m = $this->vbox->findMachine($args['vm']);
5629		$log = '';
5630		try {
5631			// Read in 8k chunks
5632			while($l = $m->readLog(intval($args['log']),strlen($log),8192)) {
5633				if(!count($l) || !strlen($l[0])) break;
5634				$log .= base64_decode($l[0]);
5635			}
5636		} catch (Exception $null) {}
5637		$m->releaseRemote();
5638
5639		// Attempt to UTF-8 encode string or json_encode may choke
5640		// and return an empty string
5641		if(function_exists('utf8_encode'))
5642			return utf8_encode($log);
5643
5644		return $log;
5645	}
5646
5647	/**
5648	 * Get a list of USB devices attached to a given VM
5649	 *
5650	 * @param array $args array of arguments. See function body for details.
5651	 * @return array list of devices
5652	 */
5653	public function remote_consoleGetUSBDevices($args) {
5654
5655		// Connect to vboxwebsrv
5656		$this->connect();
5657
5658		/* @var $machine IMachine */
5659		$machine = $this->vbox->findMachine($args['vm']);
5660		$this->session = $this->websessionManager->getSessionObject($this->vbox->handle);
5661		$machine->lockMachine($this->session->handle, 'Shared');
5662
5663		$response = array();
5664		foreach($this->session->console->USBDevices as $u) { /* @var $u IUSBDevice */
5665			$response[$u->id] = array('id'=>$u->id,'remote'=>$u->remote);
5666			$u->releaseRemote();
5667		}
5668
5669		$this->session->unlockMachine();
5670		unset($this->session);
5671		$machine->releaseRemote();
5672
5673		return $response;
5674
5675	}
5676
5677	/**
5678	 * Return a string representing the VirtualBox ExtraData key
5679	 * for this port + device + bus type IgnoreFlush setting
5680	 *
5681	 * @param integer port medium attachment port number
5682	 * @param integer device medium attachment device number
5683	 * @param string cType controller type
5684	 * @return string extra data setting string
5685	 */
5686	private function _util_getIgnoreFlushKey($port,$device,$cType) {
5687
5688		$cTypes = array(
5689			'piix3' => 'piix3ide',
5690			'piix4' => 'piix3ide',
5691			'ich6' => 'piix3ide',
5692			'intelahci' => 'ahci',
5693			'lsilogic' => 'lsilogicscsi',
5694			'buslogic' => 'buslogic',
5695			'lsilogicsas' => 'lsilogicsas'
5696		);
5697
5698		if(!isset($cTypes[strtolower($cType)])) {
5699			$this->errors[] = new Exception('Invalid controller type: ' . $cType);
5700			return '';
5701		}
5702
5703		$lun = ((intval($device)*2) + intval($port));
5704
5705		return str_replace('[b]',$lun,str_replace('[a]',$cTypes[strtolower($cType)],"VBoxInternal/Devices/[a]/0/LUN#[b]/Config/IgnoreFlush"));
5706
5707	}
5708
5709	/**
5710	 * Get a newly generated MAC address from VirtualBox
5711	 *
5712	 * @param array $args array of arguments. See function body for details
5713	 * @return string mac address
5714	 */
5715	public function remote_vboxGenerateMacAddress($args) {
5716
5717		// Connect to vboxwebsrv
5718		$this->connect();
5719
5720		return $this->vbox->host->generateMACAddress();
5721
5722	}
5723
5724	/**
5725	 * Set group definition
5726	 *
5727	 * @param array $args array of arguments. See function body for details
5728	 * @return boolean true on success
5729	 */
5730	public function remote_vboxGroupDefinitionsSet($args) {
5731
5732		$this->connect();
5733
5734		// Save a list of valid paths
5735		$validGroupPaths = array();
5736
5737		$groupKey = ($this->settings->phpVboxGroups ? vboxconnector::phpVboxGroupKey : 'GUI/GroupDefinitions');
5738
5739		// Write out each group definition
5740		foreach($args['groupDefinitions'] as $groupDef) {
5741
5742			$this->vbox->setExtraData($groupKey.$groupDef['path'], $groupDef['order']);
5743			$validGroupPaths[] = $groupDef['path'];
5744
5745		}
5746
5747		// Remove any unused group definitions
5748		$keys = $this->vbox->getExtraDataKeys();
5749		foreach($keys as $k) {
5750			if(strpos($k,$groupKey) !== 0) continue;
5751			if(array_search(substr($k,strlen($groupKey)), $validGroupPaths) === false)
5752				$this->vbox->setExtraData($k,'');
5753		}
5754
5755		return true;
5756	}
5757
5758	/**
5759	 * Return group definitions
5760	 *
5761	 * @param array $args array of arguments. See function body for details
5762	 * @return array group definitions
5763	 */
5764	public function remote_vboxGroupDefinitionsGet($args) {
5765
5766		$this->connect();
5767
5768		$response = array();
5769
5770		$keys = $this->vbox->getExtraDataKeys();
5771
5772		$groupKey = ($this->settings->phpVboxGroups ? vboxconnector::phpVboxGroupKey : 'GUI/GroupDefinitions');
5773		foreach($keys as $grouppath) {
5774
5775			if(strpos($grouppath,$groupKey) !== 0) continue;
5776
5777			$subgroups = array();
5778			$machines = array();
5779
5780			$response[] = array(
5781				'name' => substr($grouppath,strrpos($grouppath,'/')+1),
5782				'path' => substr($grouppath,strlen($groupKey)),
5783				'order' => $this->vbox->getExtraData($grouppath)
5784			);
5785		}
5786
5787		return $response;
5788
5789	}
5790
5791	/**
5792	 * Format a time span in seconds into days / hours / minutes / seconds
5793	 * @param integer $t number of seconds
5794	 * @return array containing number of days / hours / minutes / seconds
5795	 */
5796	private function _util_splitTime($t) {
5797
5798		$spans = array(
5799			'days' => 86400,
5800			'hours' => 3600,
5801			'minutes' => 60,
5802			'seconds' => 1);
5803
5804		$time = array();
5805
5806		foreach($spans as $k => $v) {
5807			if(!(floor($t / $v) > 0)) continue;
5808			$time[$k] = floor($t / $v);
5809			$t -= floor($time[$k] * $v);
5810		}
5811
5812		return $time;
5813	}
5814
5815
5816	/**
5817	 * Return VBOX result code text for result code
5818	 *
5819	 * @param integer result code number
5820	 * @return string result code text
5821	 */
5822	private function _util_resultCodeText($c) {
5823
5824		$rcodes = new ReflectionClass('VirtualBox_COM_result_codes');
5825    	$rcodes = array_flip($rcodes->getConstants());
5826    	$rcodes['0x80004005'] = 'NS_ERROR_FAILURE';
5827
5828		return @$rcodes['0x'.strtoupper(dechex($c))] . ' (0x'.strtoupper(dechex($c)).')';
5829	}
5830}
5831
5832