response->getStatus() . ": " . $app->request->getMethod() . " " . $app->request->getPathInfo() . ": $message\n", FILE_APPEND ); } } /** * @brief Compose JSON response. * @param int status Status code for HTTP response. * @param array response Response data. * @param int opts Options to encode JSON data. * @return string JSON response. */ function jsonResponse($status, $response, $opts = 0) { $app = \Slim\Slim::getInstance(); // HTTP status code. $app->status($status); // Content-type HTTP header. $app->contentType('application/json; charset=utf-8'); // JSON response. echo json_encode($response, $opts); } /** * @brief Print immediately JSON response to continue processing. * @param int status Status code for HTTP response. * @param array response Response data. * @param int opts Options to encode JSON data. * @return string JSON response. */ function jsonResponseNow($status, $response, $opts = 0) { // Compose headers and content. ignore_user_abort(); http_response_code((int)$status); header('Content-type: application/json; charset=utf-8'); ob_start(); echo json_encode($response, $opts); $size = ob_get_length(); header("Content-Length: $size"); // Print content. ob_end_flush(); flush(); session_write_close(); } /** * @brief Validate API key included in "Authorization" HTTP header. * @return string JSON response on error. */ function validateApiKey() { global $cmd; global $userid; $response = []; $app = \Slim\Slim::getInstance(); // Read Authorization HTTP header. if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { // Assign user id. that match this key to global variable. $apikey = htmlspecialchars($_SERVER['HTTP_AUTHORIZATION']); $cmd->texto = "SELECT idusuario FROM usuarios WHERE apikey='$apikey' LIMIT 1"; $rs = new Recordset; $rs->Comando = &$cmd; if ($rs->Abrir()) { $rs->Primero(); if (!$rs->EOF) { // Fetch user id. $userid = $rs->campos["idusuario"]; } else { // Credentials error. $response['message'] = 'Login failed, incorrect credentials'; jsonResponse(401, $response); $app->stop(); } $rs->Cerrar(); } else { // Database error. $response['message'] = "An error occurred, please try again"; jsonResponse(500, $response); } } else { // Error: missing API key. $response['message'] = 'Missing API key'; jsonResponse(400, $response); $app->stop(); } } /** * @brief Check if parameter is set and print error messages if empty. * @param string param Parameter to check. * @return boolean "false" if parameter is null, otherwise "true". */ function checkParameter($param) { $response = []; if (isset($param)) { return true; } else { // Print error message. $response['message'] = 'Parameter not found'; jsonResponse(400, $response); return false; } } /** * @brief Check if all parameters are positive integer numbers. * @param int id ... Identificators to check (variable number of parameters). * @return boolean "true" if all ids are int>0, otherwise "false". */ function checkIds() { $opts = ['options' => ['min_range' => 1]]; // Check for int>0 foreach (func_get_args() as $id) { if (filter_var($id, FILTER_VALIDATE_INT, $opts) === false) { return false; } } return true; } /** * @brief Show custom message for "not found" error (404). */ $app->notFound( function () { $response['message'] = 'REST route not found'; jsonResponse(404, $response); } ); /** * @brief Hook to write an error log message and a REST exit log message if debug is enabled. * @warning Error message will be written in web server's error file. * @warning REST message will be written in REST log file. */ $app->hook( 'slim.after', function () use ($app) { $max_body_len_to_log = 150; if ($app->response->getStatus() != 200) { // Compose error message (truncating long lines). $app->log->error(date(DATE_ISO8601) . ': ' . $app->getName() . ': ' . $_SERVER['REMOTE_ADDR'] . ": " . (isset($userid) ? $userid : "-") . ": " . $app->response->getStatus() . ': ' . $app->request->getMethod() . ' ' . $app->request->getPathInfo() . ': ' . substr($app->response->getBody(), 0, 100)); } if ($app->settings['debug']) { $body = $app->response->getBody(); if (strlen($body) > $max_body_len_to_log) { $body = substr($body, 0, $max_body_len_to_log) . '... (output has been truncated)'; } writeRestLog($body); } } ); // Common routes. /** * @brief Get general server information * @note Route: /info, Method: GET * @return string JSON object with basic server information (version, services, etc.) */ $app->get( '/info', function () { $hasOglive = false; $response = new \stdClass; // Reading version file. $data = json_decode(@file_get_contents(VERSION_FILE)); if (isset($data->project)) { $response = $data; } else { $response->project = 'OpenGnsys'; } // Getting actived services. @$services = parse_ini_file('/etc/default/opengnsys'); $response->services = []; if (@$services["RUN_OGADMSERVER"] === "yes") { array_push($response->services, "server"); $hasOglive = true; } if (@$services["RUN_OGADMREPO"] === "yes") array_push($response->services, "repository"); if (@$services["RUN_BTTRACKER"] === "yes") array_push($response->services, "tracker"); // Reading installed ogLive information file. if ($hasOglive === true) { $data = json_decode(@file_get_contents('/opt/opengnsys/etc/ogliveinfo.json')); if (isset($data->oglive)) { $response->oglive = $data->oglive; } } jsonResponse(200, $response); } ); function backupConfig() { $get_command = 'config-get'; $get_output = executeCurlCommand($get_command); if ($get_output == false || $get_output[0]["result"] != 0) { throw new Exception('Error al obtener la configuración actual de Kea DHCP'); } $config_text = json_encode($get_output[0]['arguments']); $configurationParsed = str_replace('\\', '', $config_text); $backup_dir = '/opt/opengnsys/etc/kea/backup'; if (!is_dir($backup_dir)) { throw new Exception('El directorio de backup no existe'); } if (!is_writable($backup_dir)) { throw new Exception('El directorio de backup no tiene permisos de escritura'); } $backup_files = glob($backup_dir . '/*.conf'); if (count($backup_files) >= 10) { usort($backup_files, function ($a, $b) { return filemtime($a) - filemtime($b); }); unlink($backup_files[0]); } $backup_file = $backup_dir . '/' . date('Y-m-d_H-i-s') . '.conf'; if (!file_put_contents($backup_file, $configurationParsed)) { throw new Exception('Error al guardar la configuración de backup'); } } function executeCurlCommand($command, $arguments = null, $create_backup = true) { $apiUrl = getenv('DHCP_IP'); if (!$apiUrl) { $apiUrl = 'http://localhost:8000/'; } if ($command == 'config-get' || $command == 'config-write') { $requestData = array( 'command' => $command, 'service' => array('dhcp4'), ); } elseif ($command == 'config-set' || $command == 'config-test') { $requestData = array( 'command' => $command, 'service' => array('dhcp4'), 'arguments' => $arguments, ); } else { return "Error: Comando no válido"; } if (($command == 'config-set' || $command == 'config-write') && $create_backup) { backupConfig(); } $jsonData = json_encode($requestData); try { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $apiUrl); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Expect:')); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_POST, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $output = json_decode($response, true); if ($httpCode >= 200 && $httpCode < 300) { return $output; } else { $error = curl_error($ch); throw new Exception($error); //return false; } } catch (Exception $e) { throw new Exception($e->getMessage()); } } /** * @brief Get configuration of kea dhcp server * @note Route: /dhcp, Method: GET * @return string JSON object with all kea configuration */ $app->get('/dhcp', function () { try { $response = executeCurlCommand('config-get'); if (!$response) { $responseError = 'Error: No se pudo acceder al archivo dhcpd.conf'; jsonResponse(400, $responseError); } else { $result_code = $response[0]["result"]; if ($result_code == 0) { $arrayDhcp4 = $response[0]['arguments']; jsonResponse(200, $arrayDhcp4, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } else { $responseError = "Error kea configuration invalid: " . $response[0]["text"]; jsonResponse(400, $responseError); } } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } }); /** * @brief Get hosts from kea configuration * @note Route: /dhcp/hosts, Method: GET * @return string JSON object with all hosts in kea configuration */ $app->get('/dhcp/hosts', function () { try { $response = executeCurlCommand('config-get'); if (!$response) { $responseError = 'Error: No se pudo acceder al archivo dhcpd.conf'; jsonResponse(400, $response); } else { $result_code = $response[0]["result"]; if ($result_code == 0) { if (!isset($response[0]['arguments']['Dhcp4']['reservations'])) { $responseError = 'El campo \'reservations\' no está inicializado'; jsonResponse(400, $responseError); } else { $arrayReservations = $response[0]['arguments']['Dhcp4']['reservations']; jsonResponse(200, $arrayReservations, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } } else { $responseError = "Error kea configuration invalid: " . $response[0]["text"]; jsonResponse(400, $responseError); } } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } }); /** * @brief Update kea configuration * @note Route: /dhcp/save, Method: POST * @param string configurationText * @return string JSON object with all hosts in kea configuration */ $app->post('/dhcp/save', function () use ($app) { try { $input = json_decode($app->request()->getBody()); $configurationText = htmlspecialchars($input->configurationText); } catch (Exception $e) { $response["message"] = $e->getMessage(); jsonResponse(400, $response); $app->stop(); } $configurationParsed = str_replace(' ', '', $configurationText); $configurationParsed = str_replace("\n", '', $configurationParsed); $configurationParsed = str_replace('"', '"', $configurationParsed); $configuration = json_decode($configurationParsed); if ($configuration === null || json_last_error() !== JSON_ERROR_NONE) { $responseError = "Error: El texto proporcionado no es un JSON válido."; jsonResponse(400, $responseError); } else { try { $responseTest = executeCurlCommand('config-test', $configuration); if ($responseTest[0]["result"] == 0) { $responseSet = executeCurlCommand('config-set', $configuration); if ($responseSet == false || $responseSet[0]["result"] != 0) { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"]; jsonResponse(400, $responseError); } else { $responseSuccess = "Configuración cargada correctamente"; jsonResponse(200, $responseSuccess); } } else { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseTest[0]["text"]; jsonResponse(400, $responseError); } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } } }); $app->post( '/dhcp/add', function () use ($app) { try { $input = json_decode($app->request()->getBody()); $host = htmlspecialchars($input->host); $macAddress = htmlspecialchars($input->macAddress); $address = htmlspecialchars($input->address); $nextServer = htmlspecialchars($input->nextServer); } catch (Exception $e) { $response["message"] = $e->getMessage(); jsonResponse(400, $response); $app->stop(); } try { $response = executeCurlCommand('config-get'); $nuevoHost = [ "hostname" => $host, "hw-address" => $macAddress, "ip-address" => $address, "next-server" => $nextServer ]; //En caso de que no esté declarado el array de reservations, lo inicializamos if (!isset($response[0]['arguments']['Dhcp4']['reservations'])) { $response[0]['arguments']['Dhcp4']['reservations'] = []; } $existe = False; $existe = array_reduce($response[0]['arguments']['Dhcp4']['reservations'], function ($existe, $reserva) use ($host) { return $existe || ($reserva['hostname'] === $host); }); if ($existe) { $response = "Error: El host con el hostname '$host' ya existe en las reservaciones."; jsonResponse(400, $response); } else { $response[0]['arguments']['Dhcp4']['reservations'][] = $nuevoHost; $array_encoded = json_encode($response[0]['arguments']); $configurationParsed = str_replace('\\', '', $array_encoded); $configuration = json_decode($configurationParsed); $responseTest = executeCurlCommand('config-test', $configuration); if ($responseTest[0]["result"] == 0) { $responseSet = executeCurlCommand('config-set', $configuration); if ($responseSet == false || $responseSet[0]["result"] != 0) { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"]; jsonResponse(400, $responseError); } else { $responseSuccess = "Configuración cargada correctamente"; jsonResponse(200, $responseSuccess); } } else { $responseError = "Error kea configuration invalid: " . $responseTest[0]["text"]; jsonResponse(400, $responseError); } } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } } ); $app->post( '/dhcp/delete', function () use ($app) { try { $input = json_decode($app->request()->getBody()); $host = htmlspecialchars($input->host); } catch (Exception $e) { $response["message"] = $e->getMessage(); jsonResponse(400, $response); $app->stop(); } try { $response = executeCurlCommand('config-get'); $existe = False; if (isset($response[0]['arguments']['Dhcp4']['reservations'])) { foreach ($response[0]['arguments']['Dhcp4']['reservations'] as $key => $reservation) { if (isset($reservation['hostname']) && $reservation['hostname'] === $host) { unset($response[0]['arguments']['Dhcp4']['reservations'][$key]); $existe = True; $response[0]['arguments']['Dhcp4']['reservations'] = array_values($response[0]['arguments']['Dhcp4']['reservations']); break; } } if ($existe) { $array_encoded = json_encode($response[0]['arguments']); $configurationParsed = str_replace('\\', '', $array_encoded); $configuration = json_decode($configurationParsed); $responseTest = executeCurlCommand('config-test', $configuration); if ($response[0]["result"] == 0) { $responseSet = executeCurlCommand('config-set', $configuration); if ($responseSet == false || $responseSet[0]["result"] != 0) { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"]; jsonResponse(400, $responseError); } else { $responseSuccess = "Configuración cargada correctamente"; jsonResponse(200, $responseSuccess); } } else { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseTest[0]["text"]; jsonResponse(400, $responseError); } } else { $responseError = "Error: El host con el hostname '$host' no existe en las reservaciones."; jsonResponse(400, $responseError); } } else { $responseError = 'El campo \'reservations\' no está inicializado'; jsonResponse(400, $responseError); } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } } ); $app->post( '/dhcp/update', function () use ($app) { try { $input = json_decode($app->request()->getBody()); $host = htmlspecialchars($input->host); $oldMacAddress = htmlspecialchars($input->oldMacAddress); $oldAddress = htmlspecialchars($input->oldAddress); $macAddress = htmlspecialchars($input->macAddress); $address = htmlspecialchars($input->address); $nextServer = htmlspecialchars($input->nextServer); } catch (Exception $e) { $response["message"] = $e->getMessage(); jsonResponse(400, $response); $app->stop(); } try { $response = executeCurlCommand('config-get'); $existe = False; if (isset($response[0]['arguments']['Dhcp4']['reservations'])) { foreach ($response[0]['arguments']['Dhcp4']['reservations'] as &$reservation) { if ($reservation['hw-address'] == $oldMacAddress && $reservation['ip-address'] == $oldAddress) { $existe = True; $reservation['hw-address'] = $macAddress; $reservation['ip-address'] = $address; $reservation['hostname'] = $host; $reservation['next-server'] = $nextServer; $array_encoded = json_encode($response[0]['arguments']); $configurationParsed = str_replace('\\', '', $array_encoded); $configuration = json_decode($configurationParsed); $responseTest = executeCurlCommand('config-test', $configuration); if ($responseTest[0]["result"] == 0) { $responseSet = executeCurlCommand('config-set', $configuration); if ($responseSet == false && $responseSet[0]["result"] != 0) { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"]; jsonResponse(400, $responseError); } else { $responseSuccess = "Configuración cargada correctamente"; jsonResponse(200, $responseSuccess); } } else { $responseError = "Error al guardar la configuración en Kea DHCP: " . $responseTest[0]["text"]; jsonResponse(400, $responseError); } } } if (!$existe) { $responseError = "Error: La IP " . $oldAddress . " y la MAC " . $oldMacAddress . " no existe en las reservaciones."; jsonResponse(400, $responseError); } } else { $responseError = 'El campo \'reservations\' no está inicializado'; jsonResponse(400, $responseError); } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } } ); $app->post( '/dhcp/apply', function () use ($app) { try { $response = executeCurlCommand('config-write'); if ($response[0]["result"] == 0) { jsonResponse(200, $response); } else { $responseError = "Error al escribir la configuración en Kea DHCP: " . $response[0]["text"]; jsonResponse(400, $responseError); } } catch (Exception $e) { $responseError = "Error al escribir la configuración en Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } } ); /** * @brief Restore kea configuration. * @note Route: /dhcp/backup, Method: POST * @return string Result of operation. * @note All modifications in kea configuration is stored"/opt/opengnsys/etc/kea/backup" directory. */ $app->post('/dhcp/backup', function () use ($app) { $backup_dir = '/opt/opengnsys/etc/kea/backup'; try { $backup_files = glob($backup_dir . '/*.conf'); if (empty($backup_files)) { $response = "No se encontraron archivos de backup"; jsonResponse(400, $response); } else { usort($backup_files, function ($a, $b) { return filemtime($b) - filemtime($a); }); $backup_file = reset($backup_files); $config = file_get_contents($backup_file); $configuration = json_decode($config); $test_command = 'config-test'; $test_output = executeCurlCommand($test_command, $configuration); if ($test_output == false || $test_output[0]["result"] != 0) { $responseError = "Error al comprobar la configuración de Kea: " . $test_output[0]["text"]; jsonResponse(400, $responseError); } else { $set_command = 'config-set'; $set_output = executeCurlCommand($set_command, $configuration, false); if ($set_output == false || $set_output[0]["result"] != 0) { $responseError = "Error al guardar la última configuración de Kea: " . $set_output[0]["text"]; jsonResponse(400, $responseError); } else { unlink($backup_file); $responseSuccess = "última configuración cargada correctamente"; jsonResponse(200, $responseSuccess); } } } } catch (Exception $e) { $responseError = "Error al restaurar la configuración: " . $e->getMessage(); jsonResponse(400, $responseError); } }); /** * @brief Upload kea configuration file. * @note Route: /dhcp/upload, Method: POST * @param File file. * @return string Route of stored file. */ $app->post('/dhcp/upload', function () use ($app) { if (!isset($_FILES['file_input_name'])) { $responseError = "No se ha subido ningún archivo"; jsonResponse(400, $responseError); return; } $file_path = $_FILES['file_input_name']['tmp_name']; $json_data = file_get_contents($file_path); $config_data = json_decode($json_data, true); if (json_last_error() !== JSON_ERROR_NONE) { $responseError = "El archivo JSON subido no está correctamente escrito"; jsonResponse(400, $responseError); } try { $test_command = 'config-test'; $test_output = executeCurlCommand($test_command, $config_data); if ($test_output == false || $test_output[0]["result"] != 0) { $responseError = "La configuración no es válida"; jsonResponse(400, $responseError); } else { $set_command = 'config-set'; $set_output = executeCurlCommand($set_command, $config_data); if ($test_output == false || $test_output[0]["result"] != 0) { $responseError = "Error al guardar la configuración en Kea DHCP"; jsonResponse(400, $responseError); http_response_code(400); } else { $responseSuccess = "Configuración cargada correctamente en el fichero: " . $file_path;; jsonResponse(200, $responseSuccess); http_response_code(200); } } } catch (Exception $e) { $responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage(); jsonResponse(400, $responseError); } }); /** * @brief Get the server status * @note Route: /status, Method: GET * @return string JSON object with all data collected from server status (RAM, %CPU, etc.). */ $app->get( '/status', function () { $response = []; // Getting memory and CPU information. exec("awk '$1~/Mem/ {print $2}' /proc/meminfo", $memInfo); $memInfo = array("total" => $memInfo[0], "used" => $memInfo[1]); $cpuInfo = exec("awk '$1==\"cpu\" {printf \"%.2f\",($2+$4)*100/($2+$4+$5)}' /proc/stat"); $cpuModel = exec("awk -F: '$1~/model name/ {print $2}' /proc/cpuinfo"); $response["memInfo"] = $memInfo; $response["cpu"] = array("model" => trim($cpuModel), "usage" => $cpuInfo); jsonResponse(200, $response); } );