call[fs]cgi: Handle serialized child connections asynchronously.
[ashd.git] / src / callscgi.c
CommitLineData
f89ce57a
FT
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>
cc5adcf9 35#include <signal.h>
f89ce57a
FT
36#include <errno.h>
37
38#ifdef HAVE_CONFIG_H
39#include <config.h>
40#endif
41#include <utils.h>
42#include <req.h>
62e76c42 43#include <resp.h>
f89ce57a
FT
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))
1376c0cd 102 tmpl = sprintf2("%s/.ashd/sockets/scgi-a-XXXXXX", home);
f89ce57a
FT
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 }
1376c0cd 109 close(fd);
f89ce57a
FT
110 unlink(tmpl);
111 return(tmpl);
112}
113
b1b0fe0f
FT
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
f89ce57a
FT
121static void startlisten(void)
122{
b1b0fe0f 123 int fd;
f89ce57a
FT
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) {
b1b0fe0f 201 setupchild();
f89ce57a 202 dup2(fd, 0);
b1b0fe0f 203 close(fd);
f89ce57a 204 execvp(*progspec, progspec);
7b9c1cff 205 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
107aa2ef 206 _exit(127);
f89ce57a
FT
207 }
208 close(fd);
209}
210
211static void startnolisten(void)
212{
b1b0fe0f 213 int fd;
f89ce57a
FT
214
215 if((child = fork()) < 0) {
216 flog(LOG_ERR, "could not fork: %s", strerror(errno));
217 exit(1);
218 }
219 if(child == 0) {
b1b0fe0f 220 setupchild();
02cb2a3d
FT
221 if((fd = open("/dev/null", O_RDONLY)) < 0) {
222 flog(LOG_ERR, "/dev/null: %s", strerror(errno));
107aa2ef 223 _exit(127);
02cb2a3d
FT
224 }
225 dup2(fd, 0);
226 close(fd);
f89ce57a 227 execvp(*progspec, progspec);
7b9c1cff 228 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
107aa2ef 229 _exit(127);
f89ce57a
FT
230 }
231}
232
233static int sconnect(void)
234{
235 int fd;
b3e49678
FT
236 int err;
237 socklen_t errlen;
f89ce57a
FT
238
239 fd = socket(cafamily, SOCK_STREAM, 0);
b3e49678
FT
240 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
241 while(1) {
242 if(!connect(fd, curaddr, caddrlen))
243 return(fd);
244 if(errno == EAGAIN) {
245 block(-1, 0, 1);
246 continue;
247 }
248 if(errno == EINPROGRESS) {
249 block(fd, EV_WRITE, 30);
250 errlen = sizeof(err);
251 if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) || ((errno = err) != 0)) {
252 close(fd);
253 return(-1);
254 }
255 return(fd);
256 }
8bff711b
FT
257 close(fd);
258 return(-1);
f89ce57a
FT
259 }
260}
261
262static int econnect(void)
263{
264 int fd;
265 struct addrinfo *ai, *cai;
266 int tries;
267 char *unpath;
268 struct sockaddr_un unm;
269
270 tries = 0;
271retry:
272 if(inspec != NULL) {
273 fd = -1;
274 for(cai = ai = resolv(0); cai != NULL; cai = cai->ai_next) {
275 if((fd = socket(cai->ai_family, cai->ai_socktype, cai->ai_protocol)) < 0)
276 continue;
277 if(connect(fd, cai->ai_addr, cai->ai_addrlen)) {
278 close(fd);
279 fd = -1;
280 continue;
281 }
282 break;
283 }
f89ce57a 284 if(fd < 0) {
02cb2a3d
FT
285 if(tries++ < nolisten) {
286 sleep(1);
287 goto retry;
288 }
f89ce57a
FT
289 flog(LOG_ERR, "could not connect to specified TCP address: %s", strerror(errno));
290 exit(1);
291 }
292 curaddr = smalloc(caddrlen = cai->ai_addrlen);
293 memcpy(curaddr, cai->ai_addr, caddrlen);
294 cafamily = cai->ai_family;
295 isanon = 0;
296 freeaddrinfo(ai);
297 return(fd);
298 } else if((unspec != NULL) || (sockid != NULL)) {
299 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
300 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
301 exit(1);
302 }
303 if(unspec != NULL)
304 unpath = unspec;
305 else
306 unpath = mksockid(sockid);
307 unlink(unpath);
308 unm.sun_family = AF_UNIX;
309 strcpy(unm.sun_path, unpath);
310 if(connect(fd, (struct sockaddr *)&unm, sizeof(unm))) {
311 close(fd);
312 if(tries++ < nolisten) {
313 sleep(1);
314 goto retry;
315 }
316 flog(LOG_ERR, "could not connect to Unix socket `%s': %s", unspec, strerror(errno));
317 exit(1);
318 }
319 curaddr = smalloc(caddrlen = sizeof(unm));
320 memcpy(curaddr, &unm, sizeof(unm));
321 cafamily = AF_UNIX;
322 isanon = 0;
323 return(fd);
324 } else {
425eccf6 325 flog(LOG_ERR, "callscgi: cannot use an anonymous socket without a program to start");
f89ce57a
FT
326 exit(1);
327 }
328}
329
330static int startconn(void)
331{
332 if(*progspec) {
333 if(nolisten == 0)
334 startlisten();
335 else
336 startnolisten();
337 }
338 if(curaddr != NULL)
339 return(sconnect());
340 return(econnect());
341}
342
343static void killcuraddr(void)
344{
345 if(curaddr == NULL)
346 return;
347 if(isanon) {
348 unlink(((struct sockaddr_un *)curaddr)->sun_path);
349 if(child > 0)
350 kill(child, SIGTERM);
351 }
352 free(curaddr);
353 curaddr = NULL;
354}
355
356static int reconn(void)
357{
358 int fd;
359
360 if(curaddr != NULL) {
361 if((fd = sconnect()) >= 0)
362 return(fd);
363 killcuraddr();
364 }
365 return(startconn());
366}
367
368static off_t passdata(FILE *in, FILE *out)
369{
370 size_t read;
371 off_t total;
372 char buf[8192];
373
374 total = 0;
375 while(!feof(in)) {
376 read = fread(buf, 1, sizeof(buf), in);
377 if(ferror(in))
378 return(-1);
379 if(fwrite(buf, 1, read, out) != read)
380 return(-1);
381 total += read;
382 }
383 return(total);
384}
385
386static void bufaddenv(struct charbuf *dst, char *name, char *fmt, ...)
387{
388 va_list args;
389
390 bufcatstr2(*dst, name);
391 va_start(args, fmt);
392 bvprintf(dst, fmt, args);
393 va_end(args);
394 bufadd(*dst, 0);
395}
396
397static char *absolutify(char *file)
398{
399 static int inited = 0;
400 static char cwd[1024];
401
402 if(*file != '/') {
403 if(!inited) {
404 getcwd(cwd, sizeof(cwd));
405 inited = 1;
406 }
407 return(sprintf2("%s/%s", cwd, file));
408 }
409 return(sstrdup(file));
410}
411
412/* Mostly copied from callcgi. */
413static void mkcgienv(struct hthead *req, struct charbuf *dst)
414{
415 int i;
53d666ca 416 char *url, *pi, *tmp, *qp, *h, *p;
f89ce57a
FT
417
418 bufaddenv(dst, "SERVER_SOFTWARE", "ashd/%s", VERSION);
419 bufaddenv(dst, "GATEWAY_INTERFACE", "CGI/1.1");
420 bufaddenv(dst, "SCGI", "1");
421 bufaddenv(dst, "SERVER_PROTOCOL", "%s", req->ver);
422 bufaddenv(dst, "REQUEST_METHOD", "%s", req->method);
423 bufaddenv(dst, "REQUEST_URI", "%s", req->url);
f89ce57a
FT
424 url = sstrdup(req->url);
425 if((qp = strchr(url, '?')) != NULL)
426 *(qp++) = 0;
427 /* XXX: This is an ugly hack (I think), but though I can think of
428 * several alternatives, none seem to be better. */
2d65add0
FT
429 if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
430 !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
53d666ca
FT
431 url[strlen(url) - strlen(req->rest)] = 0;
432 }
433 if((pi = unquoteurl(req->rest)) == NULL)
434 pi = sstrdup(req->rest);
435 if(!strcmp(url, "/")) {
436 /* This seems to be normal CGI behavior, but see callcgi.c for
437 * details. */
438 url[0] = 0;
439 pi = sprintf2("/%s", tmp = pi);
440 free(tmp);
f89ce57a 441 }
a80c8f02
FT
442 bufaddenv(dst, "PATH_INFO", "%s", pi);
443 bufaddenv(dst, "SCRIPT_NAME", "%s", url);
f89ce57a 444 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
53d666ca 445 free(pi);
b77076c4 446 free(url);
f89ce57a
FT
447 if((h = getheader(req, "Host")) != NULL)
448 bufaddenv(dst, "SERVER_NAME", "%s", h);
1af656d2
FT
449 if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
450 bufaddenv(dst, "SERVER_ADDR", "%s", h);
f89ce57a
FT
451 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
452 bufaddenv(dst, "SERVER_PORT", "%s", h);
381f9919
FT
453 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
454 bufaddenv(dst, "REMOTE_USER", "%s", h);
9230295c 455 if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
f89ce57a
FT
456 bufaddenv(dst, "HTTPS", "on");
457 if((h = getheader(req, "X-Ash-Address")) != NULL)
458 bufaddenv(dst, "REMOTE_ADDR", "%s", h);
1af656d2
FT
459 if((h = getheader(req, "X-Ash-Port")) != NULL)
460 bufaddenv(dst, "REMOTE_PORT", "%s", h);
f89ce57a
FT
461 if((h = getheader(req, "Content-Type")) != NULL)
462 bufaddenv(dst, "CONTENT_TYPE", "%s", h);
463 if((h = getheader(req, "Content-Length")) != NULL)
464 bufaddenv(dst, "CONTENT_LENGTH", "%s", h);
465 else
466 bufaddenv(dst, "CONTENT_LENGTH", "0");
e25bf4f9
FT
467 if((h = getheader(req, "X-Ash-File")) != NULL) {
468 h = absolutify(h);
469 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
470 free(h);
471 }
f89ce57a 472 for(i = 0; i < req->noheaders; i++) {
02cb2a3d 473 h = sprintf2("HTTP_%s", req->headers[i][0]);
f89ce57a
FT
474 for(p = h; *p; p++) {
475 if(isalnum(*p))
476 *p = toupper(*p);
477 else
478 *p = '_';
479 }
480 bufcatstr2(*dst, h);
481 free(h);
482 bufcatstr2(*dst, req->headers[i][1]);
483 }
484}
485
f89ce57a
FT
486static struct hthead *parseresp(FILE *in)
487{
488 struct hthead *resp;
489 char *st, *p;
490
491 omalloc(resp);
492 resp->ver = sstrdup("HTTP/1.1");
493 if(parseheaders(resp, in)) {
494 freehthead(resp);
495 return(NULL);
496 }
497 if((st = getheader(resp, "Status")) != NULL) {
498 if((p = strchr(st, ' ')) != NULL) {
499 *(p++) = 0;
500 resp->code = atoi(st);
501 resp->msg = sstrdup(p);
502 } else {
503 resp->code = atoi(st);
62e76c42 504 resp->msg = sstrdup(httpdefstatus(resp->code));
f89ce57a
FT
505 }
506 headrmheader(resp, "Status");
507 } else if(getheader(resp, "Location")) {
508 resp->code = 303;
509 resp->msg = sstrdup("See Other");
510 } else {
511 resp->code = 200;
512 resp->msg = sstrdup("OK");
513 }
514 return(resp);
515}
516
517static void serve(struct muth *muth, va_list args)
518{
519 vavar(struct hthead *, req);
520 vavar(int, fd);
521 vavar(int, sfd);
522 FILE *is, *os;
523 struct charbuf head;
524 struct hthead *resp;
525
b71ad67f
FT
526 is = mtstdopen(fd, 1, 60, "r+", NULL);
527 os = mtstdopen(sfd, 1, 600, "r+", NULL);
f89ce57a
FT
528
529 bufinit(head);
530 mkcgienv(req, &head);
531 fprintf(os, "%zi:", head.d);
532 fwrite(head.b, head.d, 1, os);
533 fputc(',', os);
534 buffree(head);
535 if(passdata(is, os) < 0)
536 goto out;
537
538 if((resp = parseresp(os)) == NULL)
539 goto out;
540 writeresp(is, resp);
541 freehthead(resp);
542 fputc('\n', is);
543 if(passdata(os, is) < 0)
544 goto out;
545
546out:
547 freehthead(req);
548 fclose(is);
549 fclose(os);
550}
551
552static void listenloop(struct muth *muth, va_list args)
553{
554 vavar(int, lfd);
b3e49678 555 int fd, sfd;
f89ce57a
FT
556 struct hthead *req;
557
558 while(1) {
559 block(0, EV_READ, 0);
560 if((fd = recvreq(lfd, &req)) < 0) {
561 if(errno != 0)
562 flog(LOG_ERR, "recvreq: %s", strerror(errno));
563 break;
564 }
b3e49678
FT
565 if((sfd = reconn()) < 0) {
566 close(fd);
567 freehthead(req);
568 continue;
569 }
570 mustart(serve, req, fd, sfd);
f89ce57a
FT
571 }
572}
573
fd735432
FT
574static void sigign(int sig)
575{
576}
577
9c86e336
FT
578static void sigexit(int sig)
579{
254eb937 580 shutdown(0, SHUT_RDWR);
9c86e336
FT
581}
582
f89ce57a
FT
583static void usage(FILE *out)
584{
425eccf6 585 fprintf(out, "usage: callscgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
f89ce57a
FT
586}
587
588int main(int argc, char **argv)
589{
590 int c;
591
592 while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
593 switch(c) {
594 case 'h':
595 usage(stdout);
596 exit(0);
597 case 'N':
598 nolisten = atoi(optarg);
599 break;
600 case 'i':
601 sockid = optarg;
602 break;
603 case 'u':
604 unspec = optarg;
605 break;
606 case 't':
607 inspec = optarg;
608 break;
609 default:
610 usage(stderr);
611 exit(1);
612 }
613 }
614 progspec = argv + optind;
615 if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
425eccf6 616 flog(LOG_ERR, "callscgi: at most one of -i, -u or -t may be given");
f89ce57a
FT
617 exit(1);
618 }
619 signal(SIGCHLD, SIG_IGN);
fd735432 620 signal(SIGPIPE, sigign);
9c86e336
FT
621 signal(SIGINT, sigexit);
622 signal(SIGTERM, sigexit);
f89ce57a 623 mustart(listenloop, 0);
f89ce57a 624 ioloop();
d06b6013 625 killcuraddr();
f89ce57a
FT
626 return(0);
627}