|  | /*****************************************************************************\ | 
|  | *  http.c - handling HTTP | 
|  | ***************************************************************************** | 
|  | *  Copyright (C) SchedMD LLC. | 
|  | * | 
|  | *  This file is part of Slurm, a resource management program. | 
|  | *  For details, see <https://slurm.schedmd.com/>. | 
|  | *  Please also read the included file: DISCLAIMER. | 
|  | * | 
|  | *  Slurm is free software; you can redistribute it and/or modify it under | 
|  | *  the terms of the GNU General Public License as published by the Free | 
|  | *  Software Foundation; either version 2 of the License, or (at your option) | 
|  | *  any later version. | 
|  | * | 
|  | *  In addition, as a special exception, the copyright holders give permission | 
|  | *  to link the code of portions of this program with the OpenSSL library under | 
|  | *  certain conditions as described in each individual source file, and | 
|  | *  distribute linked combinations including the two. You must obey the GNU | 
|  | *  General Public License in all respects for all of the code used other than | 
|  | *  OpenSSL. If you modify file(s) with this exception, you may extend this | 
|  | *  exception to your version of the file(s), but you are not obligated to do | 
|  | *  so. If you do not wish to do so, delete this exception statement from your | 
|  | *  version.  If you delete this exception statement from all source files in | 
|  | *  the program, then also delete it here. | 
|  | * | 
|  | *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY | 
|  | *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|  | *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more | 
|  | *  details. | 
|  | * | 
|  | *  You should have received a copy of the GNU General Public License along | 
|  | *  with Slurm; if not, write to the Free Software Foundation, Inc., | 
|  | *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA. | 
|  | \*****************************************************************************/ | 
|  |  | 
|  | #include "config.h" | 
|  |  | 
|  | #include <ctype.h> | 
|  | #include <limits.h> | 
|  | #include <sys/socket.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include "slurm/slurm.h" | 
|  |  | 
|  | #include "src/common/http.h" | 
|  | #include "src/common/read_config.h" | 
|  | #include "src/common/slurm_protocol_api.h" | 
|  | #include "src/common/xmalloc.h" | 
|  | #include "src/common/xstring.h" | 
|  |  | 
|  | typedef struct { | 
|  | http_status_code_t code; | 
|  | char *text; | 
|  | } http_status_code_txt_t; | 
|  |  | 
|  | static const http_status_code_txt_t http_status_codes[] = { | 
|  | { HTTP_STATUS_CODE_CONTINUE, "CONTINUE" }, | 
|  | { HTTP_STATUS_CODE_SWITCH_PROTOCOLS, "SWITCH PROTOCOLS" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_OK, "OK" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_CREATED, "CREATED" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_ACCEPTED, "ACCEPTED" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_NON_AUTHORITATIVE, | 
|  | "OK (NON AUTHORITATIVE)" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_NO_CONTENT, "NO CONTENT" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_RESET_CONNECTION, "RESET CONNECTION" }, | 
|  | { HTTP_STATUS_CODE_SUCCESS_PARTIAL_CONTENT, "PARTIAL CONTENT" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_MULTIPLE_CHOICES, | 
|  | "REDIRECT MULTIPLE CHOICES" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_MOVED_PERMANENTLY, "MOVED PERMANENTLY" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_FOUND, "REDIRECT FOUND" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_SEE_OTHER, "REDIRECT SEE OTHER" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_NOT_MODIFIED, "NOT MODIFIED" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_USE_PROXY, "USE PROXY" }, | 
|  | { HTTP_STATUS_CODE_REDIRECT_TEMP_REDIRCT, "TEMP REDIRECT" }, | 
|  | { HTTP_STATUS_CODE_ERROR_BAD_REQUEST, "BAD REQUEST" }, | 
|  | { HTTP_STATUS_CODE_ERROR_UNAUTHORIZED, "UNAUTHORIZED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_PAYMENT_REQUIRED, "PAYMENT REQUIRED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_FORBIDDEN, "FORBIDDEN" }, | 
|  | { HTTP_STATUS_CODE_ERROR_NOT_FOUND, "NOT FOUND" }, | 
|  | { HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED, "NOT ALLOWED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE, "NOT ACCEPTABLE" }, | 
|  | { HTTP_STATUS_CODE_ERROR_PROXY_AUTH_REQ, | 
|  | "PROXY AUTHENTICATION REQUIRED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_REQUEST_TIMEOUT, "REQUEST TIMEOUT" }, | 
|  | { HTTP_STATUS_CODE_ERROR_CONFLICT, "CONFLICT" }, | 
|  | { HTTP_STATUS_CODE_ERROR_GONE, "GONE" }, | 
|  | { HTTP_STATUS_CODE_ERROR_LENGTH_REQUIRED, "LENGTH REQUIRED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_PRECONDITION_FAILED, "PRECONDITION FAILED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE, "ENTITY TOO LARGE" }, | 
|  | { HTTP_STATUS_CODE_ERROR_URI_TOO_LONG, "URI TOO LONG" }, | 
|  | { HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE, | 
|  | "UNSUPPORTED MEDIA TYPE" }, | 
|  | { HTTP_STATUS_CODE_ERROR_REQUEST_RANGE_UNSATISFIABLE, | 
|  | "REQUEST RANGE UNJUSTIFIABLE" }, | 
|  | { HTTP_STATUS_CODE_ERROR_EXPECTATION_FAILED, "EXPECTATION FAILED" }, | 
|  | { HTTP_STATUS_CODE_ERROR_IM_A_TEAPOT, "I'm a Teapot" }, /* rfc7168 */ | 
|  | { HTTP_STATUS_CODE_ERROR_MISDIRECT_REQUESTED, | 
|  | "MISDIRECTED REQUEST" }, /* rfc9110 15.5.20 */ | 
|  | { HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT, | 
|  | "UNPROCESSABLE CONTENT" }, /* rfc9110 15.5.21 */ | 
|  | { HTTP_STATUS_CODE_ERROR_UPGRADE_REQUIRED, | 
|  | "UPGRADE REQUIRED" }, /* rfc7231 6.5.15 */ | 
|  | { HTTP_STATUS_CODE_SRVERR_INTERNAL, "INTERNAL ERROR" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_NOT_IMPLEMENTED, "NOT IMPLEMENTED" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY, "BAD GATEWAY" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_SERVICE_UNAVAILABLE, "SERVICE UNAVAILABLE" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_GATEWAY_TIMEOUT, "GATEWAY TIMEOUT" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_HTTP_VERSION_NOT_SUPPORTED, | 
|  | "HTTP VERSION NOT SUPPORTED" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_VARIANT_ALSO_NEGOTIATES, | 
|  | "Variant Also Negotiates" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_INSUFFICENT_STORAGE, "Insufficient Storage" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_LOOP_DETECTED, "Loop Detected" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_NOT_EXTENDED, "Not Extended" }, | 
|  | { HTTP_STATUS_CODE_SRVERR_NETWORK_AUTH_REQ, | 
|  | "Network Authentication Required" }, | 
|  | { HTTP_STATUS_CODE_DEFAULT, "default" }, | 
|  | }; | 
|  |  | 
|  | static const struct { | 
|  | http_request_method_t method; | 
|  | const char *uc_text; | 
|  | const char *lc_text; | 
|  | } method_strings[] = { | 
|  | { HTTP_REQUEST_GET, "GET", "get" }, | 
|  | { HTTP_REQUEST_POST, "POST", "post" }, | 
|  | { HTTP_REQUEST_PUT, "PUT", "put" }, | 
|  | { HTTP_REQUEST_DELETE, "DELETE", "delete" }, | 
|  | { HTTP_REQUEST_OPTIONS, "OPTIONS", "options" }, | 
|  | { HTTP_REQUEST_HEAD, "HEAD", "head" }, | 
|  | { HTTP_REQUEST_PATCH, "PATCH", "patch" }, | 
|  | { HTTP_REQUEST_TRACE, "TRACE", "trace" }, | 
|  | }; | 
|  |  | 
|  | #define T(tscheme, str) \ | 
|  | { \ | 
|  | .scheme = tscheme, \ | 
|  | .string = str, \ | 
|  | .bytes = (sizeof(str) - 1), \ | 
|  | } | 
|  | static const struct { | 
|  | url_scheme_t scheme; | 
|  | const char *string; | 
|  | size_t bytes; | 
|  | } schemes[] = { | 
|  | T(URL_SCHEME_INVALID, "INVALID"), | 
|  | T(URL_SCHEME_HTTP, "http"), | 
|  | T(URL_SCHEME_HTTPS, "https"), | 
|  | T(URL_SCHEME_UNIX, "unix"), | 
|  | T(URL_SCHEME_INVALID_MAX, "INVALID_MAX"), | 
|  | }; | 
|  | #undef T | 
|  |  | 
|  | extern int url_get_scheme(const char *str, size_t bytes, | 
|  | url_scheme_t *scheme_ptr) | 
|  | { | 
|  | if (!str || !str[0] || !bytes) { | 
|  | *scheme_ptr = URL_SCHEME_INVALID; | 
|  | return ESLURM_URL_EMPTY; | 
|  | } | 
|  |  | 
|  | for (int i = (URL_SCHEME_INVALID + 1); i < URL_SCHEME_INVALID_MAX; | 
|  | i++) { | 
|  | if (bytes != schemes[i].bytes) | 
|  | continue; | 
|  |  | 
|  | if (xstrncasecmp(schemes[i].string, str, bytes)) | 
|  | continue; | 
|  |  | 
|  | *scheme_ptr = schemes[i].scheme; | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | *scheme_ptr = URL_SCHEME_INVALID; | 
|  | return ESLURM_URL_UNKNOWN_SCHEME; | 
|  | } | 
|  |  | 
|  | extern const char *url_get_scheme_string(const url_scheme_t scheme) | 
|  | { | 
|  | xassert(scheme >= URL_SCHEME_INVALID); | 
|  | xassert(scheme < URL_SCHEME_INVALID_MAX); | 
|  |  | 
|  | if (scheme == URL_SCHEME_INVALID) | 
|  | return NULL; | 
|  |  | 
|  | for (int i = (URL_SCHEME_INVALID + 1); i < URL_SCHEME_INVALID_MAX; | 
|  | i++) { | 
|  | if (scheme == schemes[i].scheme) | 
|  | return schemes[i].string; | 
|  | } | 
|  |  | 
|  | fatal_abort("should never happen"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * chars that can pass without decoding. | 
|  | * rfc3986: unreserved characters. | 
|  | */ | 
|  | static bool _is_valid_url_char(char buffer) | 
|  | { | 
|  | return (isxdigit(buffer) || isalpha(buffer) || buffer == '~' || | 
|  | buffer == '-' || buffer == '.' || buffer == '_'); | 
|  | } | 
|  |  | 
|  | extern unsigned char url_decode_escape_seq(const char *ptr) | 
|  | { | 
|  | if (isxdigit(*(ptr + 1)) && isxdigit(*(ptr + 2))) { | 
|  | /* using uint16_t char to catch any overflows */ | 
|  | uint16_t high = *(ptr + 1); | 
|  | uint16_t low = *(ptr + 2); | 
|  | uint16_t decoded = ((slurm_char_to_hex(high) << 4) + | 
|  | slurm_char_to_hex(low)); | 
|  |  | 
|  | //TODO: find more invalid characters? | 
|  | if (decoded == '\0') { | 
|  | log_flag(DATA, "%s: invalid URL escape sequence for 0x00", | 
|  | __func__); | 
|  | return '\0'; | 
|  | } else if (decoded >= 0xff) { | 
|  | log_flag(DATA, "%s: invalid URL escape sequence for 0x%02" PRIx16, | 
|  | __func__, decoded); | 
|  | return '\0'; | 
|  | } | 
|  |  | 
|  | log_flag(DATA, "%s: URL decoded: 0x%c%c -> %c (0x%02" PRIx16 ")", | 
|  | __func__, (unsigned char) high, (unsigned char) low, | 
|  | (unsigned char) decoded, decoded); | 
|  |  | 
|  | return (unsigned char) decoded; | 
|  | } else { | 
|  | log_flag_hex(DATA, ptr, strnlen(ptr, 3), | 
|  | "%s: invalid URL escape sequence: %s", __func__, | 
|  | ptr); | 
|  | return '\0'; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int _add_path(data_t *d, char **buffer, bool convert_types) | 
|  | { | 
|  | if (!xstrcasecmp(*buffer, ".")) { | 
|  | debug5("%s: ignoring path . entry", __func__); | 
|  | } else if (!xstrcasecmp(*buffer, "..")) { | 
|  | //TODO: pop last directory off sequence instead of fail | 
|  | debug5("%s: rejecting path .. entry", __func__); | 
|  | return SLURM_ERROR; | 
|  | } else { | 
|  | data_t *c = data_list_append(d); | 
|  | data_set_string(c, *buffer); | 
|  |  | 
|  | if (convert_types) | 
|  | (void) data_convert_type(c, DATA_TYPE_NONE); | 
|  |  | 
|  | xfree(*buffer); | 
|  | } | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern data_t *parse_url_path(const char *path, bool convert_types, | 
|  | bool allow_templates) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  | data_t *d = data_set_list(data_new()); | 
|  | char *buffer = NULL; | 
|  |  | 
|  | /* extract each word */ | 
|  | for (const char *ptr = path; !rc && *ptr != '\0'; ++ptr) { | 
|  | if (_is_valid_url_char(*ptr)) { | 
|  | xstrcatchar(buffer, *ptr); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | switch (*ptr) { | 
|  | case '{': /* OASv3.0.3 section 4.7.8.2 template variable */ | 
|  | if (!allow_templates) { | 
|  | debug("%s: unexpected OAS template character: %c", | 
|  | __func__, *ptr); | 
|  | rc = SLURM_ERROR; | 
|  | break; | 
|  | } else { | 
|  | /* find end of template */ | 
|  | char *end = xstrstr(ptr, "}"); | 
|  |  | 
|  | if (!end) { | 
|  | debug("%s: missing terminated OAS template character: }", | 
|  | __func__); | 
|  | rc = SLURM_ERROR; | 
|  | break; | 
|  | } | 
|  |  | 
|  | xstrncat(buffer, ptr, (end - ptr + 1)); | 
|  | ptr = end; | 
|  | break; | 
|  | } | 
|  | case '%': /* rfc3986 */ | 
|  | { | 
|  | const char c = url_decode_escape_seq(ptr); | 
|  | if (c != '\0') { | 
|  | /* shift past the hex value */ | 
|  | ptr += 2; | 
|  |  | 
|  | xstrcatchar(buffer, c); | 
|  | } else { | 
|  | debug("%s: invalid URL escape sequence: %s", | 
|  | __func__, ptr); | 
|  | rc = SLURM_ERROR; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  | case '/': /* rfc3986 */ | 
|  | if (buffer != NULL) | 
|  | rc = _add_path(d, &buffer, convert_types); | 
|  | break; | 
|  | default: | 
|  | debug("%s: unexpected URL character: %c", | 
|  | __func__, *ptr); | 
|  | rc = SLURM_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* last part of path */ | 
|  | if (!rc && buffer != NULL) | 
|  | rc = _add_path(d, &buffer, convert_types); | 
|  |  | 
|  | if (rc) { | 
|  | FREE_NULL_DATA(d); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return d; | 
|  | } | 
|  |  | 
|  | extern http_status_code_t get_http_status_code(const char *str) | 
|  | { | 
|  | if (isdigit(str[0])) { | 
|  | uint64_t n = slurm_atoul(str); | 
|  |  | 
|  | if (!n || (n > HTTP_STATUS_CODE_DEFAULT)) | 
|  | return HTTP_STATUS_NONE; | 
|  |  | 
|  | return n; | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < ARRAY_SIZE(http_status_codes); i++) | 
|  | if (!xstrcasecmp(http_status_codes[i].text, str)) | 
|  | return http_status_codes[i].code; | 
|  |  | 
|  | return HTTP_STATUS_NONE; | 
|  | } | 
|  |  | 
|  | extern const char *get_http_status_code_string(http_status_code_t code) | 
|  | { | 
|  | for (int i = 0; i < ARRAY_SIZE(http_status_codes); i++) | 
|  | if (http_status_codes[i].code == code) | 
|  | return http_status_codes[i].text; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | extern const char *get_http_method_string(http_request_method_t method) | 
|  | { | 
|  | for (int i = 0; i < ARRAY_SIZE(method_strings); i++) | 
|  | if (method_strings[i].method == method) | 
|  | return method_strings[i].uc_text; | 
|  |  | 
|  | return "INVALID"; | 
|  | } | 
|  |  | 
|  | extern const char *get_http_method_string_lc(http_request_method_t method) | 
|  | { | 
|  | for (int i = 0; i < ARRAY_SIZE(method_strings); i++) | 
|  | if (method_strings[i].method == method) | 
|  | return method_strings[i].lc_text; | 
|  |  | 
|  | return "INVALID"; | 
|  | } | 
|  |  | 
|  | extern http_request_method_t get_http_method(const char *str) | 
|  | { | 
|  | if (!str || !str[0]) | 
|  | return HTTP_REQUEST_INVALID; | 
|  |  | 
|  | for (int i = 0; i < ARRAY_SIZE(method_strings); i++) | 
|  | if (!xstrcasecmp(method_strings[i].lc_text, str)) | 
|  | return method_strings[i].method; | 
|  |  | 
|  | return HTTP_REQUEST_INVALID; | 
|  | } | 
|  |  | 
|  | extern void url_free_members(url_t *url) | 
|  | { | 
|  | url->scheme = URL_SCHEME_INVALID; | 
|  | xfree(url->host); | 
|  | xfree(url->port); | 
|  | xfree(url->user); | 
|  | xfree(url->path); | 
|  | xfree(url->query); | 
|  | xfree(url->fragment); | 
|  | } | 
|  |  | 
|  | extern void url_copy_members(url_t *dst, const url_t *src) | 
|  | { | 
|  | url_free_members(dst); | 
|  |  | 
|  | dst->scheme = src->scheme; | 
|  | dst->host = xstrdup(src->host); | 
|  | dst->port = xstrdup(src->port); | 
|  | dst->user = xstrdup(src->user); | 
|  | dst->path = xstrdup(src->path); | 
|  | dst->query = xstrdup(src->query); | 
|  | dst->fragment = xstrdup(src->fragment); | 
|  | } |