xref: /PHP-7.4/sapi/fpm/fpm/fpm_unix.c (revision 263f14ac)
1 	/* (c) 2007,2008 Andrei Nigmatulin */
2 
3 #include "fpm_config.h"
4 
5 #include <string.h>
6 #include <sys/time.h>
7 #include <sys/resource.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <pwd.h>
12 #include <grp.h>
13 
14 #ifdef HAVE_PRCTL
15 #include <sys/prctl.h>
16 #endif
17 
18 #ifdef HAVE_APPARMOR
19 #include <sys/apparmor.h>
20 #endif
21 
22 #ifdef HAVE_SYS_ACL_H
23 #include <sys/acl.h>
24 #endif
25 
26 #include "fpm.h"
27 #include "fpm_conf.h"
28 #include "fpm_cleanup.h"
29 #include "fpm_clock.h"
30 #include "fpm_stdio.h"
31 #include "fpm_unix.h"
32 #include "fpm_signals.h"
33 #include "zlog.h"
34 
35 size_t fpm_pagesize;
36 
fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s * wp)37 int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
38 {
39 	struct fpm_worker_pool_config_s *c = wp->config;
40 #ifdef HAVE_FPM_ACL
41 	int n;
42 
43 	/* uninitialized */
44 	wp->socket_acl  = NULL;
45 #endif
46 	wp->socket_uid = -1;
47 	wp->socket_gid = -1;
48 	wp->socket_mode = 0660;
49 
50 	if (!c) {
51 		return 0;
52 	}
53 
54 	if (c->listen_mode && *c->listen_mode) {
55 		wp->socket_mode = strtoul(c->listen_mode, 0, 8);
56 	}
57 
58 #ifdef HAVE_FPM_ACL
59 	/* count the users and groups configured */
60 	n = 0;
61 	if (c->listen_acl_users && *c->listen_acl_users) {
62 		char *p;
63 		n++;
64 		for (p=strchr(c->listen_acl_users, ',') ; p ; p=strchr(p+1, ',')) {
65 			n++;
66 		}
67 	}
68 	if (c->listen_acl_groups && *c->listen_acl_groups) {
69 		char *p;
70 		n++;
71 		for (p=strchr(c->listen_acl_groups, ',') ; p ; p=strchr(p+1, ',')) {
72 			n++;
73 		}
74 	}
75 	/* if ACL configured */
76 	if (n) {
77 		acl_t acl;
78 		acl_entry_t entry;
79 		acl_permset_t perm;
80 		char *tmp, *p, *end;
81 
82 		acl = acl_init(n);
83 		if (!acl) {
84 			zlog(ZLOG_SYSERROR, "[pool %s] cannot allocate ACL", wp->config->name);
85 			return -1;
86 		}
87 		/* Create USER ACL */
88 		if (c->listen_acl_users && *c->listen_acl_users) {
89 			struct passwd *pwd;
90 
91 			tmp = estrdup(c->listen_acl_users);
92 			for (p=tmp ; p ; p=end) {
93 				if ((end = strchr(p, ','))) {
94 					*end++ = 0;
95 				}
96 				pwd = getpwnam(p);
97 				if (pwd) {
98 					zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid);
99 				} else {
100 					zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, p);
101 					acl_free(acl);
102 					efree(tmp);
103 					return -1;
104 				}
105 				if (0 > acl_create_entry(&acl, &entry) ||
106 					0 > acl_set_tag_type(entry, ACL_USER) ||
107 					0 > acl_set_qualifier(entry, &pwd->pw_uid) ||
108 					0 > acl_get_permset(entry, &perm) ||
109 					0 > acl_clear_perms (perm) ||
110 					0 > acl_add_perm (perm, ACL_READ) ||
111 					0 > acl_add_perm (perm, ACL_WRITE)) {
112 					zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for user '%s'", wp->config->name, p);
113 					acl_free(acl);
114 					efree(tmp);
115 					return -1;
116 				}
117 			}
118 			efree(tmp);
119 		}
120 		/* Create GROUP ACL */
121 		if (c->listen_acl_groups && *c->listen_acl_groups) {
122 			struct group *grp;
123 
124 			tmp = estrdup(c->listen_acl_groups);
125 			for (p=tmp ; p ; p=end) {
126 				if ((end = strchr(p, ','))) {
127 					*end++ = 0;
128 				}
129 				grp = getgrnam(p);
130 				if (grp) {
131 					zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid);
132 				} else {
133 					zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, p);
134 					acl_free(acl);
135 					efree(tmp);
136 					return -1;
137 				}
138 				if (0 > acl_create_entry(&acl, &entry) ||
139 					0 > acl_set_tag_type(entry, ACL_GROUP) ||
140 					0 > acl_set_qualifier(entry, &grp->gr_gid) ||
141 					0 > acl_get_permset(entry, &perm) ||
142 					0 > acl_clear_perms (perm) ||
143 					0 > acl_add_perm (perm, ACL_READ) ||
144 					0 > acl_add_perm (perm, ACL_WRITE)) {
145 					zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for group '%s'", wp->config->name, p);
146 					acl_free(acl);
147 					efree(tmp);
148 					return -1;
149 				}
150 			}
151 			efree(tmp);
152 		}
153 		if (c->listen_owner && *c->listen_owner) {
154 			zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.owner = '%s' is ignored", wp->config->name, c->listen_owner);
155 		}
156 		if (c->listen_group && *c->listen_group) {
157 			zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.group = '%s' is ignored", wp->config->name, c->listen_group);
158 		}
159 		wp->socket_acl  = acl;
160 		return 0;
161 	}
162 	/* When listen.users and listen.groups not configured, continue with standard right */
163 #endif
164 
165 	if (c->listen_owner && *c->listen_owner) {
166 		if (strlen(c->listen_owner) == strspn(c->listen_owner, "0123456789")) {
167 			wp->socket_uid = strtoul(c->listen_owner, 0, 10);
168 		} else {
169 			struct passwd *pwd;
170 
171 			pwd = getpwnam(c->listen_owner);
172 			if (!pwd) {
173 				zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, c->listen_owner);
174 				return -1;
175 			}
176 
177 			wp->socket_uid = pwd->pw_uid;
178 			wp->socket_gid = pwd->pw_gid;
179 		}
180 	}
181 
182 	if (c->listen_group && *c->listen_group) {
183 		if (strlen(c->listen_group) == strspn(c->listen_group, "0123456789")) {
184 			wp->socket_gid = strtoul(c->listen_group, 0, 10);
185 		} else {
186 			struct group *grp;
187 
188 			grp = getgrnam(c->listen_group);
189 			if (!grp) {
190 				zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, c->listen_group);
191 				return -1;
192 			}
193 			wp->socket_gid = grp->gr_gid;
194 		}
195 	}
196 
197 	return 0;
198 }
199 /* }}} */
200 
fpm_unix_set_socket_premissions(struct fpm_worker_pool_s * wp,const char * path)201 int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *path) /* {{{ */
202 {
203 #ifdef HAVE_FPM_ACL
204 	if (wp->socket_acl) {
205 		acl_t aclfile, aclconf;
206 		acl_entry_t entryfile, entryconf;
207 		int i;
208 
209 		/* Read the socket ACL */
210 		aclconf = wp->socket_acl;
211 		aclfile = acl_get_file (path, ACL_TYPE_ACCESS);
212 		if (!aclfile) {
213 			zlog(ZLOG_SYSERROR, "[pool %s] failed to read the ACL of the socket '%s'", wp->config->name, path);
214 			return -1;
215 		}
216 		/* Copy the new ACL entry from config */
217 		for (i=ACL_FIRST_ENTRY ; acl_get_entry(aclconf, i, &entryconf) ; i=ACL_NEXT_ENTRY) {
218 			if (0 > acl_create_entry (&aclfile, &entryfile) ||
219 			    0 > acl_copy_entry(entryfile, entryconf)) {
220 				zlog(ZLOG_SYSERROR, "[pool %s] failed to add entry to the ACL of the socket '%s'", wp->config->name, path);
221 				acl_free(aclfile);
222 				return -1;
223 			}
224 		}
225 		/* Write the socket ACL */
226 		if (0 > acl_calc_mask (&aclfile) ||
227 			0 > acl_valid (aclfile) ||
228 			0 > acl_set_file (path, ACL_TYPE_ACCESS, aclfile)) {
229 			zlog(ZLOG_SYSERROR, "[pool %s] failed to write the ACL of the socket '%s'", wp->config->name, path);
230 			acl_free(aclfile);
231 			return -1;
232 		} else {
233 			zlog(ZLOG_DEBUG, "[pool %s] ACL of the socket '%s' is set", wp->config->name, path);
234 		}
235 
236 		acl_free(aclfile);
237 		return 0;
238 	}
239 	/* When listen.users and listen.groups not configured, continue with standard right */
240 #endif
241 
242 	if (wp->socket_uid != -1 || wp->socket_gid != -1) {
243 		if (0 > chown(path, wp->socket_uid, wp->socket_gid)) {
244 			zlog(ZLOG_SYSERROR, "[pool %s] failed to chown() the socket '%s'", wp->config->name, wp->config->listen_address);
245 			return -1;
246 		}
247 	}
248 	return 0;
249 }
250 /* }}} */
251 
fpm_unix_free_socket_premissions(struct fpm_worker_pool_s * wp)252 int fpm_unix_free_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
253 {
254 #ifdef HAVE_FPM_ACL
255 	if (wp->socket_acl) {
256 		return acl_free(wp->socket_acl);
257 	}
258 #endif
259 	return 0;
260 }
261 /* }}} */
262 
fpm_unix_conf_wp(struct fpm_worker_pool_s * wp)263 static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
264 {
265 	struct passwd *pwd;
266 	int is_root = !geteuid();
267 
268 	if (is_root) {
269 		if (wp->config->user && *wp->config->user) {
270 			if (strlen(wp->config->user) == strspn(wp->config->user, "0123456789")) {
271 				wp->set_uid = strtoul(wp->config->user, 0, 10);
272 			} else {
273 				struct passwd *pwd;
274 
275 				pwd = getpwnam(wp->config->user);
276 				if (!pwd) {
277 					zlog(ZLOG_ERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, wp->config->user);
278 					return -1;
279 				}
280 
281 				wp->set_uid = pwd->pw_uid;
282 				wp->set_gid = pwd->pw_gid;
283 
284 				wp->user = strdup(pwd->pw_name);
285 				wp->home = strdup(pwd->pw_dir);
286 			}
287 		}
288 
289 		if (wp->config->group && *wp->config->group) {
290 			if (strlen(wp->config->group) == strspn(wp->config->group, "0123456789")) {
291 				wp->set_gid = strtoul(wp->config->group, 0, 10);
292 			} else {
293 				struct group *grp;
294 
295 				grp = getgrnam(wp->config->group);
296 				if (!grp) {
297 					zlog(ZLOG_ERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, wp->config->group);
298 					return -1;
299 				}
300 				wp->set_gid = grp->gr_gid;
301 			}
302 		}
303 
304 		if (!fpm_globals.run_as_root) {
305 			if (wp->set_uid == 0 || wp->set_gid == 0) {
306 				zlog(ZLOG_ERROR, "[pool %s] please specify user and group other than root", wp->config->name);
307 				return -1;
308 			}
309 		}
310 	} else { /* not root */
311 		if (wp->config->user && *wp->config->user) {
312 			zlog(ZLOG_NOTICE, "[pool %s] 'user' directive is ignored when FPM is not running as root", wp->config->name);
313 		}
314 		if (wp->config->group && *wp->config->group) {
315 			zlog(ZLOG_NOTICE, "[pool %s] 'group' directive is ignored when FPM is not running as root", wp->config->name);
316 		}
317 		if (wp->config->chroot && *wp->config->chroot) {
318 			zlog(ZLOG_NOTICE, "[pool %s] 'chroot' directive is ignored when FPM is not running as root", wp->config->name);
319 		}
320 		if (wp->config->process_priority != 64) {
321 			zlog(ZLOG_NOTICE, "[pool %s] 'process.priority' directive is ignored when FPM is not running as root", wp->config->name);
322 		}
323 
324 		/* set up HOME and USER anyway */
325 		pwd = getpwuid(getuid());
326 		if (pwd) {
327 			wp->user = strdup(pwd->pw_name);
328 			wp->home = strdup(pwd->pw_dir);
329 		}
330 	}
331 	return 0;
332 }
333 /* }}} */
334 
fpm_unix_init_child(struct fpm_worker_pool_s * wp)335 int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
336 {
337 	int is_root = !geteuid();
338 	int made_chroot = 0;
339 
340 	if (wp->config->rlimit_files) {
341 		struct rlimit r;
342 
343 		r.rlim_max = r.rlim_cur = (rlim_t) wp->config->rlimit_files;
344 
345 		if (0 > setrlimit(RLIMIT_NOFILE, &r)) {
346 			zlog(ZLOG_SYSERROR, "[pool %s] failed to set rlimit_files for this pool. Please check your system limits or decrease rlimit_files. setrlimit(RLIMIT_NOFILE, %d)", wp->config->name, wp->config->rlimit_files);
347 		}
348 	}
349 
350 	if (wp->config->rlimit_core) {
351 		struct rlimit r;
352 
353 		r.rlim_max = r.rlim_cur = wp->config->rlimit_core == -1 ? (rlim_t) RLIM_INFINITY : (rlim_t) wp->config->rlimit_core;
354 
355 		if (0 > setrlimit(RLIMIT_CORE, &r)) {
356 			zlog(ZLOG_SYSERROR, "[pool %s] failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_core. setrlimit(RLIMIT_CORE, %d)", wp->config->name, wp->config->rlimit_core);
357 		}
358 	}
359 
360 	if (is_root && wp->config->chroot && *wp->config->chroot) {
361 		if (0 > chroot(wp->config->chroot)) {
362 			zlog(ZLOG_SYSERROR, "[pool %s] failed to chroot(%s)",  wp->config->name, wp->config->chroot);
363 			return -1;
364 		}
365 		made_chroot = 1;
366 	}
367 
368 	if (wp->config->chdir && *wp->config->chdir) {
369 		if (0 > chdir(wp->config->chdir)) {
370 			zlog(ZLOG_SYSERROR, "[pool %s] failed to chdir(%s)", wp->config->name, wp->config->chdir);
371 			return -1;
372 		}
373 	} else if (made_chroot) {
374 		if (0 > chdir("/")) {
375 			zlog(ZLOG_WARNING, "[pool %s] failed to chdir(/)", wp->config->name);
376 		}
377 	}
378 
379 	if (is_root) {
380 
381 		if (wp->config->process_priority != 64) {
382 			if (setpriority(PRIO_PROCESS, 0, wp->config->process_priority) < 0) {
383 				zlog(ZLOG_SYSERROR, "[pool %s] Unable to set priority for this new process", wp->config->name);
384 				return -1;
385 			}
386 		}
387 
388 		if (wp->set_gid) {
389 			if (0 > setgid(wp->set_gid)) {
390 				zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%d)", wp->config->name, wp->set_gid);
391 				return -1;
392 			}
393 		}
394 		if (wp->set_uid) {
395 			if (0 > initgroups(wp->config->user, wp->set_gid)) {
396 				zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %d)", wp->config->name, wp->config->user, wp->set_gid);
397 				return -1;
398 			}
399 			if (0 > setuid(wp->set_uid)) {
400 				zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%d)", wp->config->name, wp->set_uid);
401 				return -1;
402 			}
403 		}
404 	}
405 
406 #ifdef HAVE_PRCTL
407 	if (wp->config->process_dumpable && 0 > prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)) {
408 		zlog(ZLOG_SYSERROR, "[pool %s] failed to prctl(PR_SET_DUMPABLE)", wp->config->name);
409 	}
410 #endif
411 
412 	if (0 > fpm_clock_init()) {
413 		return -1;
414 	}
415 
416 #ifdef HAVE_APPARMOR
417 	if (wp->config->apparmor_hat) {
418 		char *con, *new_con;
419 
420 		if (aa_getcon(&con, NULL) == -1) {
421 			zlog(ZLOG_SYSERROR, "[pool %s] failed to query apparmor confinement. Please check if \"/proc/*/attr/current\" is read and writeable.", wp->config->name);
422 			return -1;
423 		}
424 
425 		new_con = malloc(strlen(con) + strlen(wp->config->apparmor_hat) + 3); // // + 0 Byte
426 		if (!new_con) {
427 			zlog(ZLOG_SYSERROR, "[pool %s] failed to allocate memory for apparmor hat change.", wp->config->name);
428 			return -1;
429 		}
430 
431 		if (0 > sprintf(new_con, "%s//%s", con, wp->config->apparmor_hat)) {
432 			zlog(ZLOG_SYSERROR, "[pool %s] failed to construct apparmor confinement.", wp->config->name);
433 			return -1;
434 		}
435 
436 		if (0 > aa_change_profile(new_con)) {
437 			zlog(ZLOG_SYSERROR, "[pool %s] failed to change to new confinement (%s). Please check if \"/proc/*/attr/current\" is read and writeable and \"change_profile -> %s//*\" is allowed.", wp->config->name, new_con, con);
438 			return -1;
439 		}
440 
441 		free(con);
442 		free(new_con);
443 	}
444 #endif
445 
446 	return 0;
447 }
448 /* }}} */
449 
fpm_unix_init_main()450 int fpm_unix_init_main() /* {{{ */
451 {
452 	struct fpm_worker_pool_s *wp;
453 	int is_root = !geteuid();
454 
455 	if (fpm_global_config.rlimit_files) {
456 		struct rlimit r;
457 
458 		r.rlim_max = r.rlim_cur = (rlim_t) fpm_global_config.rlimit_files;
459 
460 		if (0 > setrlimit(RLIMIT_NOFILE, &r)) {
461 			zlog(ZLOG_SYSERROR, "failed to set rlimit_files for this pool. Please check your system limits or decrease rlimit_files. setrlimit(RLIMIT_NOFILE, %d)", fpm_global_config.rlimit_files);
462 			return -1;
463 		}
464 	}
465 
466 	if (fpm_global_config.rlimit_core) {
467 		struct rlimit r;
468 
469 		r.rlim_max = r.rlim_cur = fpm_global_config.rlimit_core == -1 ? (rlim_t) RLIM_INFINITY : (rlim_t) fpm_global_config.rlimit_core;
470 
471 		if (0 > setrlimit(RLIMIT_CORE, &r)) {
472 			zlog(ZLOG_SYSERROR, "failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_core. setrlimit(RLIMIT_CORE, %d)", fpm_global_config.rlimit_core);
473 			return -1;
474 		}
475 	}
476 
477 	fpm_pagesize = getpagesize();
478 	if (fpm_global_config.daemonize) {
479 		/*
480 		 * If daemonize, the calling process will die soon
481 		 * and the master process continues to initialize itself.
482 		 *
483 		 * The parent process has then to wait for the master
484 		 * process to initialize to return a consistent exit
485 		 * value. For this pupose, the master process will
486 		 * send \"1\" into the pipe if everything went well
487 		 * and \"0\" otherwise.
488 		 */
489 
490 
491 		struct timeval tv;
492 		fd_set rfds;
493 		int ret;
494 
495 		if (pipe(fpm_globals.send_config_pipe) == -1) {
496 			zlog(ZLOG_SYSERROR, "failed to create pipe");
497 			return -1;
498 		}
499 
500 		/* then fork */
501 		pid_t pid = fork();
502 		switch (pid) {
503 
504 			case -1 : /* error */
505 				zlog(ZLOG_SYSERROR, "failed to daemonize");
506 				return -1;
507 
508 			case 0 : /* children */
509 				close(fpm_globals.send_config_pipe[0]); /* close the read side of the pipe */
510 				break;
511 
512 			default : /* parent */
513 				close(fpm_globals.send_config_pipe[1]); /* close the write side of the pipe */
514 
515 				/*
516 				 * wait for 10s before exiting with error
517 				 * the child is supposed to send 1 or 0 into the pipe to tell the parent
518 				 * how it goes for it
519 				 */
520 				FD_ZERO(&rfds);
521 				FD_SET(fpm_globals.send_config_pipe[0], &rfds);
522 
523 				tv.tv_sec = 10;
524 				tv.tv_usec = 0;
525 
526 				zlog(ZLOG_DEBUG, "The calling process is waiting for the master process to ping via fd=%d", fpm_globals.send_config_pipe[0]);
527 				ret = select(fpm_globals.send_config_pipe[0] + 1, &rfds, NULL, NULL, &tv);
528 				if (ret == -1) {
529 					zlog(ZLOG_SYSERROR, "failed to select");
530 					exit(FPM_EXIT_SOFTWARE);
531 				}
532 				if (ret) { /* data available */
533 					int readval;
534 					ret = read(fpm_globals.send_config_pipe[0], &readval, sizeof(readval));
535 					if (ret == -1) {
536 						zlog(ZLOG_SYSERROR, "failed to read from pipe");
537 						exit(FPM_EXIT_SOFTWARE);
538 					}
539 
540 					if (ret == 0) {
541 						zlog(ZLOG_ERROR, "no data have been read from pipe");
542 						exit(FPM_EXIT_SOFTWARE);
543 					} else {
544 						if (readval == 1) {
545 							zlog(ZLOG_DEBUG, "I received a valid acknowledge from the master process, I can exit without error");
546 							fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT);
547 							exit(FPM_EXIT_OK);
548 						} else {
549 							zlog(ZLOG_DEBUG, "The master process returned an error !");
550 							exit(FPM_EXIT_SOFTWARE);
551 						}
552 					}
553 				} else { /* no date sent ! */
554 					zlog(ZLOG_ERROR, "the master process didn't send back its status (via the pipe to the calling process)");
555 				  exit(FPM_EXIT_SOFTWARE);
556 				}
557 				exit(FPM_EXIT_SOFTWARE);
558 		}
559 	}
560 
561 	/* continue as a child */
562 	setsid();
563 	if (0 > fpm_clock_init()) {
564 		return -1;
565 	}
566 
567 	if (fpm_global_config.process_priority != 64) {
568 		if (is_root) {
569 			if (setpriority(PRIO_PROCESS, 0, fpm_global_config.process_priority) < 0) {
570 				zlog(ZLOG_SYSERROR, "Unable to set priority for the master process");
571 				return -1;
572 			}
573 		} else {
574 			zlog(ZLOG_NOTICE, "'process.priority' directive is ignored when FPM is not running as root");
575 		}
576 	}
577 
578 	fpm_globals.parent_pid = getpid();
579 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
580 		if (0 > fpm_unix_conf_wp(wp)) {
581 			return -1;
582 		}
583 	}
584 
585 	return 0;
586 }
587 /* }}} */
588