1 /*
2  * Copyright 2017-2020 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <stdlib.h>
15 #include <signal.h>
16 #include <unistd.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 
20 #include <crm/crm.h>
21 #include "pacemaker-execd.h"
22 
23 static pid_t main_pid = 0;
24 
25 static void
sigdone(void)26 sigdone(void)
27 {
28     exit(CRM_EX_OK);
29 }
30 
31 static void
sigreap(void)32 sigreap(void)
33 {
34     pid_t pid = 0;
35     int status;
36 
37     do {
38         /*
39          * Opinions seem to differ as to what to put here:
40          *  -1, any child process
41          *  0,  any child process whose process group ID is equal to that of the calling process
42          */
43         pid = waitpid(-1, &status, WNOHANG);
44         if (pid == main_pid) {
45             /* Exit when pacemaker-remote exits and use the same return code */
46             if (WIFEXITED(status)) {
47                 exit(WEXITSTATUS(status));
48             }
49             exit(CRM_EX_ERROR);
50         }
51     } while (pid > 0);
52 }
53 
54 static struct {
55     int sig;
56     void (*handler)(void);
57 } sigmap[] = {
58     { SIGCHLD, sigreap },
59     { SIGINT,  sigdone },
60 };
61 
62 /*!
63  * \internal
64  * \brief Check a line of text for a valid environment variable name
65  *
66  * \param[in]  line  Text to check
67  * \param[out] first  First character of valid name if found, NULL otherwise
68  * \param[out] last   Last character of valid name if found, NULL otherwise
69  *
70  * \return TRUE if valid name found, FALSE otherwise
71  * \note It's reasonable to impose limitations on environment variable names
72  *       beyond what C or setenv() does: We only allow names that contain only
73  *       [a-zA-Z0-9_] characters and do not start with a digit.
74  */
75 static bool
find_env_var_name(char * line,char ** first,char ** last)76 find_env_var_name(char *line, char **first, char **last)
77 {
78     // Skip leading whitespace
79     *first = line;
80     while (isspace(**first)) {
81         ++*first;
82     }
83 
84     if (isalpha(**first) || (**first == '_')) { // Valid first character
85         *last = *first;
86         while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) {
87             ++*last;
88         }
89         return TRUE;
90     }
91 
92     *first = *last = NULL;
93     return FALSE;
94 }
95 
96 static void
load_env_vars(const char * filename)97 load_env_vars(const char *filename)
98 {
99     /* We haven't forked or initialized logging yet, so don't leave any file
100      * descriptors open, and don't log -- silently ignore errors.
101      */
102     FILE *fp = fopen(filename, "r");
103 
104     if (fp != NULL) {
105         char line[LINE_MAX] = { '\0', };
106 
107         while (fgets(line, LINE_MAX, fp) != NULL) {
108             char *name = NULL;
109             char *end = NULL;
110             char *value = NULL;
111             char *quote = NULL;
112 
113             // Look for valid name immediately followed by equals sign
114             if (find_env_var_name(line, &name, &end) && (*++end == '=')) {
115 
116                 // Null-terminate name, and advance beyond equals sign
117                 *end++ = '\0';
118 
119                 // Check whether value is quoted
120                 if ((*end == '\'') || (*end == '"')) {
121                     quote = end++;
122                 }
123                 value = end;
124 
125                 if (quote) {
126                     /* Value is remaining characters up to next non-backslashed
127                      * matching quote character.
128                      */
129                     while (((*end != *quote) || (*(end - 1) == '\\'))
130                            && (*end != '\0')) {
131                         end++;
132                     }
133                     if (*end == *quote) {
134                         // Null-terminate value, and advance beyond close quote
135                         *end++ = '\0';
136                     } else {
137                         // Matching closing quote wasn't found
138                         value = NULL;
139                     }
140 
141                 } else {
142                     /* Value is remaining characters up to next non-backslashed
143                      * whitespace.
144                      */
145                     while ((!isspace(*end) || (*(end - 1) == '\\'))
146                            && (*end != '\0')) {
147                         ++end;
148                     }
149 
150                     if (end == (line + LINE_MAX - 1)) {
151                         // Line was too long
152                         value = NULL;
153                     }
154                     // Do NOT null-terminate value (yet)
155                 }
156 
157                 /* We have a valid name and value, and end is now the character
158                  * after the closing quote or the first whitespace after the
159                  * unquoted value. Make sure the rest of the line is just
160                  * whitespace or a comment.
161                  */
162                 if (value) {
163                     char *value_end = end;
164 
165                     while (isspace(*end) && (*end != '\n')) {
166                         ++end;
167                     }
168                     if ((*end == '\n') || (*end == '#')) {
169                         if (quote == NULL) {
170                             // Now we can null-terminate an unquoted value
171                             *value_end = '\0';
172                         }
173 
174                         // Don't overwrite (bundle options take precedence)
175                         setenv(name, value, 0);
176 
177                     } else {
178                         value = NULL;
179                     }
180                 }
181             }
182 
183             if ((value == NULL) && (strchr(line, '\n') == NULL)) {
184                 // Eat remainder of line beyond LINE_MAX
185                 if (fscanf(fp, "%*[^\n]\n") == EOF) {
186                     value = NULL; // Don't care, make compiler happy
187                 }
188             }
189         }
190         fclose(fp);
191     }
192 }
193 
194 void
remoted_spawn_pidone(int argc,char ** argv,char ** envp)195 remoted_spawn_pidone(int argc, char **argv, char **envp)
196 {
197     sigset_t set;
198 
199     /* This environment variable exists for two purposes:
200      * - For testing, setting it to "full" enables full PID 1 behavior even
201      *   when PID is not 1
202      * - Setting to "vars" enables just the loading of environment variables
203      *   from /etc/pacemaker/pcmk-init.env, which could be useful for testing or
204      *   containers with a custom PID 1 script that launches pacemaker-remoted.
205      */
206     const char *pid1 = (getpid() == 1)? "full" : getenv("PCMK_remote_pid1");
207 
208     if (pid1 == NULL) {
209         return;
210     }
211 
212     /* When a container is launched, it may be given specific environment
213      * variables, which for Pacemaker bundles are given in the bundle
214      * configuration. However, that does not allow for host-specific values.
215      * To allow for that, look for a special file containing a shell-like syntax
216      * of name/value pairs, and export those into the environment.
217      */
218     load_env_vars("/etc/pacemaker/pcmk-init.env");
219 
220     if (strcmp(pid1, "full")) {
221         return;
222     }
223 
224     /* Containers can be expected to have /var/log, but they may not have
225      * /var/log/pacemaker, so use a different default if no value has been
226      * explicitly configured in the container's environment.
227      */
228     if (pcmk__env_option("logfile") == NULL) {
229         pcmk__set_env_option("logfile", "/var/log/pcmk-init.log");
230     }
231 
232     sigfillset(&set);
233     sigprocmask(SIG_BLOCK, &set, 0);
234 
235     main_pid = fork();
236     switch (main_pid) {
237         case 0:
238             sigprocmask(SIG_UNBLOCK, &set, NULL);
239             setsid();
240             setpgid(0, 0);
241 
242             // Child remains as pacemaker-remoted
243             return;
244         case -1:
245             perror("fork");
246     }
247 
248     /* Parent becomes the reaper of zombie processes */
249     /* Safe to initialize logging now if needed */
250 
251 #  ifdef HAVE___PROGNAME
252     /* Differentiate ourselves in the 'ps' output */
253     {
254         char *p;
255         int i, maxlen;
256         char *LastArgv = NULL;
257         const char *name = "pcmk-init";
258 
259         for (i = 0; i < argc; i++) {
260             if (!i || (LastArgv + 1 == argv[i]))
261                 LastArgv = argv[i] + strlen(argv[i]);
262         }
263 
264         for (i = 0; envp[i] != NULL; i++) {
265             if ((LastArgv + 1) == envp[i]) {
266                 LastArgv = envp[i] + strlen(envp[i]);
267             }
268         }
269 
270         maxlen = (LastArgv - argv[0]) - 2;
271 
272         i = strlen(name);
273 
274         /* We can overwrite individual argv[] arguments */
275         snprintf(argv[0], maxlen, "%s", name);
276 
277         /* Now zero out everything else */
278         p = &argv[0][i];
279         while (p < LastArgv) {
280             *p++ = '\0';
281         }
282         argv[1] = NULL;
283     }
284 #  endif // HAVE___PROGNAME
285 
286     while (1) {
287         int sig;
288         size_t i;
289 
290         sigwait(&set, &sig);
291         for (i = 0; i < PCMK__NELEM(sigmap); i++) {
292             if (sigmap[i].sig == sig) {
293                 sigmap[i].handler();
294                 break;
295             }
296         }
297     }
298 }
299