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

Revision 1196, 11.7 kB (checked in by alo, 9 months 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 /*
7 Copyright (c) 2004, Jan Kneschke, incremental
8  All rights reserved.
9
10 Redistribution and use in source and binary forms, with or without
11 modification, 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
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
34 THE 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
60 int 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
275 void show_version () {
276         char *b = "spawn-fcgi" " Cherokee-" PACKAGE_VERSION \
277 " - spawns fastcgi processes\n"
278 ;
279         write(1, b, strlen(b));
280 }
281
282 void 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
305 int 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
480 int main() {
481         return -1;
482 }
483 #endif
Note: See TracBrowser for help on using the browser.