1 /*
2  * Master Worker - program
3  *
4  * Copyright HAProxy Technologies - William Lallemand <wlallemand@haproxy.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  *
11  */
12 
13 #define _GNU_SOURCE
14 
15 #include <sys/types.h>
16 #include <errno.h>
17 #include <grp.h>
18 #include <pwd.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include <common/cfgparse.h>
24 #include <common/errors.h>
25 #include <common/initcall.h>
26 
27 #include <proto/log.h>
28 #include <proto/mworker.h>
29 
30 static int use_program = 0; /* do we use the program section ? */
31 
32 /*
33  * Launch every programs
34  */
mworker_ext_launch_all()35 int mworker_ext_launch_all()
36 {
37 	int ret;
38 	struct mworker_proc *child;
39 	struct mworker_proc *tmp;
40 	int reexec = 0;
41 
42 	if (!use_program)
43 		return 0;
44 
45 	reexec = getenv("HAPROXY_MWORKER_REEXEC") ? 1 : 0;
46 
47 	/* find the right mworker_proc */
48 	list_for_each_entry_safe(child, tmp, &proc_list, list) {
49 		if (child->reloads == 0 && (child->options & PROC_O_TYPE_PROG)) {
50 
51 			if (reexec && (!(child->options & PROC_O_START_RELOAD))) {
52 				struct mworker_proc *old_child;
53 
54 				/*
55 				 * This is a reload and we don't want to fork a
56 				 * new program so have to remove the entry in
57 				 * the list.
58 				 *
59 				 * But before that, we need to mark the
60 				 * previous program as not leaving, if we find one.
61 				 */
62 
63 				list_for_each_entry(old_child, &proc_list, list) {
64 					if (!(old_child->options & PROC_O_TYPE_PROG) || (!(old_child->options & PROC_O_LEAVING)))
65 						continue;
66 
67 					if (!strcmp(old_child->id, child->id))
68 						old_child->options &= ~PROC_O_LEAVING;
69 				}
70 
71 
72 				LIST_DEL(&child->list);
73 				mworker_free_child(child);
74 				child = NULL;
75 
76 				continue;
77 			}
78 
79 			child->timestamp = now.tv_sec;
80 
81 			ret = fork();
82 			if (ret < 0) {
83 				ha_alert("Cannot fork program '%s'.\n", child->id);
84 				exit(EXIT_FAILURE); /* there has been an error */
85 			} else if (ret > 0) { /* parent */
86 				child->pid = ret;
87 				ha_notice("New program '%s' (%d) forked\n", child->id, ret);
88 				continue;
89 			} else if (ret == 0) {
90 				/* In child */
91 				mworker_unblock_signals();
92 				mworker_cleanlisteners();
93 				mworker_cleantasks();
94 
95 				/* setgid / setuid */
96 				if (child->gid != -1) {
97 					if (getgroups(0, NULL) > 0 && setgroups(0, NULL) == -1)
98 						ha_warning("[%s.main()] Failed to drop supplementary groups. Using 'gid'/'group'"
99 							" without 'uid'/'user' is generally useless.\n", child->command[0]);
100 
101 					if (setgid(child->gid) == -1) {
102 						ha_alert("[%s.main()] Cannot set gid %d.\n", child->command[0], child->gid);
103 						exit(1);
104 					}
105 				}
106 
107 				if (child->uid != -1 && setuid(child->uid) == -1) {
108 					ha_alert("[%s.main()] Cannot set uid %d.\n", child->command[0], child->gid);
109 					exit(1);
110 				}
111 
112 				execvp(child->command[0], child->command);
113 
114 				ha_alert("Cannot execute %s: %s\n", child->command[0], strerror(errno));
115 				exit(EXIT_FAILURE);
116 			}
117 		}
118 	}
119 
120 	return 0;
121 
122 }
123 
124 
125 /* Configuration */
126 
cfg_parse_program(const char * file,int linenum,char ** args,int kwm)127 int cfg_parse_program(const char *file, int linenum, char **args, int kwm)
128 {
129 	static struct mworker_proc *ext_child = NULL;
130 	struct mworker_proc *child;
131 	int err_code = 0;
132 
133 	if (!strcmp(args[0], "program")) {
134 		if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
135 			err_code |= ERR_ABORT;
136 			goto error;
137 		}
138 
139 		if (!*args[1]) {
140 			ha_alert("parsing [%s:%d] : '%s' expects an <id> argument\n",
141 				 file, linenum, args[0]);
142 			err_code |= ERR_ALERT | ERR_ABORT;
143 			goto error;
144 		}
145 
146 		ext_child = calloc(1, sizeof(*ext_child));
147 		if (!ext_child) {
148 			ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
149 			err_code |= ERR_ALERT | ERR_ABORT;
150 			goto error;
151 		}
152 
153 		ext_child->options |= PROC_O_TYPE_PROG; /* external process */
154 		ext_child->command = NULL;
155 		ext_child->path = NULL;
156 		ext_child->id = NULL;
157 		ext_child->pid = -1;
158 		ext_child->relative_pid = -1;
159 		ext_child->reloads = 0;
160 		ext_child->timestamp = -1;
161 		ext_child->ipc_fd[0] = -1;
162 		ext_child->ipc_fd[1] = -1;
163 		ext_child->options |= PROC_O_START_RELOAD; /* restart the programs by default */
164 		ext_child->uid = -1;
165 		ext_child->gid = -1;
166 		LIST_INIT(&ext_child->list);
167 
168 		list_for_each_entry(child, &proc_list, list) {
169 			if (child->reloads == 0 && (child->options & PROC_O_TYPE_PROG)) {
170 				if (!strcmp(args[1], child->id)) {
171 					ha_alert("parsing [%s:%d]: '%s' program section already exists in the configuration.\n", file, linenum, args[1]);
172 					err_code |= ERR_ALERT | ERR_ABORT;
173 					goto error;
174 				}
175 			}
176 		}
177 
178 		ext_child->id = strdup(args[1]);
179 		if (!ext_child->id) {
180 			ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
181 			err_code |= ERR_ALERT | ERR_ABORT;
182 			goto error;
183 		}
184 
185 		LIST_ADDQ(&proc_list, &ext_child->list);
186 
187 	} else if (!strcmp(args[0], "command")) {
188 		int arg_nb = 0;
189 		int i = 0;
190 
191 		if (*(args[1]) == 0) {
192 			ha_alert("parsing [%s:%d]: '%s' expects a command with optional arguments separated in words.\n", file, linenum, args[0]);
193 			err_code |= ERR_ALERT | ERR_FATAL;
194 			goto error;
195 		}
196 
197 		while (*args[arg_nb+1])
198 			arg_nb++;
199 
200 		ext_child->command = calloc(arg_nb+1, sizeof(*ext_child->command));
201 
202 		if (!ext_child->command) {
203 			ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
204 			err_code |= ERR_ALERT | ERR_ABORT;
205 			goto error;
206 		}
207 
208 		while (i < arg_nb) {
209 			ext_child->command[i] = strdup(args[i+1]);
210 			if (!ext_child->command[i]) {
211 				ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
212 				err_code |= ERR_ALERT | ERR_ABORT;
213 				goto error;
214 			}
215 			i++;
216 		}
217 		ext_child->command[i] = NULL;
218 
219 	} else if (!strcmp(args[0], "option")) {
220 
221 		if (*(args[1]) == '\0') {
222 			ha_alert("parsing [%s:%d]: '%s' expects an option name.\n",
223 				 file, linenum, args[0]);
224 			err_code |= ERR_ALERT | ERR_FATAL;
225 			goto error;
226 		}
227 
228 		if (strcmp(args[1], "start-on-reload") == 0) {
229 			if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
230 				goto error;
231 			if (kwm == KWM_STD)
232 				ext_child->options |= PROC_O_START_RELOAD;
233 			else if (kwm == KWM_NO)
234 				ext_child->options &= ~PROC_O_START_RELOAD;
235 			goto out;
236 
237 		} else {
238 			ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);
239 			err_code |= ERR_ALERT | ERR_FATAL;
240 			goto error;
241 		}
242 	} else if (!strcmp(args[0], "user")) {
243 		struct passwd *ext_child_user;
244 		if (*(args[1]) == '\0') {
245 			ha_alert("parsing [%s:%d]: '%s' expects a user name.\n",
246 				 file, linenum, args[0]);
247 			err_code |= ERR_ALERT | ERR_FATAL;
248 			goto error;
249 		}
250 
251 		if (alertif_too_many_args(1, file, linenum, args, &err_code))
252 			goto error;
253 
254 		if (ext_child->uid != -1) {
255 			ha_alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum);
256 			err_code |= ERR_ALERT;
257 			goto out;
258 		}
259 
260 		ext_child_user = getpwnam(args[1]);
261 		if (ext_child_user != NULL) {
262 			ext_child->uid = (int)ext_child_user->pw_uid;
263 		} else {
264 			ha_alert("parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno));
265 			err_code |= ERR_ALERT | ERR_FATAL;
266 		}
267 	} else if (!strcmp(args[0], "group")) {
268 		struct group *ext_child_group;
269 		if (*(args[1]) == '\0') {
270 			ha_alert("parsing [%s:%d]: '%s' expects a group name.\n",
271 				 file, linenum, args[0]);
272 			err_code |= ERR_ALERT | ERR_FATAL;
273 			goto error;
274 		}
275 
276 		if (alertif_too_many_args(1, file, linenum, args, &err_code))
277 			goto error;
278 
279 		if (ext_child->gid != -1) {
280 			ha_alert("parsing [%s:%d] : group/gid already specified. Continuing.\n", file, linenum);
281 			err_code |= ERR_ALERT;
282 			goto out;
283 		}
284 
285 		ext_child_group = getgrnam(args[1]);
286 		if (ext_child_group != NULL) {
287 			ext_child->gid = (int)ext_child_group->gr_gid;
288 		} else {
289 			ha_alert("parsing [%s:%d] : cannot find group id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno));
290 			err_code |= ERR_ALERT | ERR_FATAL;
291 		}
292 	} else {
293 		ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "program");
294 		err_code |= ERR_ALERT | ERR_FATAL;
295 		goto error;
296 	}
297 
298 	use_program = 1;
299 
300 	return err_code;
301 
302 error:
303 	if (ext_child) {
304 		LIST_DEL(&ext_child->list);
305 		if (ext_child->command) {
306 			int i;
307 
308 			for (i = 0; ext_child->command[i]; i++) {
309 				if (ext_child->command[i]) {
310 					free(ext_child->command[i]);
311 					ext_child->command[i] = NULL;
312 				}
313 			}
314 			free(ext_child->command);
315 			ext_child->command = NULL;
316 		}
317 		if (ext_child->id) {
318 			free(ext_child->id);
319 			ext_child->id = NULL;
320 		}
321 	}
322 
323 	free(ext_child);
324 	ext_child = NULL;
325 
326 out:
327 	return err_code;
328 
329 }
330 
cfg_program_postparser()331 int cfg_program_postparser()
332 {
333 	int err_code = 0;
334 	struct mworker_proc *child;
335 
336 	list_for_each_entry(child, &proc_list, list) {
337 		if (child->reloads == 0 && (child->options & PROC_O_TYPE_PROG)) {
338 			if (child->command == NULL) {
339 				ha_alert("The program section '%s' lacks a command to launch.\n", child->id);
340 				err_code |= ERR_ALERT | ERR_FATAL;
341 			}
342 		}
343 	}
344 
345 	if (use_program && !(global.mode & MODE_MWORKER)) {
346 		ha_alert("Can't use a 'program' section without master worker mode.\n");
347 		err_code |= ERR_ALERT | ERR_FATAL;
348 	}
349 
350 	return err_code;
351 }
352 
353 
354 REGISTER_CONFIG_SECTION("program", cfg_parse_program, NULL);
355 REGISTER_CONFIG_POSTPARSER("program", cfg_program_postparser);
356