callcgi: Fixed possible deadlock problem on aborted requests.
[ashd.git] / src / callscgi.c
... / ...
CommitLineData
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/*
20 * XXX: All the various ways to start a child process makes this
21 * program quite ugly at the moment. It is unclear whether it is
22 * meaningfully possible to unify them better than they currently are.
23 */
24
25#include <stdlib.h>
26#include <stdio.h>
27#include <unistd.h>
28#include <string.h>
29#include <fcntl.h>
30#include <ctype.h>
31#include <sys/socket.h>
32#include <sys/un.h>
33#include <netinet/in.h>
34#include <netdb.h>
35#include <signal.h>
36#include <errno.h>
37
38#ifdef HAVE_CONFIG_H
39#include <config.h>
40#endif
41#include <utils.h>
42#include <req.h>
43#include <resp.h>
44#include <log.h>
45#include <mt.h>
46#include <mtio.h>
47
48static char **progspec;
49static char *sockid, *unspec, *inspec;
50static int nolisten;
51static struct sockaddr *curaddr;
52static size_t caddrlen;
53static int cafamily, isanon;
54static pid_t child;
55
56static struct addrinfo *resolv(int flags)
57{
58 int ret;
59 struct addrinfo *ai, h;
60 char *name, *srv, *p;
61
62 if((p = strchr(inspec, ':')) != NULL) {
63 name = smalloc(p - inspec + 1);
64 memcpy(name, inspec, p - inspec);
65 name[p - inspec] = 0;
66 srv = p + 1;
67 } else {
68 name = sstrdup("localhost");
69 srv = inspec;
70 }
71 memset(&h, 0, sizeof(h));
72 h.ai_family = AF_UNSPEC;
73 h.ai_socktype = SOCK_STREAM;
74 h.ai_flags = flags;
75 ret = getaddrinfo(name, srv, &h, &ai);
76 free(name);
77 if(ret != 0) {
78 flog(LOG_ERR, "could not resolve TCP specification `%s': %s", inspec, gai_strerror(ret));
79 exit(1);
80 }
81 return(ai);
82}
83
84static char *mksockid(char *sockid)
85{
86 char *home;
87
88 home = getenv("HOME");
89 if(home && !access(sprintf3("%s/.ashd/sockets/", home), X_OK))
90 return(sprintf3("%s/.ashd/sockets/scgi-p-%s", home, sockid));
91 return(sprintf3("/tmp/scgi-%i-%s", getuid(), sockid));
92}
93
94static char *mkanonid(void)
95{
96 char *home;
97 char *tmpl;
98 int fd;
99
100 home = getenv("HOME");
101 if(home && !access(sprintf3("%s/.ashd/sockets/", home), X_OK))
102 tmpl = sprintf2("%s/.ashd/sockets/scgi-a-XXXXXX", home);
103 else
104 tmpl = sprintf2("/tmp/scgi-a-%i-XXXXXX", getuid());
105 if((fd = mkstemp(tmpl)) < 0) {
106 flog(LOG_ERR, "could not create anonymous socket `%s': %s", tmpl, strerror(errno));
107 exit(1);
108 }
109 close(fd);
110 unlink(tmpl);
111 return(tmpl);
112}
113
114static void setupchild(void)
115{
116 /* PHP appears to not expect to inherit SIGCHLD set to SIG_IGN, so
117 * reset it for it. */
118 signal(SIGCHLD, SIG_DFL);
119}
120
121static void startlisten(void)
122{
123 int fd;
124 struct addrinfo *ai, *cai;
125 char *unpath;
126 struct sockaddr_un unm;
127 char *aname;
128
129 isanon = 0;
130 if(inspec != NULL) {
131 fd = -1;
132 for(cai = ai = resolv(AI_PASSIVE); cai != NULL; cai = cai->ai_next) {
133 if((fd = socket(cai->ai_family, cai->ai_socktype, cai->ai_protocol)) < 0)
134 continue;
135 if(bind(fd, cai->ai_addr, cai->ai_addrlen)) {
136 close(fd);
137 fd = -1;
138 continue;
139 }
140 if(listen(fd, 128)) {
141 close(fd);
142 fd = -1;
143 continue;
144 }
145 break;
146 }
147 freeaddrinfo(ai);
148 if(fd < 0) {
149 flog(LOG_ERR, "could not bind to specified TCP address: %s", strerror(errno));
150 exit(1);
151 }
152 } else if((unspec != NULL) || (sockid != NULL)) {
153 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
154 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
155 exit(1);
156 }
157 if(unspec != NULL)
158 unpath = unspec;
159 else
160 unpath = mksockid(sockid);
161 unlink(unpath);
162 unm.sun_family = AF_UNIX;
163 strcpy(unm.sun_path, unpath);
164 if(bind(fd, (struct sockaddr *)&unm, sizeof(unm))) {
165 flog(LOG_ERR, "could not bind Unix socket to `%s': %s", unspec, strerror(errno));
166 exit(1);
167 }
168 if(listen(fd, 128)) {
169 flog(LOG_ERR, "listen: %s", strerror(errno));
170 exit(1);
171 }
172 } else {
173 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
174 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
175 exit(1);
176 }
177 memset(&unm, 0, sizeof(unm));
178 aname = mkanonid();
179 unm.sun_family = AF_UNIX;
180 strcpy(unm.sun_path, aname);
181 free(aname);
182 if(bind(fd, (struct sockaddr *)&unm, sizeof(unm))) {
183 flog(LOG_ERR, "could not bind Unix socket to `%s': %s", unspec, strerror(errno));
184 exit(1);
185 }
186 if(listen(fd, 128)) {
187 flog(LOG_ERR, "listen: %s", strerror(errno));
188 exit(1);
189 }
190
191 curaddr = smalloc(caddrlen = sizeof(unm));
192 memcpy(curaddr, &unm, sizeof(unm));
193 cafamily = AF_UNIX;
194 isanon = 1;
195 }
196 if((child = fork()) < 0) {
197 flog(LOG_ERR, "could not fork: %s", strerror(errno));
198 exit(1);
199 }
200 if(child == 0) {
201 setupchild();
202 dup2(fd, 0);
203 close(fd);
204 execvp(*progspec, progspec);
205 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
206 _exit(127);
207 }
208 close(fd);
209}
210
211static void startnolisten(void)
212{
213 int fd;
214
215 if((child = fork()) < 0) {
216 flog(LOG_ERR, "could not fork: %s", strerror(errno));
217 exit(1);
218 }
219 if(child == 0) {
220 setupchild();
221 if((fd = open("/dev/null", O_RDONLY)) < 0) {
222 flog(LOG_ERR, "/dev/null: %s", strerror(errno));
223 _exit(127);
224 }
225 dup2(fd, 0);
226 close(fd);
227 execvp(*progspec, progspec);
228 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
229 _exit(127);
230 }
231}
232
233static int sconnect(void)
234{
235 int fd;
236 int err;
237 socklen_t errlen;
238
239 fd = socket(cafamily, SOCK_STREAM, 0);
240 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
241 while(1) {
242 if(connect(fd, curaddr, caddrlen)) {
243 if(errno == EINPROGRESS) {
244 block(fd, EV_WRITE, 30);
245 errlen = sizeof(err);
246 if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) || ((errno = err) != 0)) {
247 close(fd);
248 return(-1);
249 }
250 return(fd);
251 }
252 close(fd);
253 return(-1);
254 }
255 return(fd);
256 }
257}
258
259static int econnect(void)
260{
261 int fd;
262 struct addrinfo *ai, *cai;
263 int tries;
264 char *unpath;
265 struct sockaddr_un unm;
266
267 tries = 0;
268retry:
269 if(inspec != NULL) {
270 fd = -1;
271 for(cai = ai = resolv(0); cai != NULL; cai = cai->ai_next) {
272 if((fd = socket(cai->ai_family, cai->ai_socktype, cai->ai_protocol)) < 0)
273 continue;
274 if(connect(fd, cai->ai_addr, cai->ai_addrlen)) {
275 close(fd);
276 fd = -1;
277 continue;
278 }
279 break;
280 }
281 if(fd < 0) {
282 if(tries++ < nolisten) {
283 sleep(1);
284 goto retry;
285 }
286 flog(LOG_ERR, "could not connect to specified TCP address: %s", strerror(errno));
287 exit(1);
288 }
289 curaddr = smalloc(caddrlen = cai->ai_addrlen);
290 memcpy(curaddr, cai->ai_addr, caddrlen);
291 cafamily = cai->ai_family;
292 isanon = 0;
293 freeaddrinfo(ai);
294 return(fd);
295 } else if((unspec != NULL) || (sockid != NULL)) {
296 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
297 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
298 exit(1);
299 }
300 if(unspec != NULL)
301 unpath = unspec;
302 else
303 unpath = mksockid(sockid);
304 unlink(unpath);
305 unm.sun_family = AF_UNIX;
306 strcpy(unm.sun_path, unpath);
307 if(connect(fd, (struct sockaddr *)&unm, sizeof(unm))) {
308 close(fd);
309 if(tries++ < nolisten) {
310 sleep(1);
311 goto retry;
312 }
313 flog(LOG_ERR, "could not connect to Unix socket `%s': %s", unspec, strerror(errno));
314 exit(1);
315 }
316 curaddr = smalloc(caddrlen = sizeof(unm));
317 memcpy(curaddr, &unm, sizeof(unm));
318 cafamily = AF_UNIX;
319 isanon = 0;
320 return(fd);
321 } else {
322 flog(LOG_ERR, "callscgi: cannot use an anonymous socket without a program to start");
323 exit(1);
324 }
325}
326
327static int startconn(void)
328{
329 if(*progspec) {
330 if(nolisten == 0)
331 startlisten();
332 else
333 startnolisten();
334 }
335 if(curaddr != NULL)
336 return(sconnect());
337 return(econnect());
338}
339
340static void killcuraddr(void)
341{
342 if(curaddr == NULL)
343 return;
344 if(isanon) {
345 unlink(((struct sockaddr_un *)curaddr)->sun_path);
346 if(child > 0)
347 kill(child, SIGTERM);
348 }
349 free(curaddr);
350 curaddr = NULL;
351}
352
353static int reconn(void)
354{
355 int fd;
356
357 if(curaddr != NULL) {
358 if((fd = sconnect()) >= 0)
359 return(fd);
360 killcuraddr();
361 }
362 return(startconn());
363}
364
365static off_t passdata(FILE *in, FILE *out)
366{
367 size_t read;
368 off_t total;
369 char buf[8192];
370
371 total = 0;
372 while(!feof(in)) {
373 read = fread(buf, 1, sizeof(buf), in);
374 if(ferror(in))
375 return(-1);
376 if(fwrite(buf, 1, read, out) != read)
377 return(-1);
378 total += read;
379 }
380 return(total);
381}
382
383static void bufaddenv(struct charbuf *dst, char *name, char *fmt, ...)
384{
385 va_list args;
386
387 bufcatstr2(*dst, name);
388 va_start(args, fmt);
389 bvprintf(dst, fmt, args);
390 va_end(args);
391 bufadd(*dst, 0);
392}
393
394static char *absolutify(char *file)
395{
396 static int inited = 0;
397 static char cwd[1024];
398
399 if(*file != '/') {
400 if(!inited) {
401 getcwd(cwd, sizeof(cwd));
402 inited = 1;
403 }
404 return(sprintf2("%s/%s", cwd, file));
405 }
406 return(sstrdup(file));
407}
408
409/* Mostly copied from callcgi. */
410static void mkcgienv(struct hthead *req, struct charbuf *dst)
411{
412 int i;
413 char *url, *pi, *tmp, *qp, *h, *p;
414
415 bufaddenv(dst, "SERVER_SOFTWARE", "ashd/%s", VERSION);
416 bufaddenv(dst, "GATEWAY_INTERFACE", "CGI/1.1");
417 bufaddenv(dst, "SCGI", "1");
418 bufaddenv(dst, "SERVER_PROTOCOL", "%s", req->ver);
419 bufaddenv(dst, "REQUEST_METHOD", "%s", req->method);
420 bufaddenv(dst, "REQUEST_URI", "%s", req->url);
421 url = sstrdup(req->url);
422 if((qp = strchr(url, '?')) != NULL)
423 *(qp++) = 0;
424 /* XXX: This is an ugly hack (I think), but though I can think of
425 * several alternatives, none seem to be better. */
426 if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
427 !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
428 url[strlen(url) - strlen(req->rest)] = 0;
429 }
430 if((pi = unquoteurl(req->rest)) == NULL)
431 pi = sstrdup(req->rest);
432 if(!strcmp(url, "/")) {
433 /* This seems to be normal CGI behavior, but see callcgi.c for
434 * details. */
435 url[0] = 0;
436 pi = sprintf2("/%s", tmp = pi);
437 free(tmp);
438 }
439 bufaddenv(dst, "PATH_INFO", "%s", pi);
440 bufaddenv(dst, "SCRIPT_NAME", "%s", url);
441 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
442 free(pi);
443 free(url);
444 if((h = getheader(req, "Host")) != NULL)
445 bufaddenv(dst, "SERVER_NAME", "%s", h);
446 if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
447 bufaddenv(dst, "SERVER_ADDR", "%s", h);
448 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
449 bufaddenv(dst, "SERVER_PORT", "%s", h);
450 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
451 bufaddenv(dst, "REMOTE_USER", "%s", h);
452 if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
453 bufaddenv(dst, "HTTPS", "on");
454 if((h = getheader(req, "X-Ash-Address")) != NULL)
455 bufaddenv(dst, "REMOTE_ADDR", "%s", h);
456 if((h = getheader(req, "X-Ash-Port")) != NULL)
457 bufaddenv(dst, "REMOTE_PORT", "%s", h);
458 if((h = getheader(req, "Content-Type")) != NULL)
459 bufaddenv(dst, "CONTENT_TYPE", "%s", h);
460 if((h = getheader(req, "Content-Length")) != NULL)
461 bufaddenv(dst, "CONTENT_LENGTH", "%s", h);
462 else
463 bufaddenv(dst, "CONTENT_LENGTH", "0");
464 if((h = getheader(req, "X-Ash-File")) != NULL) {
465 h = absolutify(h);
466 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
467 free(h);
468 }
469 for(i = 0; i < req->noheaders; i++) {
470 h = sprintf2("HTTP_%s", req->headers[i][0]);
471 for(p = h; *p; p++) {
472 if(isalnum(*p))
473 *p = toupper(*p);
474 else
475 *p = '_';
476 }
477 bufcatstr2(*dst, h);
478 free(h);
479 bufcatstr2(*dst, req->headers[i][1]);
480 }
481}
482
483static struct hthead *parseresp(FILE *in)
484{
485 struct hthead *resp;
486 char *st, *p;
487
488 omalloc(resp);
489 resp->ver = sstrdup("HTTP/1.1");
490 if(parseheaders(resp, in)) {
491 freehthead(resp);
492 return(NULL);
493 }
494 if((st = getheader(resp, "Status")) != NULL) {
495 if((p = strchr(st, ' ')) != NULL) {
496 *(p++) = 0;
497 resp->code = atoi(st);
498 resp->msg = sstrdup(p);
499 } else {
500 resp->code = atoi(st);
501 resp->msg = sstrdup(httpdefstatus(resp->code));
502 }
503 headrmheader(resp, "Status");
504 } else if(getheader(resp, "Location")) {
505 resp->code = 303;
506 resp->msg = sstrdup("See Other");
507 } else {
508 resp->code = 200;
509 resp->msg = sstrdup("OK");
510 }
511 return(resp);
512}
513
514static void serve(struct muth *muth, va_list args)
515{
516 vavar(struct hthead *, req);
517 vavar(int, fd);
518 vavar(int, sfd);
519 FILE *is, *os;
520 struct charbuf head;
521 struct hthead *resp;
522
523 sfd = reconn();
524 is = mtstdopen(fd, 1, 60, "r+", NULL);
525 os = mtstdopen(sfd, 1, 600, "r+", NULL);
526
527 bufinit(head);
528 mkcgienv(req, &head);
529 fprintf(os, "%zi:", head.d);
530 fwrite(head.b, head.d, 1, os);
531 fputc(',', os);
532 buffree(head);
533 if(passdata(is, os) < 0)
534 goto out;
535
536 if((resp = parseresp(os)) == NULL)
537 goto out;
538 writeresp(is, resp);
539 freehthead(resp);
540 fputc('\n', is);
541 if(passdata(os, is) < 0)
542 goto out;
543
544out:
545 freehthead(req);
546 fclose(is);
547 fclose(os);
548}
549
550static void listenloop(struct muth *muth, va_list args)
551{
552 vavar(int, lfd);
553 int fd;
554 struct hthead *req;
555
556 while(1) {
557 block(0, EV_READ, 0);
558 if((fd = recvreq(lfd, &req)) < 0) {
559 if(errno != 0)
560 flog(LOG_ERR, "recvreq: %s", strerror(errno));
561 break;
562 }
563 mustart(serve, req, fd);
564 }
565}
566
567static void sigign(int sig)
568{
569}
570
571static void sigexit(int sig)
572{
573 shutdown(0, SHUT_RDWR);
574}
575
576static void usage(FILE *out)
577{
578 fprintf(out, "usage: callscgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
579}
580
581int main(int argc, char **argv)
582{
583 int c;
584
585 while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
586 switch(c) {
587 case 'h':
588 usage(stdout);
589 exit(0);
590 case 'N':
591 nolisten = atoi(optarg);
592 break;
593 case 'i':
594 sockid = optarg;
595 break;
596 case 'u':
597 unspec = optarg;
598 break;
599 case 't':
600 inspec = optarg;
601 break;
602 default:
603 usage(stderr);
604 exit(1);
605 }
606 }
607 progspec = argv + optind;
608 if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
609 flog(LOG_ERR, "callscgi: at most one of -i, -u or -t may be given");
610 exit(1);
611 }
612 signal(SIGCHLD, SIG_IGN);
613 signal(SIGPIPE, sigign);
614 signal(SIGINT, sigexit);
615 signal(SIGTERM, sigexit);
616 mustart(listenloop, 0);
617 ioloop();
618 killcuraddr();
619 return(0);
620}