ef31c4c6db74934c30a5f829a1570d96489c4071
[ashd.git] / src / callscgi.c
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
48 static char **progspec;
49 static char *sockid, *unspec, *inspec;
50 static int nolisten;
51 static struct sockaddr *curaddr;
52 static size_t caddrlen;
53 static int cafamily, isanon;
54 static pid_t child;
55
56 static 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
84 static 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
94 static 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
114 static 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
121 static 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
211 static 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
233 static 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             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         }
257         close(fd);
258         return(-1);
259     }
260 }
261
262 static 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;
271 retry:
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         }
284         if(fd < 0) {
285             if(tries++ < nolisten) {
286                 sleep(1);
287                 goto retry;
288             }
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 {
325         flog(LOG_ERR, "callscgi: cannot use an anonymous socket without a program to start");
326         exit(1);
327     }
328 }
329
330 static 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
343 static 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
356 static 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
368 static 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
386 static 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
397 static 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. */
413 static void mkcgienv(struct hthead *req, struct charbuf *dst)
414 {
415     int i;
416     char *url, *pi, *tmp, *qp, *h, *p;
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);
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. */
429     if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
430        !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
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);
441     }
442     bufaddenv(dst, "PATH_INFO", "%s", pi);
443     bufaddenv(dst, "SCRIPT_NAME", "%s", url);
444     bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
445     free(pi);
446     free(url);
447     if((h = getheader(req, "Host")) != NULL)
448         bufaddenv(dst, "SERVER_NAME", "%s", h);
449     if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
450         bufaddenv(dst, "SERVER_ADDR", "%s", h);
451     if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
452         bufaddenv(dst, "SERVER_PORT", "%s", h);
453     if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
454         bufaddenv(dst, "REMOTE_USER", "%s", h);
455     if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
456         bufaddenv(dst, "HTTPS", "on");
457     if((h = getheader(req, "X-Ash-Address")) != NULL)
458         bufaddenv(dst, "REMOTE_ADDR", "%s", h);
459     if((h = getheader(req, "X-Ash-Port")) != NULL)
460         bufaddenv(dst, "REMOTE_PORT", "%s", h);
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");
467     if((h = getheader(req, "X-Ash-File")) != NULL) {
468         h = absolutify(h);
469         bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
470         free(h);
471     }
472     for(i = 0; i < req->noheaders; i++) {
473         h = sprintf2("HTTP_%s", req->headers[i][0]);
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
486 static 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);
504             resp->msg = sstrdup(httpdefstatus(resp->code));
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
517 static 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     
526     is = mtstdopen(fd, 1, 60, "r+", NULL);
527     os = mtstdopen(sfd, 1, 600, "r+", NULL);
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     
546 out:
547     freehthead(req);
548     fclose(is);
549     fclose(os);
550 }
551
552 static void listenloop(struct muth *muth, va_list args)
553 {
554     vavar(int, lfd);
555     int fd, sfd;
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         }
565         if((sfd = reconn()) < 0) {
566             close(fd);
567             freehthead(req);
568             continue;
569         }
570         mustart(serve, req, fd, sfd);
571     }
572 }
573
574 static void sigign(int sig)
575 {
576 }
577
578 static void sigexit(int sig)
579 {
580     shutdown(0, SHUT_RDWR);
581 }
582
583 static void usage(FILE *out)
584 {
585     fprintf(out, "usage: callscgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
586 }
587
588 int 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) {
616         flog(LOG_ERR, "callscgi: at most one of -i, -u or -t may be given");
617         exit(1);
618     }
619     signal(SIGCHLD, SIG_IGN);
620     signal(SIGPIPE, sigign);
621     signal(SIGINT, sigexit);
622     signal(SIGTERM, sigexit);
623     mustart(listenloop, 0);
624     ioloop();
625     killcuraddr();
626     return(0);
627 }