dirplex, patplex: Added ability to set arbitrary headers based on match.
[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", home);
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     close(fd);
109     unlink(tmpl);
110     return(tmpl);
111 }
112
113 static 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);
197         flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
198         _exit(127);
199     }
200     close(fd);
201 }
202
203 static void startnolisten(void)
204 {
205     int i, fd;
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);
214         if((fd = open("/dev/null", O_RDONLY)) < 0) {
215             flog(LOG_ERR, "/dev/null: %s", strerror(errno));
216             _exit(127);
217         }
218         dup2(fd, 0);
219         close(fd);
220         execvp(*progspec, progspec);
221         flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
222         _exit(127);
223     }
224 }
225
226 static 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
252 static 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;
261 retry:
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         }
274         if(fd < 0) {
275             if(tries++ < nolisten) {
276                 sleep(1);
277                 goto retry;
278             }
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 {
315         flog(LOG_ERR, "callscgi: cannot use an anonymous socket without a program to start");
316         exit(1);
317     }
318 }
319
320 static 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
333 static 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
346 static 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
358 static 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
376 static 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
387 static 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. */
403 static void mkcgienv(struct hthead *req, struct charbuf *dst)
404 {
405     int i;
406     char *url, *pi, *tmp, *qp, *h, *p;
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);
414     url = sstrdup(req->url);
415     if((qp = strchr(url, '?')) != NULL)
416         *(qp++) = 0;
417     /* XXX: This is an ugly hack (I think), but though I can think of
418      * several alternatives, none seem to be better. */
419     if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
420        !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
421         url[strlen(url) - strlen(req->rest)] = 0;
422     }
423     if((pi = unquoteurl(req->rest)) == NULL)
424         pi = sstrdup(req->rest);
425     if(!strcmp(url, "/")) {
426         /* This seems to be normal CGI behavior, but see callcgi.c for
427          * details. */
428         url[0] = 0;
429         pi = sprintf2("/%s", tmp = pi);
430         free(tmp);
431     }
432     bufaddenv(dst, "PATH_INFO", pi);
433     bufaddenv(dst, "SCRIPT_NAME", url);
434     bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
435     free(pi);
436     free(url);
437     if((h = getheader(req, "Host")) != NULL)
438         bufaddenv(dst, "SERVER_NAME", "%s", h);
439     if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
440         bufaddenv(dst, "SERVER_PORT", "%s", h);
441     if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
442         bufaddenv(dst, "REMOTE_USER", "%s", h);
443     if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
444         bufaddenv(dst, "HTTPS", "on");
445     if((h = getheader(req, "X-Ash-Address")) != NULL)
446         bufaddenv(dst, "REMOTE_ADDR", "%s", h);
447     if((h = getheader(req, "Content-Type")) != NULL)
448         bufaddenv(dst, "CONTENT_TYPE", "%s", h);
449     if((h = getheader(req, "Content-Length")) != NULL)
450         bufaddenv(dst, "CONTENT_LENGTH", "%s", h);
451     else
452         bufaddenv(dst, "CONTENT_LENGTH", "0");
453     if((h = getheader(req, "X-Ash-File")) != NULL) {
454         h = absolutify(h);
455         bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
456         free(h);
457     }
458     for(i = 0; i < req->noheaders; i++) {
459         h = sprintf2("HTTP_%s", req->headers[i][0]);
460         for(p = h; *p; p++) {
461             if(isalnum(*p))
462                 *p = toupper(*p);
463             else
464                 *p = '_';
465         }
466         bufcatstr2(*dst, h);
467         free(h);
468         bufcatstr2(*dst, req->headers[i][1]);
469     }
470 }
471
472 static char *defstatus(int code)
473 {
474     if(code == 200)
475         return("OK");
476     else if(code == 201)
477         return("Created");
478     else if(code == 202)
479         return("Accepted");
480     else if(code == 204)
481         return("No Content");
482     else if(code == 300)
483         return("Multiple Choices");
484     else if(code == 301)
485         return("Moved Permanently");
486     else if(code == 302)
487         return("Found");
488     else if(code == 303)
489         return("See Other");
490     else if(code == 304)
491         return("Not Modified");
492     else if(code == 307)
493         return("Moved Temporarily");
494     else if(code == 400)
495         return("Bad Request");
496     else if(code == 401)
497         return("Unauthorized");
498     else if(code == 403)
499         return("Forbidden");
500     else if(code == 404)
501         return("Not Found");
502     else if(code == 500)
503         return("Internal Server Error");
504     else if(code == 501)
505         return("Not Implemented");
506     else if(code == 503)
507         return("Service Unavailable");
508     else
509         return("Unknown status");
510 }
511
512 static struct hthead *parseresp(FILE *in)
513 {
514     struct hthead *resp;
515     char *st, *p;
516     
517     omalloc(resp);
518     resp->ver = sstrdup("HTTP/1.1");
519     if(parseheaders(resp, in)) {
520         freehthead(resp);
521         return(NULL);
522     }
523     if((st = getheader(resp, "Status")) != NULL) {
524         if((p = strchr(st, ' ')) != NULL) {
525             *(p++) = 0;
526             resp->code = atoi(st);
527             resp->msg = sstrdup(p);
528         } else {
529             resp->code = atoi(st);
530             resp->msg = sstrdup(defstatus(resp->code));
531         }
532         headrmheader(resp, "Status");
533     } else if(getheader(resp, "Location")) {
534         resp->code = 303;
535         resp->msg = sstrdup("See Other");
536     } else {
537         resp->code = 200;
538         resp->msg = sstrdup("OK");
539     }
540     return(resp);
541 }
542
543 static void serve(struct muth *muth, va_list args)
544 {
545     vavar(struct hthead *, req);
546     vavar(int, fd);
547     vavar(int, sfd);
548     FILE *is, *os;
549     struct charbuf head;
550     struct hthead *resp;
551     
552     sfd = reconn();
553     is = mtstdopen(fd, 1, 60, "r+");
554     os = mtstdopen(sfd, 1, 600, "r+");
555     
556     bufinit(head);
557     mkcgienv(req, &head);
558     fprintf(os, "%zi:", head.d);
559     fwrite(head.b, head.d, 1, os);
560     fputc(',', os);
561     buffree(head);
562     if(passdata(is, os) < 0)
563         goto out;
564     
565     if((resp = parseresp(os)) == NULL)
566         goto out;
567     writeresp(is, resp);
568     freehthead(resp);
569     fputc('\n', is);
570     if(passdata(os, is) < 0)
571         goto out;
572     
573 out:
574     freehthead(req);
575     fclose(is);
576     fclose(os);
577 }
578
579 static void listenloop(struct muth *muth, va_list args)
580 {
581     vavar(int, lfd);
582     int fd;
583     struct hthead *req;
584     
585     while(1) {
586         block(0, EV_READ, 0);
587         if((fd = recvreq(lfd, &req)) < 0) {
588             if(errno != 0)
589                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
590             break;
591         }
592         mustart(serve, req, fd);
593     }
594 }
595
596 static void sigign(int sig)
597 {
598 }
599
600 static void sigexit(int sig)
601 {
602     shutdown(0, SHUT_RDWR);
603     exit(0);
604 }
605
606 static void usage(FILE *out)
607 {
608     fprintf(out, "usage: callscgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
609 }
610
611 int main(int argc, char **argv)
612 {
613     int c;
614     
615     while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
616         switch(c) {
617         case 'h':
618             usage(stdout);
619             exit(0);
620         case 'N':
621             nolisten = atoi(optarg);
622             break;
623         case 'i':
624             sockid = optarg;
625             break;
626         case 'u':
627             unspec = optarg;
628             break;
629         case 't':
630             inspec = optarg;
631             break;
632         default:
633             usage(stderr);
634             exit(1);
635         }
636     }
637     progspec = argv + optind;
638     if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
639         flog(LOG_ERR, "callscgi: at most one of -i, -u or -t may be given");
640         exit(1);
641     }
642     signal(SIGCHLD, SIG_IGN);
643     signal(SIGPIPE, sigign);
644     signal(SIGINT, sigexit);
645     signal(SIGTERM, sigexit);
646     mustart(listenloop, 0);
647     atexit(killcuraddr);
648     ioloop();
649     return(0);
650 }