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>
32 # include <arpa/inet.h>
36 #ifdef HAVE_SYS_WAIT_H
37 # include <sys/wait.h>
40 /* for solaris 2.5 and netbsd 1.3.x */
41 #ifndef HAVE_SOCKLEN_T
42 typedef int socklen_t
;
45 #ifndef HAVE_ISSETUGID
46 static int issetugid() {
47 return (geteuid() != getuid() || getegid() != getgid());
51 #if defined(HAVE_IPV6) && defined(HAVE_INET_PTON)
56 #define PACKAGE_FEATURES " (ipv6)"
58 #define PACKAGE_FEATURES ""
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);
71 static ssize_t
write_all(int fildes
, const void *buf
, size_t nbyte
) {
73 for (rem
= nbyte
; rem
> 0;) {
74 ssize_t res
= write(fildes
, buf
, rem
);
76 if (EINTR
!= errno
) return res
;
78 buf
= res
+ (char const*) buf
;
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
;
91 struct sockaddr_in6 fcgi_addr_in6
;
93 struct sockaddr
*fcgi_addr
;
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
);
106 servlen
= SUN_LEN(&fcgi_addr_un
);
109 servlen
= strlen(fcgi_addr_un
.sun_path
) + sizeof(fcgi_addr_un
.sun_family
);
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
));
122 if (0 == connect(fcgi_fd
, fcgi_addr
, servlen
)) {
123 fprintf(stderr
, "spawn-fcgi: socket is already in use, can't spawn\n");
128 /* cleanup previous socket if it exists */
129 if (-1 == unlink(unixsocket
)) {
134 fprintf(stderr
, "spawn-fcgi: removing old socket failed: %s\n", strerror(errno
));
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
;
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
;
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
)) {
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
;
168 fprintf(stderr
, "spawn-fcgi: '%s' is not a valid IP address\n", addr
);
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
);
181 if (-1 == (fcgi_fd
= socket(socket_type
, SOCK_STREAM
, 0))) {
182 fprintf(stderr
, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno
));
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
));
193 if (-1 == bind(fcgi_fd
, fcgi_addr
, servlen
)) {
194 fprintf(stderr
, "spawn-fcgi: bind failed: %s\n", strerror(errno
));
200 if (-1 == chmod(unixsocket
, mode
)) {
201 fprintf(stderr
, "spawn-fcgi: couldn't chmod socket: %s\n", strerror(errno
));
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
));
219 if (-1 == listen(fcgi_fd
, backlog
)) {
220 fprintf(stderr
, "spawn-fcgi: listen failed: %s\n", strerror(errno
));
222 if (unixsocket
) unlink(unixsocket
);
229 static int fcgi_spawn_connection(char *appPath
, char **appArgv
, int fcgi_fd
, int fork_count
, int child_count
, int pid_fd
, int nofork
) {
231 struct timeval tv
= { 0, 100 * 1000 };
235 while (fork_count
-- > 0) {
250 if (child_count
>= 0) {
251 snprintf(cgi_childs
, sizeof(cgi_childs
), "PHP_FCGI_CHILDREN=%d", child_count
);
255 if(fcgi_fd
!= FCGI_LISTENSOCK_FILENO
) {
256 close(FCGI_LISTENSOCK_FILENO
);
257 dup2(fcgi_fd
, FCGI_LISTENSOCK_FILENO
);
261 /* loose control terminal */
265 max_fd
= open("/dev/null", O_RDWR
);
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
);
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 */
282 execv(appArgv
[0], appArgv
);
285 char *b
= malloc((sizeof("exec ") - 1) + strlen(appPath
) + 1);
290 execl("/bin/sh", "sh", "-c", b
, (char *)NULL
);
295 /* in nofork mode stderr is still open */
296 fprintf(stderr
, "spawn-fcgi: exec failed: %s\n", strerror(errno
));
303 fprintf(stderr
, "spawn-fcgi: fork failed: %s\n", strerror(errno
));
309 select(0, NULL
, NULL
, NULL
, &tv
);
311 switch (waitpid(child
, &status
, WNOHANG
)) {
313 fprintf(stdout
, "spawn-fcgi: child spawned successfully: PID: %d\n", child
);
317 /* assume a 32bit pid_t */
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
));
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
));
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",
350 fprintf(stderr
, "spawn-fcgi: child died somehow: exit status = %d\n",
369 static int find_user_group(const char *user
, const char *group
, uid_t
*uid
, gid_t
*gid
, const char **username
) {
372 struct passwd
*my_pwd
= NULL
;
373 struct group
*my_grp
= NULL
;
376 if (username
) *username
= NULL
;
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
);
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");
393 if (username
) *username
= user
;
395 my_pwd
= getpwuid(my_uid
);
396 if (username
&& my_pwd
) *username
= my_pwd
->pw_name
;
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
);
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");
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");
429 static void show_version () {
430 (void) write_all(1, CONST_STR_LEN(
435 static void show_help () {
436 (void) write_all(1, CONST_STR_LEN(
437 "Usage: spawn-fcgi [options] [-- <fcgiapp> [fcgi app arguments]]\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" \
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" \
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
,
476 char **fcgi_app_argv
= { 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;
486 int sockbeforechroot
= 0;
487 struct sockaddr_un un
;
490 if (argc
< 2) { /* no arguments given */
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"))) {
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 */
504 fprintf(stderr
, "spawn-fcgi: invalid port: %u\n", (unsigned int) port
);
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;
523 case 'h': show_help(); return 0;
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");
539 if (0 == port
&& NULL
== unixsocket
) {
540 fprintf(stderr
, "spawn-fcgi: no socket given (use either -p or -s)\n");
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");
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");
553 if (!i_am_root
&& issetugid()) {
554 fprintf(stderr
, "spawn-fcgi: Are you nuts? Don't apply a SUID bit to this binary\n");
558 if (nofork
) pid_file
= NULL
; /* ignore pid file in no-fork mode */
561 (-1 == (pid_fd
= open(pid_file
, O_WRONLY
| O_CREAT
| O_EXCL
| O_TRUNC
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
)))) {
563 if (errno
!= EEXIST
) {
564 fprintf(stderr
, "spawn-fcgi: opening PID-file '%s' failed: %s\n",
565 pid_file
, strerror(errno
));
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
));
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",
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
));
595 const char* real_username
;
597 if (-1 == find_user_group(username
, groupname
, &uid
, &gid
, &real_username
))
600 if (-1 == find_user_group(sockusername
, sockgroupname
, &sockuid
, &sockgid
, NULL
))
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
)))
613 /* Change group before chroot, when we have access
617 if (-1 == setgid(gid
)) {
618 fprintf(stderr
, "spawn-fcgi: setgid(%i) failed: %s\n", (int) gid
, strerror(errno
));
621 if (-1 == setgroups(0, NULL
)) {
622 fprintf(stderr
, "spawn-fcgi: setgroups(0, NULL) failed: %s\n", strerror(errno
));
626 if (-1 == initgroups(real_username
, gid
)) {
627 fprintf(stderr
, "spawn-fcgi: initgroups('%s', %i) failed: %s\n", real_username
, (int) gid
, strerror(errno
));
634 if (-1 == chroot(changeroot
)) {
635 fprintf(stderr
, "spawn-fcgi: chroot('%s') failed: %s\n", changeroot
, strerror(errno
));
638 if (-1 == chdir("/")) {
639 fprintf(stderr
, "spawn-fcgi: chdir('/') failed: %s\n", strerror(errno
));
644 if (!sockbeforechroot
&& -1 == (fcgi_fd
= bind_socket(addr
, port
, unixsocket
, sockuid
, sockgid
, sockmode
, backlog
)))
647 /* drop root privs */
649 if (-1 == setuid(uid
)) {
650 fprintf(stderr
, "spawn-fcgi: setuid(%i) failed: %s\n", (int) uid
, strerror(errno
));
655 if (-1 == (fcgi_fd
= bind_socket(addr
, port
, unixsocket
, 0, 0, sockmode
, backlog
)))
659 if (fcgi_dir
&& -1 == chdir(fcgi_dir
)) {
660 fprintf(stderr
, "spawn-fcgi: chdir('%s') failed: %s\n", fcgi_dir
, strerror(errno
));
664 return fcgi_spawn_connection(fcgi_app
, fcgi_app_argv
, fcgi_fd
, fork_count
, child_count
, pid_fd
, nofork
);