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