summaryrefslogblamecommitdiffstats
path: root/modules-available/rebootcontrol/page.inc.php
blob: 67996f02bad87bb43d214083299d72224eb0383d (plain) (tree)
1
2
3
4
5




                                     











                                                                                         
                                                                    
 










                                                                     
 






                                                                               
 












                                                                                                                                 
                         




























                                                                                             
 
























                                                                                                                                                

                                 
                 
















                                                                   
 












                                                                                                                     







                                                                                        
                                                                    
 



                                                                                
 







                                                  
 



                                                                              
 



                                                                           
 




































                                                                                       
 
















                                                                                                                      
 









                                                                                                 
 
         
 































                                                                                                                                                
 







                                                                                                 
                 
                             

         
                                   
         

                                                                   


                                                                          




                                               
 
 







                                                  
 
<?php

class Page_RebootControl extends Page
{

	/**
	 * Called before any page rendering happens - early hook to check parameters etc.
	 */
	protected function doPreprocess()
	{
		User::load();

		if (!User::isLoggedIn()) {
			Message::addError('main.no-permission');
			Util::redirect('?do=Main'); // does not return
		}

		$action = Request::post('action', 'show', 'string');

		if ($action === 'reboot' || $action === 'shutdown') {
			$this->execRebootShutdown($action);
		} elseif ($action === 'savejumphost') {
			$this->saveJumpHost();
		} elseif ($action === 'jumphost') {
			$this->postJumpHostDispatch();
		}
		if (Request::isPost()) {
			Util::redirect('?do=rebootcontrol');
		}
	}

	private function execRebootShutdown($action)
	{
		$requestedClients = Request::post('clients', false, 'array');
		if (!is_array($requestedClients) || empty($requestedClients)) {
			Message::addError('no-clients-selected');
			return;
		}

		$actualClients = RebootQueries::getMachinesByUuid($requestedClients);
		if (count($actualClients) !== count($requestedClients)) {
			// We could go ahead an see which ones were not found in DB but this should not happen anyways unless the
			// user manipulated the request
			Message::addWarning('some-machine-not-found');
		}
		// Filter ones with no permission
		foreach (array_keys($actualClients) as $idx) {
			if (!User::hasPermission('action.' . $action, $actualClients[$idx]['locationid'])) {
				Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']);
				unset($actualClients[$idx]);
			} else {
				$locationId = $actualClients[$idx]['locationid'];
			}
		}
		// See if anything is left
		if (!is_array($actualClients) || empty($actualClients)) {
			Message::addError('no-clients-selected');
			return;
		}
		usort($actualClients, function($a, $b) {
			$a = ($a['state'] === 'IDLE' || $a['state'] === 'OCCUPIED');
			$b = ($b['state'] === 'IDLE' || $b['state'] === 'OCCUPIED');
			if ($a === $b)
				return 0;
			return $a ? -1 : 1;
		});
		if ($action === 'shutdown') {
			$mode = 'SHUTDOWN';
			$minutes = Request::post('s-minutes', 0, 'int');
		} elseif (Request::any('quick', false, 'string') === 'on') {
			$mode = 'KEXEC_REBOOT';
			$minutes = Request::post('r-minutes', 0, 'int');
		} else {
			$mode = 'REBOOT';
			$minutes = Request::post('r-minutes', 0, 'int');
		}
		$task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
		if (Taskmanager::isTask($task)) {
			Util::redirect("?do=rebootcontrol&show=task&taskid=" . $task["id"]);
		}
		return;
	}

