diff --git a/daemons/execd/remoted_pidone.c b/daemons/execd/remoted_pidone.c index 57fce8fc69..321ac88a74 100644 --- a/daemons/execd/remoted_pidone.c +++ b/daemons/execd/remoted_pidone.c @@ -1,308 +1,301 @@ /* * Copyright 2017-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include "pacemaker-execd.h" static pid_t main_pid = 0; static void sigdone(void) { crm_exit(CRM_EX_OK); } static void sigreap(void) { pid_t pid = 0; int status; do { /* * Opinions seem to differ as to what to put here: * -1, any child process * 0, any child process whose process group ID is equal to that of the calling process */ pid = waitpid(-1, &status, WNOHANG); if (pid == main_pid) { /* Exit when pacemaker-remote exits and use the same return code */ if (WIFEXITED(status)) { crm_exit(WEXITSTATUS(status)); } crm_exit(CRM_EX_ERROR); } } while (pid > 0); } static struct { int sig; void (*handler)(void); } sigmap[] = { { SIGCHLD, sigreap }, { SIGINT, sigdone }, }; /*! * \internal * \brief Check a line of text for a valid environment variable name * - * \param[in] line Text to check - * \param[out] first First character of valid name if found, NULL otherwise - * \param[out] last Last character of valid name if found, NULL otherwise + * \param[in] line Text to check * - * \return TRUE if valid name found, FALSE otherwise + * \return Last character of valid name if found, or \c NULL otherwise * \note It's reasonable to impose limitations on environment variable names * beyond what C or setenv() does: We only allow names that contain only * [a-zA-Z0-9_] characters and do not start with a digit. */ -static bool -find_env_var_name(char *line, char **first, char **last) +static char * +find_env_var_name(char *line) { - // Skip leading whitespace - *first = line; - while (isspace(**first)) { - ++*first; + if (!isalpha(*line) && (*line != '_')) { + // Invalid first character + return NULL; } - - if (isalpha(**first) || (**first == '_')) { // Valid first character - *last = *first; - while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) { - ++*last; - } - return TRUE; - } - - *first = *last = NULL; - return FALSE; + for (line++; isalnum(*line) || (*line == '_'); line++); + return line; } #define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env" static void load_env_vars(void) { /* We haven't forked or initialized logging yet, so don't leave any file * descriptors open, and don't log -- silently ignore errors. */ FILE *fp = fopen(CONTAINER_ENV_FILE, "r"); char *line = NULL; size_t buf_size = 0; if (fp == NULL) { return; } while (getline(&line, &buf_size, fp) != -1) { char *name = NULL; char *end = NULL; char *value = NULL; char *quote = NULL; + // Strip leading whitespace + g_strchug(line); + // Look for valid name immediately followed by equals sign - if (!find_env_var_name(line, &name, &end) || (*++end != '=')) { + end = find_env_var_name(line); + if (*++end != '=') { goto cleanup_loop; } + name = line; // Null-terminate name, and advance beyond equals sign *end++ = '\0'; // Check whether value is quoted if ((*end == '\'') || (*end == '"')) { quote = end++; } value = end; if (quote != NULL) { /* Value is remaining characters up to next non-backslashed matching * quote character. */ while (((*end != *quote) || (*(end - 1) == '\\')) && (*end != '\0')) { end++; } if (*end == *quote) { // Null-terminate value, and advance beyond close quote *end++ = '\0'; } else { // Matching closing quote wasn't found value = NULL; } } else { /* Value is remaining characters up to next non-backslashed * whitespace. */ while ((!isspace(*end) || (*(end - 1) == '\\')) && (*end != '\0')) { end++; } // Do NOT null-terminate value (yet) } /* We have a valid name and value, and end is now the character after * the closing quote or the first whitespace after the unquoted value. * Make sure the rest of the line is just whitespace or a comment. */ if (value != NULL) { char *value_end = end; while (isspace(*end) && (*end != '\n')) { end++; } if ((*end == '\n') || (*end == '#')) { if (quote == NULL) { // Now we can null-terminate an unquoted value *value_end = '\0'; } // Don't overwrite (bundle options take precedence) setenv(name, value, 0); } else { value = NULL; } } cleanup_loop: errno = 0; } // getline() returns -1 on EOF or error if (errno != 0) { int rc = errno; crm_err("Error while reading environment variables from " CONTAINER_ENV_FILE ": %s", pcmk_rc_str(rc)); } fclose(fp); free(line); } void remoted_spawn_pidone(int argc, char **argv, char **envp) { sigset_t set; /* This environment variable exists for two purposes: * - For testing, setting it to "full" enables full PID 1 behavior even * when PID is not 1 * - Setting to "vars" enables just the loading of environment variables * from /etc/pacemaker/pcmk-init.env, which could be useful for testing or * containers with a custom PID 1 script that launches the remote * executor. */ const char *pid1 = PCMK_VALUE_DEFAULT; if (getpid() != 1) { pid1 = pcmk__env_option(PCMK__ENV_REMOTE_PID1); if (!pcmk__str_any_of(pid1, "full", "vars", NULL)) { // Default, unset, or invalid return; } } /* When a container is launched, it may be given specific environment * variables, which for Pacemaker bundles are given in the bundle * configuration. However, that does not allow for host-specific values. * To allow for that, look for a special file containing a shell-like syntax * of name/value pairs, and export those into the environment. */ load_env_vars(); if (strcmp(pid1, "vars") == 0) { return; } /* Containers can be expected to have /var/log, but they may not have * /var/log/pacemaker, so use a different default if no value has been * explicitly configured in the container's environment. */ if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) { pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log", true); } sigfillset(&set); sigprocmask(SIG_BLOCK, &set, 0); main_pid = fork(); switch (main_pid) { case 0: sigprocmask(SIG_UNBLOCK, &set, NULL); setsid(); setpgid(0, 0); // Child remains as pacemaker-remoted return; case -1: crm_err("fork failed: %s", pcmk_rc_str(errno)); } /* Parent becomes the reaper of zombie processes */ /* Safe to initialize logging now if needed */ # ifdef HAVE_PROGNAME /* Differentiate ourselves in the 'ps' output */ { char *p; int i, maxlen; char *LastArgv = NULL; const char *name = "pcmk-init"; for (i = 0; i < argc; i++) { if (!i || (LastArgv + 1 == argv[i])) LastArgv = argv[i] + strlen(argv[i]); } for (i = 0; envp[i] != NULL; i++) { if ((LastArgv + 1) == envp[i]) { LastArgv = envp[i] + strlen(envp[i]); } } maxlen = (LastArgv - argv[0]) - 2; i = strlen(name); /* We can overwrite individual argv[] arguments */ snprintf(argv[0], maxlen, "%s", name); /* Now zero out everything else */ p = &argv[0][i]; while (p < LastArgv) { *p++ = '\0'; } argv[1] = NULL; } # endif // HAVE_PROGNAME while (1) { int sig; size_t i; sigwait(&set, &sig); for (i = 0; i < PCMK__NELEM(sigmap); i++) { if (sigmap[i].sig == sig) { sigmap[i].handler(); break; } } } }