= $mtime && $now - $mtime <= CONFIG_DOZMOD_EXPIRE; } function cache_get(string $key): string { $filename = cache_key_to_filename($key); return file_get_contents($filename); } /* good for large binary files */ #[NoReturn] function cache_get_passthru(string $key): void { $filename = cache_key_to_filename($key); $fp = fopen($filename, "r"); if ($fp) { fpassthru($fp); exit; } error_log('DMSD-cache: Cannot passthrough cache file ' . $filename); } /* END: Cache ---------------------------------------------------- */ /* this script requires 2 (3 with implicit client ip) parameters * * resource = list,metadata,... * lecture_uuid = client can choose **/ /** * Takes raw lecture list xml, returns array of uuids. * * @param string $responseXML XML from dozmod server * @return array list of UUIDs */ function xmlToLectureIds(string $responseXML): array { try { $xml = new SimpleXMLElement($responseXML); } catch (Exception $e) { EventLog::warning('Error parsing XML response data from DMSD: ' . $e->getMessage(), $responseXML); return []; } if (!isset($xml->eintrag)) return []; $uuids = []; foreach ($xml->eintrag as $e) { if (isset($e->uuid) && isset($e->uuid['param']) && isset($e->uuid['param'][0])) { $uuids[] = strval($e->uuid['param'][0]); } } return $uuids; } #[NoReturn] function sendExamModeMismatch(): void { Header('Content-Type: text/xml; charset=utf-8'); echo << BLA; exit(0); } /** Caching wrapper around _getLecturesForLocations() */ function getListForLocations(array $locationIds, bool $raw) { /* if in any of the locations there is an exam active, consider the client to be in "exam-mode" and only offer him exams (no lectures) */ $key = 'lectures_' . cache_hash($locationIds); $examMode = Request::get('exams', 'normal-mode', 'string') !== 'normal-mode'; $clientServerMismatch = false; if (Module::isAvailable('exams')) { // If we have the exam mode module, we can enforce a server side check and make sure it agrees with the client $serverExamMode = Exams::isInExamMode($locationIds); if ($raw) { $clientServerMismatch = ($serverExamMode !== $examMode); } $examMode = $serverExamMode; } // Only enforce exam mode validity check if the client requests the raw xml data if ($clientServerMismatch) { sendExamModeMismatch(); // does not return } // Proceed normally from here on if ($examMode) { $key .= '_exams'; } $rawKey = $key . '_raw'; if ($raw) { Header('Content-Type: text/xml; charset=utf-8'); if (cache_has($rawKey)) { cache_get_passthru($rawKey); } } elseif (cache_has($key)) { return unserialize(cache_get($key)); } // Not in cache $url = LIST_URL . "?locations=" . implode('%20', $locationIds); if ($examMode) { $url .= '&exams'; } $t = microtime(true); $value = Download::asString($url, 60, $code); $t = microtime(true) - $t; if ($t > 5) { error_log("DMSD-cache: Download of lecture list took $t ($code)"); } if ($value === false || $code < 200 || $code > 299) return false; cache_put($rawKey, $value); $list = xmlToLectureIds($value); cache_put($key, serialize($list)); if ($raw) { die($value); } return $list; } function getLectureUuidsForLocations(array $locationIds) { return getListForLocations($locationIds, false); } function outputLectureXmlForLocation(array $locationIds) { return getListForLocations($locationIds, true); } function _getVmData(string $lecture_uuid, string $subResource = null) { $url = VMX_URL . '/' . $lecture_uuid; if ($subResource !== null) { $url .= '/' . $subResource; } $t = microtime(true); $response = Download::asString($url, 60, $code); $t = microtime(true) - $t; if ($t > 5) { error_log("DMSD-cache: Download of $subResource took $t ($code)"); } if ($code < 200 || $code > 299) { error_log("DMSD-cache: Return code $code, payload len " . strlen($response)); return (int)$code; } return $response; } /** Caching wrapper around _getVmData() **/ function outputResource(string $lecture_uuid, string $resource): void { if ($resource === 'metadata') { // HACK: config.tgz is compressed, don't use gzip output handler @ob_end_clean(); Header('Content-Type: application/gzip'); } else { Header('Content-Type: text/plain; charset=utf-8'); } $key = $resource . '_' . $lecture_uuid; if (cache_has($key)) { if ($resource === 'metadata' || $resource === 'vmx') { // HACK HACK HACK: Update launch counter as it was cached, // otherwise dmsd would take care of increasing it... Database::exec("UPDATE sat.lecture SET usecount = usecount + 1 WHERE lectureid = :lectureid", ['lectureid' => $lecture_uuid], true); } cache_get_passthru($key); } else { $value = _getVmData($lecture_uuid, $resource); if ($value === false) return; if (is_int($value)) { http_response_code($value); exit; } cache_put($key, $value); die($value); } } #[NoReturn] function fatalDozmodUnreachable() { Header('HTTP/1.1 504 Gateway Timeout'); die('DMSD currently not available'); } function readLectureParam(array $locationIds): string { $lecture = Request::get('lecture', false, 'string'); if ($lecture === false) { Header('HTTP/1.1 400 Bad Request'); die('Missing lecture UUID'); } $lectures = getLectureUuidsForLocations($locationIds); if ($lectures === false) { fatalDozmodUnreachable(); } /* check that the user requests a lecture that he is allowed to have */ if (!in_array($lecture, $lectures)) { Header('HTTP/1.1 403 Forbidden'); die("You don't have permission to access this lecture"); } return $lecture; } // in this context the lecture param is an image id (container), // just read and check if valid. // TODO do we need to check if this is allowed? function readImageParam(): string { $image = Request::get('lecture', false, 'string'); if ($image === false) { Header('HTTP/1.1 400 Bad Request'); die('Missing IMAGE UUID'); } return $image; } // -----------------------------------------------------------------------------// /* request data, don't trust */ $resource = Request::get('resource', false, 'string'); if ($resource === false) { ErrorHandler::traceError("you have to specify the 'resource' parameter"); } if (!in_array($resource, $availableRessources)) { Header('HTTP/1.1 400 Bad Request'); die("unknown resource: $resource"); } $ip = $_SERVER['REMOTE_ADDR']; if (substr($ip, 0, 7) === '::ffff:') { $ip = substr($ip, 7); } /* lookup location id(s) */ $location_ids = Location::getFromIp($ip, true); $location_ids = Location::getLocationRootChain($location_ids); if ($resource === 'list') { outputLectureXmlForLocation($location_ids); // Won't return on success... } elseif ($resource === 'imagemeta') { $image = readImageParam(); outputResource($image, $resource); } else { $lecture = readLectureParam($location_ids); outputResource($lecture, $resource); } fatalDozmodUnreachable();