	private function saveJumpHost()
	{
		User::assertPermission('jumphost.edit');
		$id = Request::post('hostid', Request::REQUIRED, 'string');
		$host = Request::post('host', Request::REQUIRED, 'string');
		$port = Request::post('port', Request::REQUIRED, 'int');
		if ($port < 1 || $port > 65535) {
			Message::addError('invalid-port', $port);
			return;
		}
		$username = Request::post('username', Request::REQUIRED, 'string');
		$sshkey = Request::post('sshkey', Request::REQUIRED, 'string');
		$script = preg_replace('/\r\n?/', "\n", Request::post('script', Request::REQUIRED, 'string'));
		if ($id === 'new') {
			$ret = Database::exec('INSERT INTO reboot_jumphost (host, port, username, sshkey, script, reachable)
				VALUE (:host, :port, :username, :sshkey, :script, 0)', compact('host', 'port', 'username', 'sshkey', 'script'));
			$id = Database::lastInsertId();
		} else {
			$ret = Database::exec('UPDATE reboot_jumphost SET
				host = :host, port = :port, username = :username, sshkey = :sshkey, script = :script, reachable = 0
				WHERE hostid = :id', compact('host', 'port', 'username', 'sshkey', 'script', 'id'));
			if ($ret === 0) {
				$ret = Database::queryFirst('SELECT hostid FROM reboot_jumphost WHERE hostid = :id', ['id' => $id]);
				if ($ret !== false) {
					$ret = 1;
				}
			}
		}
		if ($ret > 0) {
			Message::addSuccess('jumphost-saved', $id);
			$this->execCheckConnection($id);
		} else {
			Message::addError('no-such-jumphost', $id);
		}
	}

	private function postJumpHostDispatch()
	{
		$id = Request::post('checkid', false, 'int');
		if ($id !== false) {
			// Check connectivity
			$this->execCheckConnection($id);
			return;
		}
	}

	private function execCheckConnection($hostid)
	{
		$host = $this->getJumpHost($hostid);
		$script = str_replace(['%IP%', '%MACS%'], ['255.255.255.255', '00:11:22:33:44:55'], $host['script']);
		$task = RebootControl::runScript([[
			'clientip' => $host['host'],
			'port' => $host['port'],
			'username' => $host['username'],
		]], $script, 5, $host['sshkey']);
		if (!Taskmanager::isTask($task))
			return;
		TaskmanagerCallback::addCallback($task, 'rbcConnCheck', $hostid);
		Util::redirect('?do=rebootcontrol&show=task&type=checkhost&taskid=' . $task['id']);
	}

	/**
	 * Menu etc. has already been generated, now it's time to generate page content.
	 */

	protected function doRender()
	{
		$show = Request::get('show', 'jumphosts', 'string');

		// Always show public key (it's public, isn't it?)
		$data = ['pubkey' => SSHKey::getPublicKey()];
		Permission::addGlobalTags($data['perms'], null, ['newkeypair']);
		Render::addTemplate('header', $data);

		if ($show === 'task') {
			$this->showTask();
		} elseif ($show === 'jumphosts') {
			$this->showJumpHosts();
		} elseif ($show === 'jumphost') {
			$this->showJumpHost();
		}
	}

	private function showTask()
	{
		$taskid = Request::get("taskid", Request::REQUIRED, 'string');
		$task = Taskmanager::status($taskid);

		if (!Taskmanager::isTask($task) || !isset($task['data'])) {
			Message::addError('no-such-task', $taskid);
			return;
		}

		$td =& $task['data'];
		$type = Request::get('type', false, 'string');
		if ($type === false) {
			// Try to guess
			if (isset($td['locationId']) || isset($td['clients'])) {
				$type = 'reboot';
			} elseif (isset($td['result'])) {
				$type = 'exec';
			}
		}
		if ($type === 'reboot') {
			$data = [
				'taskId' => $task['id'],
				'locationId' => $td['locationId'],
				'locationName' => Location::getName($td['locationId']),
			];
			$uuids = array_map(function ($entry) {
				return $entry['machineuuid'];
			}, $td['clients']);
			$data['clients'] = RebootQueries::getMachinesByUuid($uuids);
			Render::addTemplate('status-reboot', $data);
		} elseif ($type === 'exec') {
			$data = [
				'taskId' => $task['id'],
			];
			Render::addTemplate('status-exec', $data);
		} elseif ($type === 'checkhost') {
			$ip = array_key_first($td['result']);
			$data = [
				'taskId' => $task['id'],
				'host' => $ip,
			];
			Render::addTemplate('status-checkconnection', $data);
		} else {
			Message::addError('unknown-task-type');
		}
	}

	private function showJumpHosts()
	{
		User::assertPermission('jumphost.*');
		$hosts = [];
		$res = Database::simpleQuery('SELECT hostid, host, port, Count(jxs.subnetid) AS subnetCount, reachable
				FROM reboot_jumphost jh
				LEFT JOIN reboot_jumphost_x_subnet jxs USING (hostid)
				GROUP BY hostid
				ORDER BY hostid');
		while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
			$hosts[] = $row;
		}
		$data = [
			'jumpHosts' => $hosts
		];
		Permission::addGlobalTags($data['perms'], null, ['jumphost.edit', 'jumphost.assign-subnet']);
		Render::addTemplate('jumphost-list', $data);

		// Append list of active reboot/shutdown tasks
		$allowedLocs = User::getAllowedLocations("action.*");
		$active = RebootControl::getActiveTasks($allowedLocs);
		if (!empty($active)) {
			foreach ($active as &$entry) {
				$entry['locationName'] = Location::getName($entry['locationId']);
			}
			unset($entry);
			Render::addTemplate('task-list', ['list' => $active]);
		}

	}

	private function showJumpHost()
	{
		User::assertPermission('jumphost.edit');
		$id = Request::get('id', Request::REQUIRED, 'string');
		if ($id === 'new') {
			$host = ['hostid' => 'new', 'port' => 22, 'script' => "# Assume bash\n"
				. "MACS='%MACS%'\n"
				. "IP='%IP'\n"
				. "EW=false\n"
				. "WOL=false\n"
				. "command -v etherwake > /dev/null && ( [ \"\$(id -u)\" = 0 ] || [ -u \"\$(which etherwake)\" ] ) && EW=true\n"
				. "command -v wakeonlan > /dev/null && WOL=true\n"
				. "if \$EW && ( ! \$WOL || [ \"\$IP\" = '255.255.255.255' ] ); then\n"
				. "\tifaces=\"\$(ls -1 /sys/class/net/)\"\n"
				. "\t[ -z \"\$ifaces\" ] && ifaces=eth0\n"
				. "\tfor ifc in \$ifaces; do\n"
				. "\t\t[ \"\$ifc\" = 'lo' ] && continue\n"
				. "\t\tfor mac in \$MACS; do\n"
				. "\t\t\tetherwake -i \"\$ifc\" \"\$mac\"\n"
				. "\t\tdone\n"
				. "\tdone\n"
				. "elif \$WOL; then\n"
				. "\twakeonlan -i \"\$IP\" \$MACS\n"
				. "else\n"
				. "\techo 'No suitable WOL tool found' >&2\n"
				. "\texit 1\n"
				. "fi\n"];
		} else {
			$host = $this->getJumpHost($id);
		}
		Render::addTemplate('jumphost-edit', $host);
	}

	private function getJumpHost($hostid)
	{
		$host = Database::queryFirst('SELECT hostid, host, port, username, sshkey, script
				FROM reboot_jumphost
				WHERE hostid = :id', ['id' => $hostid]);
		if ($host === false) {
			Message::addError('no-such-jumphost', $hostid);
			Util::redirect('?do=rebootcontrol');
		}
		return $host;
	}

	protected function doAjax()
	{
		$action = Request::post('action', false, 'string');
		if ($action === 'generateNewKeypair') {
			User::assertPermission("newkeypair");
			Property::set("rebootcontrol-private-key", false);
			echo SSHKey::getPublicKey();
		} else {
			echo 'Invalid action.';
		}
	}

}

// Remove when we require >= 7.3.0
if (!function_exists('array_key_first')) {
	function array_key_first(array $arr) {
		foreach($arr as $key => $unused) {
			return $key;
		}
		return NULL;
	}
}