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