|  | /*****************************************************************************\ | 
|  | *  slurmrestd.c - Slurm REST API daemon | 
|  | ***************************************************************************** | 
|  | *  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" | 
|  |  | 
|  | #define _GNU_SOURCE | 
|  |  | 
|  | #include <getopt.h> | 
|  | #include <grp.h> | 
|  | #include <limits.h> | 
|  | #include <netdb.h> | 
|  | #include <signal.h> | 
|  | #include <sys/socket.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #if HAVE_SYS_PRCTL_H | 
|  | #include <sys/prctl.h> | 
|  | #endif | 
|  |  | 
|  | #include "slurm/slurm.h" | 
|  |  | 
|  | #include "src/common/data.h" | 
|  | #include "src/common/fd.h" | 
|  | #include "src/common/log.h" | 
|  | #include "src/common/plugrack.h" | 
|  | #include "src/common/proc_args.h" | 
|  | #include "src/common/read_config.h" | 
|  | #include "src/common/ref.h" | 
|  | #include "src/common/run_in_daemon.h" | 
|  | #include "src/common/slurm_protocol_defs.h" | 
|  | #include "src/common/uid.h" | 
|  | #include "src/common/util-net.h" | 
|  | #include "src/common/xmalloc.h" | 
|  | #include "src/common/xstring.h" | 
|  |  | 
|  | #include "src/conmgr/conmgr.h" | 
|  |  | 
|  | #include "src/interfaces/accounting_storage.h" | 
|  | #include "src/interfaces/auth.h" | 
|  | #include "src/interfaces/cred.h" | 
|  | #include "src/interfaces/data_parser.h" | 
|  | #include "src/interfaces/hash.h" | 
|  | #include "src/interfaces/http_parser.h" | 
|  | #include "src/interfaces/select.h" | 
|  | #include "src/interfaces/serializer.h" | 
|  | #include "src/interfaces/tls.h" | 
|  | #include "src/interfaces/url_parser.h" | 
|  |  | 
|  | #include "src/slurmrestd/http.h" | 
|  | #include "src/slurmrestd/openapi.h" | 
|  | #include "src/slurmrestd/operations.h" | 
|  | #include "src/slurmrestd/rest_auth.h" | 
|  |  | 
|  | #define OPT_LONG_MAX_CON 0x100 | 
|  | #define OPT_LONG_AUTOCOMP 0x101 | 
|  | #define OPT_LONG_GEN_OAS 0x102 | 
|  |  | 
|  | #define SLURM_CONF_DISABLED "/dev/null" | 
|  |  | 
|  | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) | 
|  | #define unshare(_) (false) | 
|  | #endif | 
|  |  | 
|  | decl_static_data(usage_txt); | 
|  |  | 
|  | uint32_t slurm_daemon = IS_SLURMRESTD; | 
|  |  | 
|  | typedef struct { | 
|  | bool stdin_tty; /* running with a TTY for stdin */ | 
|  | bool stdin_socket; /* running with a socket for stdin */ | 
|  | bool stderr_tty; /* running with a TTY for stderr */ | 
|  | bool stdout_tty; /* running with a TTY for stdout */ | 
|  | bool stdout_socket; /* running with a socket for stdout */ | 
|  | bool listen; /* running in listening daemon mode aka not INET mode */ | 
|  | } run_mode_t; | 
|  |  | 
|  | /* Debug level to use */ | 
|  | static int debug_level = 0; | 
|  | static int debug_increase = 0; | 
|  | /* detected run mode */ | 
|  | static run_mode_t run_mode = { 0 }; | 
|  | /* Listen string */ | 
|  | static list_t *socket_listen = NULL; | 
|  | static char *slurm_conf_filename = NULL; | 
|  | /* Number of requested threads */ | 
|  | static int thread_count = 0; | 
|  | /* Max number of connections */ | 
|  | static int max_connections = 124; | 
|  | /* User to become once loaded */ | 
|  | static uid_t uid = 0; | 
|  | static gid_t gid = 0; | 
|  | static bool dump_spec_requested = false; | 
|  |  | 
|  | static char *rest_auth = NULL; | 
|  | static plugin_handle_t *auth_plugin_handles = NULL; | 
|  | static char **auth_plugin_types = NULL; | 
|  | static size_t auth_plugin_count = 0; | 
|  | static plugrack_t *auth_rack = NULL; | 
|  |  | 
|  | static char *oas_specs = NULL; | 
|  | static char *data_parser_plugins = NULL; | 
|  | static data_parser_t **parsers = NULL; | 
|  | static bool unshare_sysv = true; | 
|  | static bool unshare_files = true; | 
|  | static bool check_user = true; | 
|  | static bool become_user = false; | 
|  | static http_status_code_t *response_status_codes = NULL; | 
|  |  | 
|  | static void _plugrack_foreach_list(const char *full_type, const char *fq_path, | 
|  | const plugin_handle_t id, void *arg) | 
|  | { | 
|  | fprintf(stdout, "%s\n", full_type); | 
|  | } | 
|  |  | 
|  | /* SIGPIPE handler - mostly a no-op */ | 
|  | static void _sigpipe_handler(conmgr_callback_args_t conmgr_args, void *arg) | 
|  | { | 
|  | if (conmgr_args.status == CONMGR_WORK_STATUS_CANCELLED) | 
|  | return; | 
|  |  | 
|  | debug5("%s: received SIGPIPE", __func__); | 
|  | } | 
|  |  | 
|  | static void _set_max_connections(const char *buffer) | 
|  | { | 
|  | max_connections = slurm_atoul(buffer); | 
|  |  | 
|  | if (max_connections < 1) | 
|  | fatal("Invalid max connection count: %s", buffer); | 
|  |  | 
|  | debug3("%s: setting max_connections=%d", __func__, max_connections); | 
|  | } | 
|  |  | 
|  | static void _parse_env(void) | 
|  | { | 
|  | char *buffer = NULL; | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_DEBUG")) != NULL) { | 
|  | debug_level = log_string2num(buffer); | 
|  |  | 
|  | if ((debug_level < 0) || (debug_level == NO_VAL16)) | 
|  | fatal("Invalid env SLURMRESTD_DEBUG: %s", buffer); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_LISTEN")) != NULL) { | 
|  | /* split comma delimited list */ | 
|  | char *toklist = xstrdup(buffer); | 
|  | char *ptr1 = NULL, *ptr2 = NULL; | 
|  |  | 
|  | ptr1 = strtok_r(toklist, ",", &ptr2); | 
|  | while (ptr1) { | 
|  | list_append(socket_listen, xstrdup(ptr1)); | 
|  | ptr1 = strtok_r(NULL, ",", &ptr2); | 
|  | } | 
|  | xfree(toklist); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_AUTH_TYPES"))) { | 
|  | xfree(rest_auth); | 
|  | rest_auth = xstrdup(buffer); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_MAX_CONNECTIONS"))) | 
|  | _set_max_connections(buffer); | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_OPENAPI_PLUGINS")) != NULL) { | 
|  | xfree(oas_specs); | 
|  | oas_specs = xstrdup(buffer); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_DATA_PARSER_PLUGINS")) != NULL) { | 
|  | xfree(data_parser_plugins); | 
|  | data_parser_plugins = xstrdup(buffer); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_SECURITY"))) { | 
|  | char *token = NULL, *save_ptr = NULL; | 
|  | char *toklist = xstrdup(buffer); | 
|  |  | 
|  | token = strtok_r(toklist, ",", &save_ptr); | 
|  | while (token) { | 
|  | if (!xstrcasecmp(token, "disable_unshare_sysv")) { | 
|  | unshare_sysv = false; | 
|  | } else if (!xstrcasecmp(token, | 
|  | "disable_unshare_files")) { | 
|  | unshare_files = false; | 
|  | } else if (!xstrcasecmp(token, "disable_user_check")) { | 
|  | #ifdef NDEBUG | 
|  | fatal_abort("SLURMRESTD_SECURITY=disable_user_check should only be used for development. Disabling the user check to run slurmrestd as root or SlurmUser will allow anyone to run any command on the cluster as root."); | 
|  | #endif /* NDEBUG */ | 
|  | check_user = false; | 
|  | } else if (!xstrcasecmp(token, "become_user")) { | 
|  | become_user = true; | 
|  | } else { | 
|  | fatal("Unexpected value in SLURMRESTD_SECURITY=%s", | 
|  | token); | 
|  | } | 
|  | token = strtok_r(NULL, ",", &save_ptr); | 
|  | } | 
|  | xfree(toklist); | 
|  | } | 
|  |  | 
|  | if ((buffer = getenv("SLURMRESTD_RESPONSE_STATUS_CODES"))) { | 
|  | char *token = NULL, *save_ptr = NULL; | 
|  | char *toklist = xstrdup(buffer); | 
|  | int count = 0; | 
|  |  | 
|  | token = strtok_r(toklist, ",", &save_ptr); | 
|  | while (token) { | 
|  | http_status_code_t code = get_http_status_code(token); | 
|  |  | 
|  | if (code == HTTP_STATUS_NONE) | 
|  | fatal("Unable to parse %s as HTTP status code", | 
|  | token); | 
|  |  | 
|  | xrecalloc(response_status_codes, (count + 2), | 
|  | sizeof(*response_status_codes)); | 
|  |  | 
|  | response_status_codes[count] = code; | 
|  | count++; | 
|  |  | 
|  | token = strtok_r(NULL, ",", &save_ptr); | 
|  | } | 
|  | xfree(toklist); | 
|  |  | 
|  | if (response_status_codes) | 
|  | response_status_codes[count] = HTTP_STATUS_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _examine_stdin(void) | 
|  | { | 
|  | struct stat status = { 0 }; | 
|  |  | 
|  | if (fstat(STDIN_FILENO, &status)) | 
|  | fatal("unable to stat STDIN: %m"); | 
|  |  | 
|  | if ((status.st_mode & S_IFMT) == S_IFSOCK) | 
|  | run_mode.stdin_socket = true; | 
|  |  | 
|  | if (isatty(STDIN_FILENO)) | 
|  | run_mode.stdin_tty = true; | 
|  | } | 
|  |  | 
|  | static void _examine_stderr(void) | 
|  | { | 
|  | struct stat status = { 0 }; | 
|  |  | 
|  | if (fstat(STDERR_FILENO, &status)) | 
|  | fatal("unable to stat STDERR: %m"); | 
|  |  | 
|  | if (isatty(STDERR_FILENO)) | 
|  | run_mode.stderr_tty = true; | 
|  | } | 
|  |  | 
|  | static void _examine_stdout(void) | 
|  | { | 
|  | struct stat status = { 0 }; | 
|  |  | 
|  | if (fstat(STDOUT_FILENO, &status)) | 
|  | fatal("unable to stat STDOUT: %m"); | 
|  |  | 
|  | if ((status.st_mode & S_IFMT) == S_IFSOCK) | 
|  | run_mode.stdout_socket = true; | 
|  |  | 
|  | if (isatty(STDOUT_FILENO)) | 
|  | run_mode.stdout_tty = true; | 
|  | } | 
|  |  | 
|  | static void _setup_logging(int argc, char **argv) | 
|  | { | 
|  | /* Default to logging as a daemon */ | 
|  | log_options_t logopt = LOG_OPTS_INITIALIZER; | 
|  | log_facility_t fac = SYSLOG_FACILITY_DAEMON; | 
|  |  | 
|  | /* | 
|  | * Set debug level as requested. | 
|  | * debug_level is set to the value of SLURMRESTD_DEBUG. | 
|  | * SLURMRESTD_DEBUG sets the debug level if -v's are not given. | 
|  | * debug_increase is the command line option -v, which applies on top | 
|  | * of the default log level (info). | 
|  | */ | 
|  | if (debug_increase) | 
|  | debug_level = MIN((LOG_LEVEL_INFO + debug_increase), | 
|  | (LOG_LEVEL_END - 1)); | 
|  | else if (!debug_level) | 
|  | debug_level = LOG_LEVEL_INFO; | 
|  |  | 
|  | logopt.syslog_level = debug_level; | 
|  |  | 
|  | if (run_mode.stderr_tty) { | 
|  | /* Log to stderr if it is a tty */ | 
|  | logopt = (log_options_t) LOG_OPTS_STDERR_ONLY; | 
|  | fac = SYSLOG_FACILITY_USER; | 
|  | logopt.stderr_level = debug_level; | 
|  | } | 
|  |  | 
|  | if (log_init(xbasename(argv[0]), logopt, fac, NULL)) | 
|  | fatal("Unable to setup logging: %m"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * _usage - print a message describing the command line arguments of slurmrestd | 
|  | */ | 
|  | static void _usage(void) | 
|  | { | 
|  | char *txt; | 
|  | static_ref_to_cstring(txt, usage_txt); | 
|  | fprintf(stderr, "%s", txt); | 
|  | xfree(txt); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Load only required plugins to dump OpenAPI Specification to stdout | 
|  | */ | 
|  | __attribute__((noreturn)) | 
|  | static void dump_spec(int argc, char **argv) | 
|  | { | 
|  | const char *dump_mime_types[] = { MIME_TYPE_JSON, NULL }; | 
|  | int rc = SLURM_SUCCESS; | 
|  | data_t *spec = data_new(); | 
|  | char *output = NULL; | 
|  | size_t output_len = 0; | 
|  |  | 
|  | _setup_logging(argc, argv); | 
|  |  | 
|  | (void) is_spec_generation_only(true); | 
|  |  | 
|  | /* Load slurm.conf if possible and ignore if it fails */ | 
|  | if (!xstrcmp(slurm_conf_filename, SLURM_CONF_DISABLED)) { | 
|  | /* Avoid another part of Slurm from trying to load slurm.conf */ | 
|  | setenvfs("SLURM_CONF="SLURM_CONF_DISABLED); | 
|  | } else if (!xstrcmp(getenv("SLURM_CONF"), SLURM_CONF_DISABLED)) { | 
|  | ; /* Do not try to load slurm.conf */ | 
|  | } else if ((rc = slurm_conf_init(slurm_conf_filename))) { | 
|  | debug("Unable to load %s: %s", | 
|  | slurm_conf_filename, slurm_strerror(rc)); | 
|  | } | 
|  |  | 
|  | serializer_required(MIME_TYPE_JSON); | 
|  |  | 
|  | if (!(parsers = data_parser_g_new_array(NULL, NULL, NULL, NULL, NULL, | 
|  | NULL, NULL, NULL, | 
|  | data_parser_plugins, NULL, | 
|  | false))) | 
|  | fatal("Unable to initialize data_parser plugins"); | 
|  |  | 
|  | if ((rc = init_operations(parsers))) | 
|  | fatal("Unable to initialize operations structures: %s", | 
|  | slurm_strerror(rc)); | 
|  |  | 
|  | if (init_openapi(oas_specs, NULL, parsers, response_status_codes)) | 
|  | fatal("Unable to initialize OpenAPI structures"); | 
|  |  | 
|  | if ((rc = generate_spec(spec, dump_mime_types))) | 
|  | fatal("Unable to generate OpenAPI Specification: %s", | 
|  | slurm_strerror(rc)); | 
|  |  | 
|  | if ((rc = serialize_g_data_to_string(&output, &output_len, spec, | 
|  | MIME_TYPE_JSON, SER_FLAGS_PRETTY))) | 
|  | fatal("Unable to dump OpenAPI Specification: %s", | 
|  | slurm_strerror(rc)); | 
|  |  | 
|  | fprintf(stdout, "%s", output); | 
|  | fflush(stdout); | 
|  |  | 
|  | _exit(rc); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * _parse_commandline - parse and process any command line arguments | 
|  | * IN argc - number of command line arguments | 
|  | * IN argv - the command line arguments | 
|  | * IN/OUT conf_ptr - pointer to current configuration, update as needed | 
|  | */ | 
|  | static void _parse_commandline(int argc, char **argv) | 
|  | { | 
|  | static struct option long_options[] = { | 
|  | { "autocomplete", required_argument, NULL, OPT_LONG_AUTOCOMP }, | 
|  | { "help", no_argument, NULL, 'h' }, | 
|  | { "max-connections", required_argument, NULL, OPT_LONG_MAX_CON }, | 
|  | { "generate-openapi-spec", no_argument, NULL, OPT_LONG_GEN_OAS }, | 
|  | { NULL, required_argument, NULL, 'a' }, | 
|  | { NULL, required_argument, NULL, 'd' }, | 
|  | { NULL, required_argument, NULL, 'f' }, | 
|  | { NULL, required_argument, NULL, 'g' }, | 
|  | { NULL, no_argument, NULL, 'h' }, | 
|  | { NULL, required_argument, NULL, 's' }, | 
|  | { NULL, required_argument, NULL, 't' }, | 
|  | { NULL, required_argument, NULL, 'u' }, | 
|  | { NULL, no_argument, NULL, 'v' }, | 
|  | { NULL, no_argument, NULL, 'V' }, | 
|  | { NULL, 0, NULL, 0 } | 
|  | }; | 
|  | int c = 0, option_index = 0; | 
|  |  | 
|  | opterr = 0; | 
|  |  | 
|  | while ((c = getopt_long(argc, argv, "a:d:f:g:hs:t:u:vV", long_options, | 
|  | &option_index)) != -1) { | 
|  | switch (c) { | 
|  | case 'a': | 
|  | xfree(rest_auth); | 
|  | rest_auth = xstrdup(optarg); | 
|  | break; | 
|  | case 'd': | 
|  | if (!xstrcasecmp(optarg, "list")) { | 
|  | fprintf(stderr, "Possible data_parser plugins:\n"); | 
|  | parsers = data_parser_g_new_array( | 
|  | NULL, NULL, NULL, NULL, NULL, NULL, | 
|  | NULL, NULL, optarg, | 
|  | _plugrack_foreach_list, false); | 
|  | exit(SLURM_SUCCESS); | 
|  | } | 
|  |  | 
|  | xfree(data_parser_plugins); | 
|  | data_parser_plugins = xstrdup(optarg); | 
|  | break; | 
|  | case 'f': | 
|  | xfree(slurm_conf_filename); | 
|  | slurm_conf_filename = xstrdup(optarg); | 
|  | break; | 
|  | case 'g': | 
|  | if (gid_from_string(optarg, &gid)) | 
|  | fatal("Unable to resolve gid: %s", optarg); | 
|  | break; | 
|  | case 'h': | 
|  | _usage(); | 
|  | exit(0); | 
|  | break; | 
|  | case 's': | 
|  | xfree(oas_specs); | 
|  | oas_specs = xstrdup(optarg); | 
|  | break; | 
|  | case 't': | 
|  | thread_count = atoi(optarg); | 
|  | break; | 
|  | case 'u': | 
|  | if (uid_from_string(optarg, &uid)) | 
|  | fatal("Unable to resolve user: %s", optarg); | 
|  | break; | 
|  | case 'v': | 
|  | debug_increase++; | 
|  | break; | 
|  | case 'V': | 
|  | print_slurm_version(); | 
|  | exit(0); | 
|  | break; | 
|  | case OPT_LONG_MAX_CON: | 
|  | _set_max_connections(optarg); | 
|  | break; | 
|  | case OPT_LONG_AUTOCOMP: | 
|  | suggest_completion(long_options, optarg); | 
|  | exit(0); | 
|  | break; | 
|  | case OPT_LONG_GEN_OAS: | 
|  | dump_spec_requested = true; | 
|  | break; | 
|  | default: | 
|  | _usage(); | 
|  | exit(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | while (optind < argc) { | 
|  | list_append(socket_listen, xstrdup(argv[optind])); | 
|  | optind++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check for supplementary group that could result in an unintended privilege | 
|  | * escalation | 
|  | */ | 
|  | static void _check_gids(void) | 
|  | { | 
|  | gid_t *gids = NULL; | 
|  | bool need_drop = false; | 
|  | int gid_count = getgroups(0, NULL); | 
|  |  | 
|  | if (gid_count < 0) | 
|  | fatal("%s: getgroups(0, NULL) failed: %m", __func__); | 
|  |  | 
|  | if (!gid_count) | 
|  | return; | 
|  |  | 
|  | gids = xcalloc(gid_count, sizeof(*gids)); | 
|  |  | 
|  | if ((gid_count = getgroups(gid_count, gids)) < 0) | 
|  | fatal("%s: getgroups() failed: %m", __func__); | 
|  |  | 
|  | for (int i = 0; i < gid_count; i++) { | 
|  | /* | 
|  | * Ignore same gid being in supplementary groups | 
|  | * as it won't change permissions | 
|  | */ | 
|  | if (gids[i] == gid) | 
|  | continue; | 
|  |  | 
|  | need_drop = true; | 
|  | debug("%s: Supplementary group %d needs to be dropped", | 
|  | __func__, gids[i]); | 
|  | } | 
|  |  | 
|  | xfree(gids); | 
|  |  | 
|  | if (!need_drop) | 
|  | return; | 
|  |  | 
|  | debug("%s: Dropping all supplementary groups", __func__); | 
|  |  | 
|  | if (!setgroups(0, NULL)) | 
|  | return; | 
|  |  | 
|  | #ifdef __linux__ | 
|  | if (errno == EPERM) | 
|  | fatal("slurmrestd process lacks CAP_SETGID to drop supplementary groups. Supplementary groups must be removed from slurmrestd user (uid=%d,gid=%d) prior to starting slurmrestd.", | 
|  | uid, gid); | 
|  | #endif /* __linux__ */ | 
|  |  | 
|  | fatal("Unable to drop supplementary groups: %m"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * slurmrestd is merely a translator from REST to Slurm. | 
|  | * Try to lock down any extra unneeded permissions. | 
|  | */ | 
|  | static void _lock_down(void) | 
|  | { | 
|  | if ((getuid() == SLURM_AUTH_NOBODY) || (getgid() == SLURM_AUTH_NOBODY)) | 
|  | fatal("slurmrestd must not be run as nobody"); | 
|  |  | 
|  | #if HAVE_SYS_PRCTL_H | 
|  | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) | 
|  | fatal("Unable to disable new privileges: %m"); | 
|  | #endif | 
|  |  | 
|  | if (unshare_sysv && unshare(CLONE_SYSVSEM)) | 
|  | fatal("Unable to unshare System V namespace: %m"); | 
|  | if (unshare_files && unshare(CLONE_FILES)) | 
|  | fatal("Unable to unshare file descriptors: %m"); | 
|  |  | 
|  | if (uid != 0 && (gid == 0)) | 
|  | gid = gid_from_uid(uid); | 
|  | if (gid) | 
|  | _check_gids(); | 
|  | if (gid != 0 && setgid(gid)) | 
|  | fatal("Unable to setgid: %m"); | 
|  | if (uid != 0 && setuid(uid)) | 
|  | fatal("Unable to setuid: %m"); | 
|  |  | 
|  | if (become_user && getuid()) | 
|  | fatal("slurmrestd must run as root in become_user mode"); | 
|  |  | 
|  | if (become_user && getgid()) | 
|  | fatal("slurmrestd must run as root in become_user mode"); | 
|  |  | 
|  | #ifdef PR_SET_DUMPABLE | 
|  | if (prctl(PR_SET_DUMPABLE, 1) < 0) | 
|  | error("%s: Unable to set process as dumpable: %m", __func__); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check slurmrestd is not running as SlurmUser unless check_user is false. | 
|  | */ | 
|  | static void _check_user(void) | 
|  | { | 
|  | int gid_count = 0; | 
|  |  | 
|  | if (!check_user) | 
|  | return; | 
|  |  | 
|  | if (getuid() == SLURM_AUTH_NOBODY) | 
|  | fatal("slurmrestd should not be run as nobody(%d)", | 
|  | SLURM_AUTH_NOBODY); | 
|  | if (getgid() == SLURM_AUTH_NOBODY) | 
|  | fatal("slurmrestd should not be run with nobody(%d) group.", | 
|  | SLURM_AUTH_NOBODY); | 
|  |  | 
|  | if (slurm_conf.slurm_user_id == getuid()) | 
|  | fatal("slurmrestd should not be run as SlurmUser"); | 
|  | if (gid_from_uid(slurm_conf.slurm_user_id) == getgid()) | 
|  | fatal("slurmrestd should not be run with SlurmUser's group."); | 
|  |  | 
|  | if (!getuid() && !become_user) | 
|  | fatal("slurmrestd should not be run as the root user."); | 
|  | if (!getgid() && !become_user) | 
|  | fatal("slurmrestd should not be run with the root group."); | 
|  |  | 
|  | if ((gid_count = getgroups(0, NULL)) > 0) { | 
|  | gid_t *list = xcalloc(gid_count, sizeof(*list)); | 
|  |  | 
|  | if (getgroups(gid_count, list) != gid_count) | 
|  | fatal_abort("Inconsistent getgroups() group counts. This should never happen"); | 
|  |  | 
|  | for (int i = 0; i < gid_count; i++) { | 
|  | if (list[i] == slurm_conf.slurm_user_id) | 
|  | fatal("slurmrestd should not be run with SlurmUser's group."); | 
|  |  | 
|  | if (!list[i] && !become_user) | 
|  | fatal("slurmrestd should not be run with the root group."); | 
|  |  | 
|  | if (list[i] == SLURM_AUTH_NOBODY) | 
|  | fatal("slurmrestd should not be run with nobody(%d) group.", | 
|  | SLURM_AUTH_NOBODY); | 
|  | } | 
|  |  | 
|  | xfree(list); | 
|  | } else if (gid_count < 0) { | 
|  | fatal_abort("getgroups()=%d failed[%d]: %m", errno, gid_count); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* simple wrapper to hand over operations router in http context */ | 
|  | static void *_setup_http_context(conmgr_fd_t *con, void *arg) | 
|  | { | 
|  | xassert(operations_router == arg); | 
|  | return setup_http_context(con, operations_router); | 
|  | } | 
|  |  | 
|  | static void _auth_plugrack_foreach(const char *full_type, const char *fq_path, | 
|  | const plugin_handle_t id, void *arg) | 
|  | { | 
|  | auth_plugin_count += 1; | 
|  | xrecalloc(auth_plugin_handles, auth_plugin_count, | 
|  | sizeof(*auth_plugin_handles)); | 
|  | xrecalloc(auth_plugin_types, auth_plugin_count, | 
|  | sizeof(*auth_plugin_types)); | 
|  |  | 
|  | auth_plugin_types[auth_plugin_count - 1] = xstrdup(full_type); | 
|  | auth_plugin_handles[auth_plugin_count - 1] = id; | 
|  |  | 
|  | debug5("%s: auth plugin type:%s path:%s", | 
|  | __func__, full_type, fq_path); | 
|  | } | 
|  |  | 
|  | static void _on_signal_interrupt(conmgr_callback_args_t conmgr_args, void *arg) | 
|  | { | 
|  | if (conmgr_args.status == CONMGR_WORK_STATUS_CANCELLED) | 
|  | return; | 
|  |  | 
|  | info("%s: caught SIGINT. Shutting down.", __func__); | 
|  | conmgr_request_shutdown(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void _inet_on_finish(conmgr_fd_t *con, void *ctxt) | 
|  | { | 
|  | on_http_connection_finish(con, ctxt); | 
|  | conmgr_request_shutdown(); | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | int rc = SLURM_SUCCESS, parse_rc = SLURM_SUCCESS; | 
|  | socket_listen = list_create(xfree_ptr); | 
|  | static const conmgr_events_t conmgr_events = { | 
|  | .on_data = parse_http, | 
|  | .on_connection = _setup_http_context, | 
|  | .on_finish = on_http_connection_finish, | 
|  | .on_fingerprint = on_fingerprint_tls, | 
|  | }; | 
|  | static const conmgr_events_t inet_events = { | 
|  | .on_data = parse_http, | 
|  | .on_connection = _setup_http_context, | 
|  | .on_finish = _inet_on_finish, | 
|  | }; | 
|  |  | 
|  | _parse_env(); | 
|  | _parse_commandline(argc, argv); | 
|  |  | 
|  | if (dump_spec_requested) | 
|  | dump_spec(argc, argv); | 
|  |  | 
|  | /* attempt to release all unneeded permissions */ | 
|  | _lock_down(); | 
|  |  | 
|  | _examine_stdin(); | 
|  | _examine_stderr(); | 
|  | _examine_stdout(); | 
|  | _setup_logging(argc, argv); | 
|  |  | 
|  | run_mode.listen = !list_is_empty(socket_listen); | 
|  |  | 
|  | slurm_init(slurm_conf_filename); | 
|  | _check_user(); | 
|  |  | 
|  | /* Load serializers if they are present */ | 
|  | serializer_required(MIME_TYPE_JSON); | 
|  | if (getenv("SLURMRESTD_YAML")) | 
|  | serializer_required(MIME_TYPE_YAML); | 
|  | serializer_required(MIME_TYPE_URL_ENCODED); | 
|  |  | 
|  | if ((rc = http_parser_g_init())) | 
|  | fatal("Unable to load http_parser plugin: %s", | 
|  | slurm_strerror(rc)); | 
|  | if ((rc = url_parser_g_init())) | 
|  | fatal("Unable to load url_parser plugin: %s", | 
|  | slurm_strerror(rc)); | 
|  |  | 
|  | /* This checks if slurmrestd is running in inetd mode */ | 
|  | conmgr_init((run_mode.listen ? thread_count : CONMGR_THREAD_COUNT_MIN), | 
|  | max_connections); | 
|  |  | 
|  | /* | 
|  | * Attempt to load TLS plugin and then attempt to load the certificate | 
|  | * or give user warning TLS will not be supported | 
|  | */ | 
|  | if (!tls_g_init() && tls_available() && | 
|  | (rc = tls_g_load_own_cert(NULL, 0, NULL, 0))) { | 
|  | warning("Disabling TLS support due to failure loading TLS certificate: %s", | 
|  | slurm_strerror(rc)); | 
|  |  | 
|  | if ((rc = tls_g_fini())) | 
|  | fatal("Unable to unload TLS plugin: %s", | 
|  | slurm_strerror(rc)); | 
|  | } | 
|  |  | 
|  | conmgr_add_work_signal(SIGINT, _on_signal_interrupt, NULL); | 
|  | conmgr_add_work_signal(SIGPIPE, _sigpipe_handler, NULL); | 
|  |  | 
|  | auth_rack = plugrack_create("rest_auth"); | 
|  | plugrack_read_dir(auth_rack, slurm_conf.plugindir); | 
|  |  | 
|  | if (rest_auth && !xstrcasecmp(rest_auth, "list")) { | 
|  | fprintf(stderr, "Possible REST authentication plugins:\n"); | 
|  | plugrack_foreach(auth_rack, _plugrack_foreach_list, NULL); | 
|  | exit(0); | 
|  | } else if (rest_auth) { | 
|  | /* User provide which plugins they want */ | 
|  | char *type, *last = NULL; | 
|  |  | 
|  | type = strtok_r(rest_auth, ",", &last); | 
|  | while (type) { | 
|  | xstrtrim(type); | 
|  |  | 
|  | /* Permit both prefix and no-prefix for plugin names. */ | 
|  | if (xstrncmp(type, "rest_auth/", 10) == 0) | 
|  | type += 10; | 
|  | type = xstrdup_printf("rest_auth/%s", type); | 
|  | xstrtrim(type); | 
|  |  | 
|  | _auth_plugrack_foreach(type, NULL, | 
|  | PLUGIN_INVALID_HANDLE, NULL); | 
|  |  | 
|  | xfree(type); | 
|  | type = strtok_r(NULL, ",", &last); | 
|  | } | 
|  |  | 
|  | xfree(rest_auth); | 
|  | } else /* Add all possible */ | 
|  | plugrack_foreach(auth_rack, _auth_plugrack_foreach, NULL); | 
|  |  | 
|  | if (!auth_plugin_count) | 
|  | fatal("No authentication plugins to load."); | 
|  |  | 
|  | for (size_t i = 0; i < auth_plugin_count; i++) { | 
|  | if ((auth_plugin_handles[i] == PLUGIN_INVALID_HANDLE) && | 
|  | (auth_plugin_handles[i] = | 
|  | plugrack_use_by_type(auth_rack, auth_plugin_types[i])) == | 
|  | PLUGIN_INVALID_HANDLE) | 
|  | fatal("Unable to find plugin: %s", | 
|  | auth_plugin_types[i]); | 
|  | } | 
|  |  | 
|  | if (init_rest_auth(become_user, auth_plugin_handles, auth_plugin_count)) | 
|  | fatal("Unable to initialize rest authentication"); | 
|  |  | 
|  | if (!(parsers = data_parser_g_new_array(NULL, NULL, NULL, NULL, NULL, | 
|  | NULL, NULL, NULL, | 
|  | data_parser_plugins, NULL, | 
|  | false))) { | 
|  | fatal("Unable to initialize data_parser plugins"); | 
|  | } | 
|  | xfree(data_parser_plugins); | 
|  |  | 
|  | if (init_operations(parsers)) | 
|  | fatal("Unable to initialize operations structures"); | 
|  |  | 
|  | if (oas_specs && !xstrcasecmp(oas_specs, "list")) { | 
|  | fprintf(stderr, "Possible OpenAPI plugins:\n"); | 
|  | init_openapi(oas_specs, _plugrack_foreach_list, NULL, NULL); | 
|  | exit(0); | 
|  | } else if (init_openapi(oas_specs, NULL, parsers, | 
|  | response_status_codes)) | 
|  | fatal("Unable to initialize OpenAPI structures"); | 
|  |  | 
|  | xfree(oas_specs); | 
|  |  | 
|  | /* Sanity check modes */ | 
|  | if (run_mode.stdin_socket) { | 
|  | char *in = fd_resolve_path(STDIN_FILENO); | 
|  | char *out = fd_resolve_path(STDOUT_FILENO); | 
|  |  | 
|  | if (in && out && xstrcmp(in, out)) | 
|  | fatal("STDIN and STDOUT must be same socket"); | 
|  |  | 
|  | xfree(in); | 
|  | xfree(out); | 
|  | } | 
|  |  | 
|  | if (run_mode.stdin_tty) | 
|  | debug("Interactive mode activated (TTY detected on STDIN)"); | 
|  |  | 
|  | if (!run_mode.listen) { | 
|  | if ((rc = conmgr_process_fd(CON_TYPE_RAW, STDIN_FILENO, | 
|  | STDOUT_FILENO, &inet_events, | 
|  | CON_FLAG_NONE, NULL, 0, | 
|  | NULL, operations_router))) | 
|  | fatal("%s: unable to process stdin: %s", | 
|  | __func__, slurm_strerror(rc)); | 
|  |  | 
|  | /* fail on first error if this is piped process */ | 
|  | conmgr_set_exit_on_error(true); | 
|  | } else if (run_mode.listen) { | 
|  | mode_t mask = umask(0); | 
|  |  | 
|  | if (conmgr_create_listen_sockets(CON_TYPE_RAW, CON_FLAG_NONE, | 
|  | socket_listen, &conmgr_events, | 
|  | operations_router)) | 
|  | fatal("Unable to create sockets"); | 
|  |  | 
|  | umask(mask); | 
|  |  | 
|  | FREE_NULL_LIST(socket_listen); | 
|  | debug("%s: server listen mode activated", __func__); | 
|  | } | 
|  |  | 
|  | rc = conmgr_run(true); | 
|  |  | 
|  | /* | 
|  | * Capture if there were issues during parsing in inet mode. | 
|  | * Inet mode expects connection errors to propagate upwards as | 
|  | * connection errors so they can be logged appropriately. | 
|  | */ | 
|  | if (conmgr_get_exit_on_error()) | 
|  | parse_rc = conmgr_get_error(); | 
|  |  | 
|  | /* cleanup everything */ | 
|  | destroy_rest_auth(); | 
|  | destroy_operations(); | 
|  | destroy_openapi(); | 
|  | conmgr_fini(); | 
|  | FREE_NULL_DATA_PARSER_ARRAY(parsers, false); | 
|  | serializer_g_fini(); | 
|  | for (size_t i = 0; i < auth_plugin_count; i++) { | 
|  | plugrack_release_by_type(auth_rack, auth_plugin_types[i]); | 
|  | xfree(auth_plugin_types[i]); | 
|  | } | 
|  | xfree(auth_plugin_types); | 
|  | if ((rc = plugrack_destroy(auth_rack))) | 
|  | fatal_abort("unable to clean up plugrack: %s", | 
|  | slurm_strerror(rc)); | 
|  | auth_rack = NULL; | 
|  |  | 
|  | xfree(auth_plugin_handles); | 
|  | http_parser_g_fini(); | 
|  | url_parser_g_fini(); | 
|  | acct_storage_g_fini(); | 
|  | slurm_fini(); | 
|  | hash_g_fini(); | 
|  | conn_g_fini(); | 
|  | cred_g_fini(); | 
|  | auth_g_fini(); | 
|  | getnameinfo_cache_purge(); | 
|  | log_fini(); | 
|  |  | 
|  | /* send parsing RC if there were no higher level errors */ | 
|  | return (rc ? rc : parse_rc); | 
|  | } |