re-apply: allow run spawn-fcgi as root
[tomato.git] / release / src / router / spawn-fcgi / src / spawn-fcgi.c
blob3df27db0779c5c7e3bb2ff932b47ee28e6a8006e
1 #ifdef HAVE_CONFIG_H
2 # include "config.h"
3 #endif
5 #include <sys/types.h>
6 #include <sys/time.h>
7 #include <sys/stat.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <errno.h>
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <fcntl.h>
16 #ifdef HAVE_PWD_H
17 # include <grp.h>
18 # include <pwd.h>
19 #endif
21 #ifdef HAVE_GETOPT_H
22 # include <getopt.h>
23 #endif
25 #define FCGI_LISTENSOCK_FILENO 0
27 # include <sys/socket.h>
28 # include <sys/ioctl.h>
29 # include <netinet/in.h>
30 # include <netinet/tcp.h>
31 # include <sys/un.h>
32 # include <arpa/inet.h>
34 # include <netdb.h>
36 #ifdef HAVE_SYS_WAIT_H
37 # include <sys/wait.h>
38 #endif
40 /* for solaris 2.5 and netbsd 1.3.x */
41 #ifndef HAVE_SOCKLEN_T
42 typedef int socklen_t;
43 #endif
45 #ifndef HAVE_ISSETUGID
46 static int issetugid() {
47 return (geteuid() != getuid() || getegid() != getgid());
49 #endif
51 #if defined(HAVE_IPV6) && defined(HAVE_INET_PTON)
52 # define USE_IPV6
53 #endif
55 #ifdef USE_IPV6
56 #define PACKAGE_FEATURES " (ipv6)"
57 #else
58 #define PACKAGE_FEATURES ""
59 #endif
61 #define PACKAGE_DESC "spawn-fcgi v" PACKAGE_VERSION PACKAGE_FEATURES " - spawns FastCGI processes\n"
63 #define CONST_STR_LEN(s) s, sizeof(s) - 1
65 static mode_t read_umask(void) {
66 mode_t mask = umask(0);
67 umask(mask);
68 return mask;
71 static ssize_t write_all(int fildes, const void *buf, size_t nbyte) {
72 size_t rem;
73 for (rem = nbyte; rem > 0;) {
74 ssize_t res = write(fildes, buf, rem);
75 if (-1 == res) {
76 if (EINTR != errno) return res;
77 } else {
78 buf = res + (char const*) buf;
79 rem -= res;
82 return nbyte;
85 static int bind_socket(const char *addr, unsigned short port, const char *unixsocket, uid_t uid, gid_t gid, mode_t mode, int backlog) {
86 int fcgi_fd, socket_type, val;
88 struct sockaddr_un fcgi_addr_un;
89 struct sockaddr_in fcgi_addr_in;
90 #ifdef USE_IPV6
91 struct sockaddr_in6 fcgi_addr_in6;
92 #endif
93 struct sockaddr *fcgi_addr;
95 socklen_t servlen;
97 if (unixsocket) {
98 memset(&fcgi_addr_un, 0, sizeof(fcgi_addr_un));
100 fcgi_addr_un.sun_family = AF_UNIX;
101 /* already checked in main() */
102 if (strlen(unixsocket) > sizeof(fcgi_addr_un.sun_path) - 1) return -1;
103 strcpy(fcgi_addr_un.sun_path, unixsocket);
105 #ifdef SUN_LEN
106 servlen = SUN_LEN(&fcgi_addr_un);
107 #else
108 /* stevens says: */
109 servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family);
110 #endif
111 socket_type = AF_UNIX;
112 fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
114 /* check if some backend is listening on the socket
115 * as if we delete the socket-file and rebind there will be no "socket already in use" error
117 if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
118 fprintf(stderr, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno));
119 return -1;
122 if (0 == connect(fcgi_fd, fcgi_addr, servlen)) {
123 fprintf(stderr, "spawn-fcgi: socket is already in use, can't spawn\n");
124 close(fcgi_fd);
125 return -1;
128 /* cleanup previous socket if it exists */
129 if (-1 == unlink(unixsocket)) {
130 switch (errno) {
131 case ENOENT:
132 break;
133 default:
134 fprintf(stderr, "spawn-fcgi: removing old socket failed: %s\n", strerror(errno));
135 close(fcgi_fd);
136 return -1;
140 close(fcgi_fd);
141 } else {
142 memset(&fcgi_addr_in, 0, sizeof(fcgi_addr_in));
143 fcgi_addr_in.sin_family = AF_INET;
144 fcgi_addr_in.sin_port = htons(port);
146 servlen = sizeof(fcgi_addr_in);
147 socket_type = AF_INET;
148 fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
150 #ifdef USE_IPV6
151 memset(&fcgi_addr_in6, 0, sizeof(fcgi_addr_in6));
152 fcgi_addr_in6.sin6_family = AF_INET6;
153 fcgi_addr_in6.sin6_port = fcgi_addr_in.sin_port;
154 #endif
156 if (addr == NULL) {
157 fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
158 #ifdef HAVE_INET_PTON
159 } else if (1 == inet_pton(AF_INET, addr, &fcgi_addr_in.sin_addr)) {
160 /* nothing to do */
161 #ifdef HAVE_IPV6
162 } else if (1 == inet_pton(AF_INET6, addr, &fcgi_addr_in6.sin6_addr)) {
163 servlen = sizeof(fcgi_addr_in6);
164 socket_type = AF_INET6;
165 fcgi_addr = (struct sockaddr *) &fcgi_addr_in6;
166 #endif
167 } else {
168 fprintf(stderr, "spawn-fcgi: '%s' is not a valid IP address\n", addr);
169 return -1;
170 #else
171 } else {
172 if ((in_addr_t)(-1) == (fcgi_addr_in.sin_addr.s_addr = inet_addr(addr))) {
173 fprintf(stderr, "spawn-fcgi: '%s' is not a valid IPv4 address\n", addr);
174 return -1;
176 #endif
181 if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
182 fprintf(stderr, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno));
183 return -1;
186 val = 1;
187 if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
188 fprintf(stderr, "spawn-fcgi: couldn't set SO_REUSEADDR: %s\n", strerror(errno));
189 close(fcgi_fd);
190 return -1;
193 if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
194 fprintf(stderr, "spawn-fcgi: bind failed: %s\n", strerror(errno));
195 close(fcgi_fd);
196 return -1;
199 if (unixsocket) {
200 if (-1 == chmod(unixsocket, mode)) {
201 fprintf(stderr, "spawn-fcgi: couldn't chmod socket: %s\n", strerror(errno));
202 close(fcgi_fd);
203 unlink(unixsocket);
204 return -1;
207 if (0 != uid || 0 != gid) {
208 if (0 == uid) uid = -1;
209 if (0 == gid) gid = -1;
210 if (-1 == chown(unixsocket, uid, gid)) {
211 fprintf(stderr, "spawn-fcgi: couldn't chown socket: %s\n", strerror(errno));
212 close(fcgi_fd);
213 unlink(unixsocket);
214 return -1;
219 if (-1 == listen(fcgi_fd, backlog)) {
220 fprintf(stderr, "spawn-fcgi: listen failed: %s\n", strerror(errno));
221 close(fcgi_fd);
222 if (unixsocket) unlink(unixsocket);
223 return -1;
226 return fcgi_fd;
229 static int fcgi_spawn_connection(char *appPath, char **appArgv, int fcgi_fd, int fork_count, int child_count, int pid_fd, int nofork) {
230 int status, rc = 0;
231 struct timeval tv = { 0, 100 * 1000 };
233 pid_t child;
235 while (fork_count-- > 0) {
237 if (!nofork) {
238 child = fork();
239 } else {
240 child = 0;
243 switch (child) {
244 case 0: {
245 char cgi_childs[64];
246 int max_fd = 0;
248 int i = 0;
250 if (child_count >= 0) {
251 snprintf(cgi_childs, sizeof(cgi_childs), "PHP_FCGI_CHILDREN=%d", child_count);
252 putenv(cgi_childs);
255 if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
256 close(FCGI_LISTENSOCK_FILENO);
257 dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
258 close(fcgi_fd);
261 /* loose control terminal */
262 if (!nofork) {
263 setsid();
265 max_fd = open("/dev/null", O_RDWR);
266 if (-1 != max_fd) {
267 if (max_fd != STDOUT_FILENO) dup2(max_fd, STDOUT_FILENO);
268 if (max_fd != STDERR_FILENO) dup2(max_fd, STDERR_FILENO);
269 if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO) close(max_fd);
270 } else {
271 fprintf(stderr, "spawn-fcgi: couldn't open and redirect stdout/stderr to '/dev/null': %s\n", strerror(errno));
275 /* we don't need the client socket */
276 for (i = 3; i < max_fd; i++) {
277 if (i != FCGI_LISTENSOCK_FILENO) close(i);
280 /* fork and replace shell */
281 if (appArgv) {
282 execv(appArgv[0], appArgv);
284 } else {
285 char *b = malloc((sizeof("exec ") - 1) + strlen(appPath) + 1);
286 strcpy(b, "exec ");
287 strcat(b, appPath);
289 /* exec the cgi */
290 execl("/bin/sh", "sh", "-c", b, (char *)NULL);
292 free(b);
295 /* in nofork mode stderr is still open */
296 fprintf(stderr, "spawn-fcgi: exec failed: %s\n", strerror(errno));
297 exit(errno);
299 break;
301 case -1:
302 /* error */
303 fprintf(stderr, "spawn-fcgi: fork failed: %s\n", strerror(errno));
304 break;
305 default:
306 /* father */
308 /* wait */
309 select(0, NULL, NULL, NULL, &tv);
311 switch (waitpid(child, &status, WNOHANG)) {
312 case 0:
313 fprintf(stdout, "spawn-fcgi: child spawned successfully: PID: %d\n", child);
315 /* write pid file */
316 if (-1 != pid_fd) {
317 /* assume a 32bit pid_t */
318 char pidbuf[12];
320 snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child);
322 if (-1 == write_all(pid_fd, pidbuf, strlen(pidbuf))) {
323 fprintf(stderr, "spawn-fcgi: writing pid file failed: %s\n", strerror(errno));
324 close(pid_fd);
325 pid_fd = -1;
327 /* avoid eol for the last one */
328 if (-1 != pid_fd && fork_count != 0) {
329 if (-1 == write_all(pid_fd, "\n", 1)) {
330 fprintf(stderr, "spawn-fcgi: writing pid file failed: %s\n", strerror(errno));
331 close(pid_fd);
332 pid_fd = -1;
337 break;
338 case -1:
339 break;
340 default:
341 if (WIFEXITED(status)) {
342 fprintf(stderr, "spawn-fcgi: child exited with: %d\n",
343 WEXITSTATUS(status));
344 rc = WEXITSTATUS(status);
345 } else if (WIFSIGNALED(status)) {
346 fprintf(stderr, "spawn-fcgi: child signaled: %d\n",
347 WTERMSIG(status));
348 rc = 1;
349 } else {
350 fprintf(stderr, "spawn-fcgi: child died somehow: exit status = %d\n",
351 status);
352 rc = status;
356 break;
360 if (-1 != pid_fd) {
361 close(pid_fd);
364 close(fcgi_fd);
366 return rc;
369 static int find_user_group(const char *user, const char *group, uid_t *uid, gid_t *gid, const char **username) {
370 uid_t my_uid = 0;
371 gid_t my_gid = 0;
372 struct passwd *my_pwd = NULL;
373 struct group *my_grp = NULL;
374 char *endptr = NULL;
375 *uid = 0; *gid = 0;
376 if (username) *username = NULL;
378 if (user) {
379 my_uid = strtol(user, &endptr, 10);
381 if (my_uid <= 0 || *endptr) {
382 if (NULL == (my_pwd = getpwnam(user))) {
383 fprintf(stderr, "spawn-fcgi: can't find user name %s\n", user);
384 return -1;
386 my_uid = my_pwd->pw_uid;
388 // if (my_uid == 0) {
389 // fprintf(stderr, "spawn-fcgi: I will not set uid to 0\n");
390 // return -1;
391 // }
393 if (username) *username = user;
394 } else {
395 my_pwd = getpwuid(my_uid);
396 if (username && my_pwd) *username = my_pwd->pw_name;
400 if (group) {
401 my_gid = strtol(group, &endptr, 10);
403 if (my_gid <= 0 || *endptr) {
404 if (NULL == (my_grp = getgrnam(group))) {
405 fprintf(stderr, "spawn-fcgi: can't find group name %s\n", group);
406 return -1;
408 my_gid = my_grp->gr_gid;
410 // if (my_gid == 0) {
411 // fprintf(stderr, "spawn-fcgi: I will not set gid to 0\n");
412 // return -1;
413 // }
415 } else if (my_pwd) {
416 my_gid = my_pwd->pw_gid;
418 // if (my_gid == 0) {
419 // fprintf(stderr, "spawn-fcgi: I will not set gid to 0\n");
420 // return -1;
421 // }
424 *uid = my_uid;
425 *gid = my_gid;
426 return 0;
429 static void show_version () {
430 (void) write_all(1, CONST_STR_LEN(
431 PACKAGE_DESC
435 static void show_help () {
436 (void) write_all(1, CONST_STR_LEN(
437 "Usage: spawn-fcgi [options] [-- <fcgiapp> [fcgi app arguments]]\n" \
438 "\n" \
439 PACKAGE_DESC \
440 "\n" \
441 "Options:\n" \
442 " -f <path> filename of the fcgi-application (deprecated; ignored if\n" \
443 " <fcgiapp> is given; needs /bin/sh)\n" \
444 " -d <directory> chdir to directory before spawning\n" \
445 " -a <address> bind to IPv4/IPv6 address (defaults to 0.0.0.0)\n" \
446 " -p <port> bind to TCP-port\n" \
447 " -s <path> bind to Unix domain socket\n" \
448 " -M <mode> change Unix domain socket mode (octal integer, default: allow\n" \
449 " read+write for user and group as far as umask allows it) \n" \
450 " -C <children> (PHP only) numbers of childs to spawn (default: not setting\n" \
451 " the PHP_FCGI_CHILDREN environment variable - PHP defaults to 0)\n" \
452 " -F <children> number of children to fork (default 1)\n" \
453 " -b <backlog> backlog to allow on the socket (default 1024)\n" \
454 " -P <path> name of PID-file for spawned process (ignored in no-fork mode)\n" \
455 " -n no fork (for daemontools)\n" \
456 " -v show version\n" \
457 " -?, -h show this help\n" \
458 "(root only)\n" \
459 " -c <directory> chroot to directory\n" \
460 " -S create socket before chroot() (default is to create the socket\n" \
461 " in the chroot)\n" \
462 " -u <user> change to user-id\n" \
463 " -g <group> change to group-id (default: primary group of user if -u\n" \
464 " is given)\n" \
465 " -U <user> change Unix domain socket owner to user-id\n" \
466 " -G <group> change Unix domain socket group to group-id\n" \
471 int main(int argc, char **argv) {
472 char *fcgi_app = NULL, *changeroot = NULL, *username = NULL,
473 *groupname = NULL, *unixsocket = NULL, *pid_file = NULL,
474 *sockusername = NULL, *sockgroupname = NULL, *fcgi_dir = NULL,
475 *addr = NULL;
476 char **fcgi_app_argv = { NULL };
477 char *endptr = NULL;
478 unsigned short port = 0;
479 mode_t sockmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) & ~read_umask();
480 int child_count = -1;
481 int fork_count = 1;
482 int backlog = 1024;
483 int i_am_root, o;
484 int pid_fd = -1;
485 int nofork = 0;
486 int sockbeforechroot = 0;
487 struct sockaddr_un un;
488 int fcgi_fd = -1;
490 if (argc < 2) { /* no arguments given */
491 show_help();
492 return -1;
495 i_am_root = (getuid() == 0);
497 while (-1 != (o = getopt(argc, argv, "c:d:f:g:?hna:p:b:u:vC:F:s:P:U:G:M:S"))) {
498 switch(o) {
499 case 'f': fcgi_app = optarg; break;
500 case 'd': fcgi_dir = optarg; break;
501 case 'a': addr = optarg;/* ip addr */ break;
502 case 'p': port = strtol(optarg, &endptr, 10);/* port */
503 if (*endptr) {
504 fprintf(stderr, "spawn-fcgi: invalid port: %u\n", (unsigned int) port);
505 return -1;
507 break;
508 case 'C': child_count = strtol(optarg, NULL, 10);/* */ break;
509 case 'F': fork_count = strtol(optarg, NULL, 10);/* */ break;
510 case 'b': backlog = strtol(optarg, NULL, 10);/* */ break;
511 case 's': unixsocket = optarg; /* unix-domain socket */ break;
512 case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break;
513 case 'u': if (i_am_root) { username = optarg; } /* set user */ break;
514 case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break;
515 case 'U': if (i_am_root) { sockusername = optarg; } /* set socket user */ break;
516 case 'G': if (i_am_root) { sockgroupname = optarg; } /* set socket group */ break;
517 case 'S': if (i_am_root) { sockbeforechroot = 1; } /* open socket before chroot() */ break;
518 case 'M': sockmode = strtol(optarg, NULL, 8); /* set socket mode */ break;
519 case 'n': nofork = 1; break;
520 case 'P': pid_file = optarg; /* PID file */ break;
521 case 'v': show_version(); return 0;
522 case '?':
523 case 'h': show_help(); return 0;
524 default:
525 show_help();
526 return -1;
530 if (optind < argc) {
531 fcgi_app_argv = &argv[optind];
534 if (NULL == fcgi_app && NULL == fcgi_app_argv) {
535 fprintf(stderr, "spawn-fcgi: no FastCGI application given\n");
536 return -1;
539 if (0 == port && NULL == unixsocket) {
540 fprintf(stderr, "spawn-fcgi: no socket given (use either -p or -s)\n");
541 return -1;
542 } else if (0 != port && NULL != unixsocket) {
543 fprintf(stderr, "spawn-fcgi: either a Unix domain socket or a TCP-port, but not both\n");
544 return -1;
547 if (unixsocket && strlen(unixsocket) > sizeof(un.sun_path) - 1) {
548 fprintf(stderr, "spawn-fcgi: path of the Unix domain socket is too long\n");
549 return -1;
552 /* SUID handling */
553 if (!i_am_root && issetugid()) {
554 fprintf(stderr, "spawn-fcgi: Are you nuts? Don't apply a SUID bit to this binary\n");
555 return -1;
558 if (nofork) pid_file = NULL; /* ignore pid file in no-fork mode */
560 if (pid_file &&
561 (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))) {
562 struct stat st;
563 if (errno != EEXIST) {
564 fprintf(stderr, "spawn-fcgi: opening PID-file '%s' failed: %s\n",
565 pid_file, strerror(errno));
566 return -1;
569 /* ok, file exists */
571 if (0 != stat(pid_file, &st)) {
572 fprintf(stderr, "spawn-fcgi: stating PID-file '%s' failed: %s\n",
573 pid_file, strerror(errno));
574 return -1;
577 /* is it a regular file ? */
579 if (!S_ISREG(st.st_mode)) {
580 fprintf(stderr, "spawn-fcgi: PID-file exists and isn't regular file: '%s'\n",
581 pid_file);
582 return -1;
585 if (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
586 fprintf(stderr, "spawn-fcgi: opening PID-file '%s' failed: %s\n",
587 pid_file, strerror(errno));
588 return -1;
592 if (i_am_root) {
593 uid_t uid, sockuid;
594 gid_t gid, sockgid;
595 const char* real_username;
597 if (-1 == find_user_group(username, groupname, &uid, &gid, &real_username))
598 return -1;
600 if (-1 == find_user_group(sockusername, sockgroupname, &sockuid, &sockgid, NULL))
601 return -1;
603 if (uid != 0 && gid == 0) {
604 fprintf(stderr, "spawn-fcgi: WARNING: couldn't find the user for uid %i and no group was specified, so only the user privileges will be dropped\n", (int) uid);
607 if (0 == sockuid) sockuid = uid;
608 if (0 == sockgid) sockgid = gid;
610 if (sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode, backlog)))
611 return -1;
613 /* Change group before chroot, when we have access
614 * to /etc/group
616 if (gid != 0) {
617 if (-1 == setgid(gid)) {
618 fprintf(stderr, "spawn-fcgi: setgid(%i) failed: %s\n", (int) gid, strerror(errno));
619 return -1;
621 if (-1 == setgroups(0, NULL)) {
622 fprintf(stderr, "spawn-fcgi: setgroups(0, NULL) failed: %s\n", strerror(errno));
623 return -1;
625 if (real_username) {
626 if (-1 == initgroups(real_username, gid)) {
627 fprintf(stderr, "spawn-fcgi: initgroups('%s', %i) failed: %s\n", real_username, (int) gid, strerror(errno));
628 return -1;
633 if (changeroot) {
634 if (-1 == chroot(changeroot)) {
635 fprintf(stderr, "spawn-fcgi: chroot('%s') failed: %s\n", changeroot, strerror(errno));
636 return -1;
638 if (-1 == chdir("/")) {
639 fprintf(stderr, "spawn-fcgi: chdir('/') failed: %s\n", strerror(errno));
640 return -1;
644 if (!sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode, backlog)))
645 return -1;
647 /* drop root privs */
648 if (uid != 0) {
649 if (-1 == setuid(uid)) {
650 fprintf(stderr, "spawn-fcgi: setuid(%i) failed: %s\n", (int) uid, strerror(errno));
651 return -1;
654 } else {
655 if (-1 == (fcgi_fd = bind_socket(addr, port, unixsocket, 0, 0, sockmode, backlog)))
656 return -1;
659 if (fcgi_dir && -1 == chdir(fcgi_dir)) {
660 fprintf(stderr, "spawn-fcgi: chdir('%s') failed: %s\n", fcgi_dir, strerror(errno));
661 return -1;
664 return fcgi_spawn_connection(fcgi_app, fcgi_app_argv, fcgi_fd, fork_count, child_count, pid_fd, nofork);