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