| /*****************************************************************************\ |
| * 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); |
| } |