root/cherokee/trunk/contrib/spawn-fcgi.c @ 1196

Revision 1196, 11.7 KB (checked in by alo, 3 years ago)
Line 
1/* File borrowed from lighttpd. I have only cleaned up the headers
2 * inclusion mess. Its original license follows:
3 *                            Alvaro Lopez Ortega <alvaro@gnu.org>
4 */
5
6/*
7Copyright (c) 2004, Jan Kneschke, incremental
8 All rights reserved.
9
10Redistribution and use in source and binary forms, with or without
11modification, are permitted provided that the following conditions are met:
12
13- Redistributions of source code must retain the above copyright notice, this
14  list of conditions and the following disclaimer.
15
16- Redistributions in binary form must reproduce the above copyright notice,
17  this list of conditions and the following disclaimer in the documentation
18  and/or other materials provided with the distribution.
19
20- Neither the name of the 'incremental' nor the names of its contributors may
21  be used to endorse or promote products derived from this software without
22  specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
34THE POSSIBILITY OF SUCH DAMAGE.
35*/
36
37
38#include "common-internal.h"
39#include "socket.h"
40#include "util.h"
41
42#ifdef HAVE_FCNTL_H
43# include <fcntl.h>
44#endif
45
46#ifdef HAVE_SYS_STAT_H
47# include <sys/stat.h>
48#endif
49
50#ifdef HAVE_SYS_WAIT_H
51# include <sys/wait.h>
52#endif
53
54#ifndef FCGI_LISTENSOCK_FILENO
55# define FCGI_LISTENSOCK_FILENO 0
56#endif
57
58
59#ifdef HAVE_SYS_UN_H
60int fcgi_spawn_connection(char *appPath, char *addr, unsigned short port, const char *unixsocket, int fork_count, int child_count, int pid_fd, int nofork) {
61        int fcgi_fd;
62        int socket_type, status;
63        struct timeval tv = { 0, 100 * 1000 };
64
65        struct sockaddr_un fcgi_addr_un;
66        struct sockaddr_in fcgi_addr_in;
67        struct sockaddr *fcgi_addr;
68
69        socklen_t servlen;
70
71        if (child_count < 2) {
72                child_count = 5;
73        }
74
75        if (child_count > 256) {
76                child_count = 256;
77        }
78
79
80        if (unixsocket) {
81                memset(&fcgi_addr, 0, sizeof(fcgi_addr));
82
83                fcgi_addr_un.sun_family = AF_UNIX;
84                strcpy(fcgi_addr_un.sun_path, unixsocket);
85
86#ifdef SUN_LEN
87                servlen = SUN_LEN(&fcgi_addr_un);
88#else
89                /* stevens says: */
90                servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family);
91#endif
92                socket_type = AF_UNIX;
93                fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
94        } else {
95                fcgi_addr_in.sin_family = AF_INET;
96                if (addr != NULL) {
97                        fcgi_addr_in.sin_addr.s_addr = inet_addr(addr);
98                } else {
99                        fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
100                }
101                fcgi_addr_in.sin_port = htons(port);
102                servlen = sizeof(fcgi_addr_in);
103
104                socket_type = AF_INET;
105                fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
106        }
107
108        if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
109                fprintf(stderr, "%s.%d\n",
110                        __FILE__, __LINE__);
111                return -1;
112        }
113
114        if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
115                /* server is not up, spawn in  */
116                pid_t child;
117                int val;
118
119                if (unixsocket) unlink(unixsocket);
120
121                close(fcgi_fd);
122
123                /* reopen socket */
124                if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
125                        fprintf(stderr, "%s.%d\n",
126                                __FILE__, __LINE__);
127                        return -1;
128                }
129
130                val = 1;
131                if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
132                        fprintf(stderr, "%s.%d\n",
133                                __FILE__, __LINE__);
134                        return -1;
135                }
136
137                /* create socket */
138                if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
139                        fprintf(stderr, "%s.%d: bind failed: %s\n",
140                                __FILE__, __LINE__,
141                                strerror(errno));
142                        return -1;
143                }
144
145                if (-1 == listen(fcgi_fd, 1024)) {
146                        fprintf(stderr, "%s.%d: fd = -1\n",
147                                __FILE__, __LINE__);
148                        return -1;
149                }
150
151                while (fork_count-- > 0) {
152
153                        if (!nofork) {
154                                child = fork();
155                        } else {
156                                child = 0;
157                        }
158
159                        switch (child) {
160                        case 0: {
161                                char cgi_childs[64];
162                                char *b;
163                                int max_fd = 0;
164
165                                int i = 0;
166
167                                /* loose control terminal */
168                                setsid();
169
170                                /* is save as we limit to 256 childs */
171                                sprintf(cgi_childs, "PHP_FCGI_CHILDREN=%d", child_count);
172
173                                if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
174                                        close(FCGI_LISTENSOCK_FILENO);
175                                        dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
176                                        close(fcgi_fd);
177                                }
178
179                                max_fd = open("/dev/null", O_RDWR);
180                                close(STDERR_FILENO);
181                                dup2(max_fd, STDERR_FILENO);
182                                close(max_fd);
183
184                                max_fd = open("/dev/null", O_RDWR);
185                                close(STDOUT_FILENO);
186                                dup2(max_fd, STDOUT_FILENO);
187                                close(max_fd);
188
189                                /* we don't need the client socket */
190                                for (i = 3; i < max_fd; i++) {
191                                        if (i != FCGI_LISTENSOCK_FILENO) close(i);
192                                }
193
194                                /* create environment */
195
196                                putenv(cgi_childs);
197
198                                /* fork and replace shell */
199                                b = malloc(strlen("exec ") + strlen(appPath) + 1);
200                                strcpy(b, "exec ");
201                                strcat(b, appPath);
202
203                                /* exec the cgi */
204                                execl("/bin/sh", "sh", "-c", b, (char *)NULL);
205
206                                exit(errno);
207
208                                break;
209                        }
210                        case -1:
211                                /* error */
212                                break;
213                        default:
214                                /* father */
215
216                                /* wait */
217                                select(0, NULL, NULL, NULL, &tv);
218
219                                switch (waitpid(child, &status, WNOHANG)) {
220                                case 0:
221                                        fprintf(stderr, "%s.%d: child spawned successfully: PID: %d\n",
222                                                __FILE__, __LINE__,
223                                                child);
224
225                                        /* write pid file */
226                                        if (pid_fd != -1) {
227                                                /* assume a 32bit pid_t */
228                                                char pidbuf[12];
229
230                                                snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child);
231
232                                                write(pid_fd, pidbuf, strlen(pidbuf));
233                                                /* avoid eol for the last one */
234                                                if (fork_count != 0) {
235                                                        write(pid_fd, "\n", 1);
236                                                }
237                                        }
238
239                                        break;
240                                case -1:
241                                        break;
242                                default:
243                                        if (WIFEXITED(status)) {
244                                                fprintf(stderr, "%s.%d: child exited with: %d, %s\n",
245                                                        __FILE__, __LINE__,
246                                                        WEXITSTATUS(status), strerror(WEXITSTATUS(status)));
247                                        } else if (WIFSIGNALED(status)) {
248                                                fprintf(stderr, "%s.%d: child signaled: %d\n",
249                                                        __FILE__, __LINE__,
250                                                        WTERMSIG(status));
251                                        } else {
252                                                fprintf(stderr, "%s.%d: child died somehow: %d\n",
253                                                        __FILE__, __LINE__,
254                                                        status);
255                                        }
256                                }
257
258                                break;
259                        }
260                }
261                close(pid_fd);
262                pid_fd = -1;
263        } else {
264                fprintf(stderr, "%s.%d: socket is already used, can't spawn\n",
265                        __FILE__, __LINE__);
266                return -1;
267        }
268
269        close(fcgi_fd);
270
271        return 0;
272}
273
274
275void show_version () {
276        char *b = "spawn-fcgi" " Cherokee-" PACKAGE_VERSION \
277" - spawns fastcgi processes\n"
278;
279        write(1, b, strlen(b));
280}
281
282void show_help () {
283        char *b = "spawn-fcgi" " Cherokee-" PACKAGE_VERSION \
284" - spawns fastcgi processes\n" \
285"usage:\n" \
286" -f <fcgiapp> filename of the fcgi-application\n" \
287" -a <addr>    bind to ip address\n" \
288" -p <port>    bind to tcp-port\n" \
289" -s <path>    bind to unix-domain socket\n" \
290" -C <childs>  (PHP only) numbers of childs to spawn (default 5)\n" \
291" -F <childs>  numbers of childs to fork (default 1)\n" \
292" -P <path>    name of PID-file for spawed process\n" \
293" -n           no fork (for daemontools)\n" \
294" -v           show version\n" \
295" -h           show this help\n" \
296"(root only)\n" \
297" -c <dir>     chroot to directory\n" \
298" -u <user>    change to user-id\n" \
299" -g <group>   change to group-id\n" \
300;
301        write(1, b, strlen(b));
302}
303
304
305int main(int argc, char **argv) {
306        char *fcgi_app = NULL, *changeroot = NULL, *username = NULL,
307               *groupname = NULL, *unixsocket = NULL, *pid_file = NULL,
308                *addr = NULL;
309        unsigned short port = 0;
310        int child_count = 5;
311        int fork_count = 1;
312        int i_am_root, o;
313        int pid_fd = -1;
314        int nofork = 0;
315        struct sockaddr_un un;
316
317        i_am_root = (getuid() == 0);
318
319       while (-1 != (o = getopt(argc, argv, "c:f:g:hna:p:u:vC:F:s:P:"))) {
320                switch(o) {
321                case 'f': fcgi_app = optarg; break;
322                case 'a': addr = optarg;/* ip addr */ break;
323                case 'p': port = strtol(optarg, NULL, 10);/* port */ break;
324                case 'C': child_count = strtol(optarg, NULL, 10);/*  */ break;
325                case 'F': fork_count = strtol(optarg, NULL, 10);/*  */ break;
326                case 's': unixsocket = optarg; /* unix-domain socket */ break;
327                case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break;
328                case 'u': if (i_am_root) { username = optarg; } /* set user */ break;
329                case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break;
330                case 'n': nofork = 1; break;
331                case 'P': pid_file = optarg; /* PID file */ break;
332                case 'v': show_version(); return 0;
333                case 'h': show_help(); return 0;
334                default:
335                        show_help();
336                        return -1;
337                }
338        }
339
340        if (fcgi_app == NULL || (port == 0 && unixsocket == NULL)) {
341                show_help();
342                return -1;
343        }
344
345        if (unixsocket && port) {
346                fprintf(stderr, "%s.%d: %s\n",
347                        __FILE__, __LINE__,
348                        "either a unix domain socket or a tcp-port, but not both\n");
349
350                return -1;
351        }
352
353        if (unixsocket && strlen(unixsocket) > sizeof(un.sun_path) - 1) {
354                fprintf(stderr, "%s.%d: %s\n",
355                        __FILE__, __LINE__,
356                        "path of the unix socket is too long\n");
357
358                return -1;
359        }
360
361        /* UID handling */
362        if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
363                /* we are setuid-root */
364
365                fprintf(stderr, "%s.%d: %s\n",
366                        __FILE__, __LINE__,
367                        "Are you nuts ? Don't apply a SUID bit to this binary\n");
368
369                return -1;
370        }
371
372        if (pid_file &&
373            (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))) {
374                struct stat st;
375                if (errno != EEXIST) {
376                        fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n",
377                                __FILE__, __LINE__,
378                                pid_file, strerror(errno));
379
380                        return -1;
381                }
382
383                /* ok, file exists */
384
385                if (0 != stat(pid_file, &st)) {
386                        fprintf(stderr, "%s.%d: stating pid-file '%s' failed: %s\n",
387                                __FILE__, __LINE__,
388                                pid_file, strerror(errno));
389
390                        return -1;
391                }
392
393                /* is it a regular file ? */
394
395                if (!S_ISREG(st.st_mode)) {
396                        fprintf(stderr, "%s.%d: pid-file exists and isn't regular file: '%s'\n",
397                                __FILE__, __LINE__,
398                                pid_file);
399
400                        return -1;
401                }
402
403                if (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
404                        fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n",
405                                __FILE__, __LINE__,
406                                pid_file, strerror(errno));
407
408                        return -1;
409                }
410        }
411
412        if (i_am_root) {
413                struct group *grp = NULL;
414                struct passwd *pwd = NULL;
415
416                /* set user and group */
417
418                if (username) {
419                        if (NULL == (pwd = getpwnam(username))) {
420                                fprintf(stderr, "%s.%d: %s, %s\n",
421                                        __FILE__, __LINE__,
422                                        "can't find username", username);
423                                return -1;
424                        }
425
426                        if (pwd->pw_uid == 0) {
427                                fprintf(stderr, "%s.%d: %s\n",
428                                        __FILE__, __LINE__,
429                                        "I will not set uid to 0\n");
430                                return -1;
431                        }
432                }
433
434                if (groupname) {
435                        if (NULL == (grp = getgrnam(groupname))) {
436                                fprintf(stderr, "%s.%d: %s %s\n",
437                                        __FILE__, __LINE__,
438                                        "can't find groupname",
439                                        groupname);
440                                return -1;
441                        }
442                        if (grp->gr_gid == 0) {
443                                fprintf(stderr, "%s.%d: %s\n",
444                                        __FILE__, __LINE__,
445                                        "I will not set gid to 0\n");
446                                return -1;
447                        }
448                }
449
450                if (changeroot) {
451                        if (-1 == chroot(changeroot)) {
452                                fprintf(stderr, "%s.%d: %s %s\n",
453                                        __FILE__, __LINE__,
454                                        "chroot failed: ", strerror(errno));
455                                return -1;
456                        }
457                        if (-1 == chdir("/")) {
458                                fprintf(stderr, "%s.%d: %s %s\n",
459                                        __FILE__, __LINE__,
460                                        "chdir failed: ", strerror(errno));
461                                return -1;
462                        }
463                }
464
465                /* drop root privs */
466                if (groupname) {
467                        setgid(grp->gr_gid);
468                }
469                if (username) {
470                        if (groupname) {
471                                initgroups(username, grp->gr_gid);
472                        }
473                        setuid(pwd->pw_uid);
474                }
475        }
476
477       return fcgi_spawn_connection(fcgi_app, addr, port, unixsocket, fork_count, child_count, pid_fd, nofork);
478}
479#else
480int main() {
481        return -1;
482}
483#endif
Note: See TracBrowser for help on using the browser.