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