getIdentifier(); } // - Check if machine exists $machine = Statistics::getMachine($machineuuid, Machine::NO_DATA); if ($machine === false) return false; // - Delete entry if mode is null if ($modeId === null) { return Database::exec('DELETE FROM runmode WHERE machineuuid = :machineuuid', compact('machineuuid')) > 0; } // - Add/replace entry in runmode table // - Check if module provides runmode config at all $config = self::getModuleConfig($moduleId); if ($config === false) return false; if ($isClient === null) { $isClient = $config->isClient; } Database::exec('INSERT INTO runmode (machineuuid, module, modeid, modedata, isclient)' . ' VALUES (:uuid, :module, :modeid, :modedata, :isclient)' . ' ON DUPLICATE KEY' . ' UPDATE module = VALUES(module), modeid = VALUES(modeid), modedata = VALUES(modedata), isclient = VALUES(isclient)', array( 'uuid' => $machineuuid, 'module' => $moduleId, 'modeid' => $modeId, 'modedata' => $modeData, 'isclient' => ($isClient ? 1 : 0), )); return true; } /** * @param string $machineuuid * @param int $returnData bitfield of data to return * @return false|array {'machineuuid', 'isclient', 'module', 'modeid', 'modedata', * ('hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'), ('moduleName', 'modeName')} */ public static function getRunMode($machineuuid, $returnData = self::DATA_MACHINE_DATA) { if ($returnData === true) { $returnData = self::DATA_MACHINE_DATA | self::DATA_DETAILED; } if ($returnData & self::DATA_MACHINE_DATA) { if ($returnData & self::DATA_DETAILED) { $sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen'; } else { $sel = ''; } $res = Database::queryFirst( "SELECT m.machineuuid, r.isclient, r.module, r.modeid, r.modedata $sel FROM machine m INNER JOIN runmode r USING (machineuuid) WHERE m.machineuuid = :machineuuid LIMIT 1", compact('machineuuid')); } else { $res = Database::queryFirst('SELECT r.machineuuid, r.isclient, r.module, r.modeid, r.modedata FROM runmode r WHERE r.machineuuid = :machineuuid LIMIT 1', compact('machineuuid')); } if ($res === false) return false; if ($returnData & self::DATA_STRINGS) { $module = Module::get($res['module']); if ($module === false) { $res['moduleName'] = $res['module']; } else { $res['moduleName'] = $module->getDisplayName(); } $mode = self::getModeName($res['module'], $res['modeid']); if ($mode === false) { $mode = '???? unknown'; } $res['modeName'] = $mode; } return $res; } /** * @param string|\Module $module * @param bool true = wrap in array where key is modeid * @return array key=machineuuid, value={'machineuuid', 'modeid', 'modedata'} */ public static function getForModule($module, $groupByModeId = false) { if (is_object($module)) { $module = $module->getIdentifier(); } $res = Database::simpleQuery('SELECT machineuuid, modeid, modedata FROM runmode WHERE module = :module', compact('module')); $ret = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($groupByModeId) { if (!isset($ret[$row['modeid']])) { $ret[$row['modeid']] = array(); } $ret[$row['modeid']][] = $row; } else { $ret[$row['machineuuid']] = $row; } } return $ret; } /** * @param string|\Module $module * @param string $modeId * @param bool $detailed whether to return meta data about machine, not just machineuuid * @param bool $assoc use machineuuid as array key * @return array , value={'machineuuid', 'modedata', * <'hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'>} */ public static function getForMode($module, $modeId, $detailed = false, $assoc = false) { if (is_object($module)) { $module = $module->getIdentifier(); } if ($detailed) { $sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen'; $join = 'INNER JOIN machine m USING (machineuuid)'; } else { $join = $sel = ''; } $res = Database::simpleQuery( "SELECT r.machineuuid, r.modedata, r.isclient $sel FROM runmode r $join WHERE module = :module AND modeid = :modeId", compact('module', 'modeId')); $ret = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($detailed && empty($row['hostname'])) { $row['hostname'] = $row['clientip']; } if ($assoc) { $ret[$row['machineuuid']] = $row; } else { $ret[] = $row; } } return $ret; } /** * Return assoc array of all configured clients. * @param bool $withData also return data field? * @param bool $isClient true = return clients only, false = return non-clients only, null = return both * @return array all the entries from the table */ public static function getAllClients($withData = false, $isClient = null) { $xtra = ''; if ($withData) { $xtra .= ', modedata'; } $res = Database::simpleQuery("SELECT machineuuid, module, modeid, isclient $xtra FROM runmode"); $ret = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (!is_null($isClient) && ($row['isclient'] != 0) !== $isClient) continue; $ret[$row['machineuuid']] = $row; } return $ret; } /** * Get display name of a module's mode. If the module doesn't have a getModeName * method configured, the modeId is simply returned. Otherwise the return value of * that method is passed through. getModeName by contract should return false if * the module doesn't think the given modeId exists. * * @param string|\Module $module * @param string $modeId * @return string|bool mode name if known, modeId as fallback, or false if mode is not known by module */ public static function getModeName($module, $modeId) { if (is_object($module)) { $module = $module->getIdentifier(); } $conf = self::getModuleConfig($module); if ($conf === false || $conf->getModeName === false || !Module::isAvailable($module)) return $modeId; return call_user_func($conf->getModeName, $modeId); } /** * Delete given runmode. * * @param string|\Module $module Module runmode belongs to * @param string $modeId run mode id */ public static function deleteMode($module, $modeId) { if (is_object($module)) { $module = $module->getIdentifier(); } Database::exec('DELETE FROM runmode WHERE module = :module AND modeid = :modeId', compact('module', 'modeId')); } } /* *\ |* Helper classes *| \* */ /** * Class RunModeModuleConfig represents desired config of a runmode */ class RunModeModuleConfig { /** * @var string|false */ public $systemdDefaultTarget = false; /** * @var string[] */ public $systemdDisableTargets = []; /** * @var string[] */ public $systemdEnableTargets = []; /** * @var string Name of function that turns a modeId into a string */ public $getModeName = false; /** * @var string Name of function that is called to add additional config entries */ public $configHook = false; /** * @var bool Consider this a normal client that should e.g. be shown in client statistics by default */ public $isClient = false; /** * @var bool If true, config.tgz should not be downloaded by the client */ public $noSysconfig = false; /** * @var bool Allow adding and removing machines to this mode via the generic form */ public $allowGenericEditor = true; /** * @var string Snippet to construct URL for delete */ public $deleteUrlSnippet = false; public function __construct($file) { $data = json_decode(file_get_contents($file), true); if (!is_array($data)) return; $this->loadType($data, 'systemdDefaultTarget', 'string'); $this->loadType($data, 'systemdDisableTargets', 'array'); $this->loadType($data, 'systemdEnableTargets', 'array'); $this->loadType($data, 'getModeName', 'string'); $this->loadType($data, 'configHook', 'string'); $this->loadType($data, 'isClient', 'boolean'); $this->loadType($data, 'noSysconfig', 'boolean'); $this->loadType($data, 'allowGenericEditor', 'boolean'); $this->loadType($data, 'deleteUrlSnippet', 'string'); } private function loadType($data, $key, $type) { if (!isset($data[$key])) return false; if (is_string($type) && gettype($data[$key]) !== $type) return false; if (is_array($type) && !in_array(gettype($data[$key]), $type)) return false; $this->{$key} = $data[$key]; return true; } }