2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
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.
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.
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/>.
20 * XXX: This program is mostly copied from callscgi. It may be
21 * reasonable to unify some of their shared code in a source file.
25 * XXX: All the various ways to start a child process makes this
26 * program quite ugly at the moment. It is unclear whether it is
27 * meaningfully possible to unify them better than they currently are.
36 #include <sys/socket.h>
38 #include <netinet/in.h>
53 #define FCGI_BEGIN_REQUEST 1
54 #define FCGI_ABORT_REQUEST 2
55 #define FCGI_END_REQUEST 3
61 static char **progspec;
62 static char *sockid, *unspec, *inspec;
64 static struct sockaddr *curaddr;
65 static size_t caddrlen;
66 static int cafamily, isanon;
69 static struct addrinfo *resolv(int flags)
72 struct addrinfo *ai, h;
75 if((p = strchr(inspec, ':')) != NULL) {
76 name = smalloc(p - inspec + 1);
77 memcpy(name, inspec, p - inspec);
81 name = sstrdup("localhost");
84 memset(&h, 0, sizeof(h));
85 h.ai_family = AF_UNSPEC;
86 h.ai_socktype = SOCK_STREAM;
88 ret = getaddrinfo(name, srv, &h, &ai);
91 flog(LOG_ERR, "could not resolve TCP specification `%s': %s", inspec, gai_strerror(ret));
97 static char *mksockid(char *sockid)
101 home = getenv("HOME");
102 if(home && !access(sprintf3("%s/.ashd/sockets/", home), X_OK))
103 return(sprintf3("%s/.ashd/sockets/fcgi-p-%s", home, sockid));
104 return(sprintf3("/tmp/fcgi-%i-%s", getuid(), sockid));
107 static char *mkanonid(void)
113 home = getenv("HOME");
114 if(home && !access(sprintf3("%s/.ashd/sockets/", home), X_OK))
115 tmpl = sprintf2("%s/.ashd/sockets/fcgi-a-XXXXXX", home);
117 tmpl = sprintf2("/tmp/fcgi-a-%i-XXXXXX", getuid());
118 if((fd = mkstemp(tmpl)) < 0) {
119 flog(LOG_ERR, "could not create anonymous socket `%s': %s", tmpl, strerror(errno));
127 static void startlisten(void)
130 struct addrinfo *ai, *cai;
132 struct sockaddr_un unm;
138 for(cai = ai = resolv(AI_PASSIVE); cai != NULL; cai = cai->ai_next) {
139 if((fd = socket(cai->ai_family, cai->ai_socktype, cai->ai_protocol)) < 0)
141 if(bind(fd, cai->ai_addr, cai->ai_addrlen)) {
146 if(listen(fd, 128)) {
155 flog(LOG_ERR, "could not bind to specified TCP address: %s", strerror(errno));
158 } else if((unspec != NULL) || (sockid != NULL)) {
159 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
160 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
166 unpath = mksockid(sockid);
168 unm.sun_family = AF_UNIX;
169 strcpy(unm.sun_path, unpath);
170 if(bind(fd, (struct sockaddr *)&unm, sizeof(unm))) {
171 flog(LOG_ERR, "could not bind Unix socket to `%s': %s", unspec, strerror(errno));
174 if(listen(fd, 128)) {
175 flog(LOG_ERR, "listen: %s", strerror(errno));
179 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
180 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
183 memset(&unm, 0, sizeof(unm));
185 unm.sun_family = AF_UNIX;
186 strcpy(unm.sun_path, aname);
188 if(bind(fd, (struct sockaddr *)&unm, sizeof(unm))) {
189 flog(LOG_ERR, "could not bind Unix socket to `%s': %s", unspec, strerror(errno));
192 if(listen(fd, 128)) {
193 flog(LOG_ERR, "listen: %s", strerror(errno));
197 curaddr = smalloc(caddrlen = sizeof(unm));
198 memcpy(curaddr, &unm, sizeof(unm));
202 if((child = fork()) < 0) {
203 flog(LOG_ERR, "could not fork: %s", strerror(errno));
208 for(i = 3; i < FD_SETSIZE; i++)
210 execvp(*progspec, progspec);
211 flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
217 static void startnolisten(void)
221 if((child = fork()) < 0) {
222 flog(LOG_ERR, "could not fork: %s", strerror(errno));
226 for(i = 3; i < FD_SETSIZE; i++)
228 if((fd = open("/dev/null", O_RDONLY)) < 0) {
229 flog(LOG_ERR, "/dev/null: %s", strerror(errno));
234 execvp(*progspec, progspec);
235 flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
240 static int sconnect(void)
246 fd = socket(cafamily, SOCK_STREAM, 0);
247 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
249 if(connect(fd, curaddr, caddrlen)) {
250 if(errno == EINPROGRESS) {
251 block(fd, EV_WRITE, 30);
252 errlen = sizeof(err);
253 if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) || ((errno = err) != 0)) {
266 static int econnect(void)
269 struct addrinfo *ai, *cai;
272 struct sockaddr_un unm;
278 for(cai = ai = resolv(0); cai != NULL; cai = cai->ai_next) {
279 if((fd = socket(cai->ai_family, cai->ai_socktype, cai->ai_protocol)) < 0)
281 if(connect(fd, cai->ai_addr, cai->ai_addrlen)) {
289 if(tries++ < nolisten) {
293 flog(LOG_ERR, "could not connect to specified TCP address: %s", strerror(errno));
296 curaddr = smalloc(caddrlen = cai->ai_addrlen);
297 memcpy(curaddr, cai->ai_addr, caddrlen);
298 cafamily = cai->ai_family;
302 } else if((unspec != NULL) || (sockid != NULL)) {
303 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
304 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
310 unpath = mksockid(sockid);
312 unm.sun_family = AF_UNIX;
313 strcpy(unm.sun_path, unpath);
314 if(connect(fd, (struct sockaddr *)&unm, sizeof(unm))) {
316 if(tries++ < nolisten) {
320 flog(LOG_ERR, "could not connect to Unix socket `%s': %s", unspec, strerror(errno));
323 curaddr = smalloc(caddrlen = sizeof(unm));
324 memcpy(curaddr, &unm, sizeof(unm));
329 flog(LOG_ERR, "callfcgi: cannot use an anonymous socket without a program to start");
334 static int startconn(void)
347 static void killcuraddr(void)
352 unlink(((struct sockaddr_un *)curaddr)->sun_path);
354 kill(child, SIGTERM);
360 static int reconn(void)
364 if(curaddr != NULL) {
365 if((fd = sconnect()) >= 0)
372 static off_t passdata(FILE *in, FILE *out)
380 read = fread(buf, 1, sizeof(buf), in);
383 if(fwrite(buf, 1, read, out) != read)
390 static void bufcatkv(struct charbuf *dst, char *key, char *val)
394 if((kl = strlen(key)) < 128) {
397 bufadd(*dst, ((kl & 0x7f000000) >> 24) | 0x80);
398 bufadd(*dst, (kl & 0x00ff0000) >> 16);
399 bufadd(*dst, (kl & 0x0000ff00) >> 8);
400 bufadd(*dst, kl & 0x000000ff);
402 if((vl = strlen(val)) < 128) {
405 bufadd(*dst, ((vl & 0x7f000000) >> 24) | 0x80);
406 bufadd(*dst, (vl & 0x00ff0000) >> 16);
407 bufadd(*dst, (vl & 0x0000ff00) >> 8);
408 bufadd(*dst, vl & 0x000000ff);
410 bufcat(*dst, key, kl);
411 bufcat(*dst, val, vl);
414 static void bufaddenv(struct charbuf *dst, char *name, char *fmt, ...)
420 val = vsprintf2(fmt, args);
422 bufcatkv(dst, name, val);
426 static char *absolutify(char *file)
428 static int inited = 0;
429 static char cwd[1024];
433 getcwd(cwd, sizeof(cwd));
436 return(sprintf2("%s/%s", cwd, file));
438 return(sstrdup(file));
441 /* Mostly copied from callcgi. */
442 static void mkcgienv(struct hthead *req, struct charbuf *dst)
445 char *url, *pi, *tmp, *qp, *h, *p;
447 bufaddenv(dst, "SERVER_SOFTWARE", "ashd/%s", VERSION);
448 bufaddenv(dst, "GATEWAY_INTERFACE", "CGI/1.1");
449 bufaddenv(dst, "SERVER_PROTOCOL", "%s", req->ver);
450 bufaddenv(dst, "REQUEST_METHOD", "%s", req->method);
451 bufaddenv(dst, "REQUEST_URI", "%s", req->url);
452 url = sstrdup(req->url);
453 if((qp = strchr(url, '?')) != NULL)
455 /* XXX: This is an ugly hack (I think), but though I can think of
456 * several alternatives, none seem to be better. */
457 if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
458 !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
459 url[strlen(url) - strlen(req->rest)] = 0;
461 if((pi = unquoteurl(req->rest)) == NULL)
462 pi = sstrdup(req->rest);
463 if(!strcmp(url, "/")) {
464 /* This seems to be normal CGI behavior, but see callcgi.c for
467 pi = sprintf2("/%s", tmp = pi);
470 bufaddenv(dst, "PATH_INFO", pi);
471 bufaddenv(dst, "SCRIPT_NAME", url);
472 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
475 if((h = getheader(req, "Host")) != NULL)
476 bufaddenv(dst, "SERVER_NAME", "%s", h);
477 if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
478 bufaddenv(dst, "SERVER_ADDR", "%s", h);
479 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
480 bufaddenv(dst, "SERVER_PORT", "%s", h);
481 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
482 bufaddenv(dst, "REMOTE_USER", "%s", h);
483 if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
484 bufaddenv(dst, "HTTPS", "on");
485 if((h = getheader(req, "X-Ash-Address")) != NULL)
486 bufaddenv(dst, "REMOTE_ADDR", "%s", h);
487 if((h = getheader(req, "X-Ash-Port")) != NULL)
488 bufaddenv(dst, "REMOTE_PORT", "%s", h);
489 if((h = getheader(req, "Content-Type")) != NULL)
490 bufaddenv(dst, "CONTENT_TYPE", "%s", h);
491 if((h = getheader(req, "Content-Length")) != NULL)
492 bufaddenv(dst, "CONTENT_LENGTH", "%s", h);
494 bufaddenv(dst, "CONTENT_LENGTH", "0");
495 if((h = getheader(req, "X-Ash-File")) != NULL) {
497 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
500 for(i = 0; i < req->noheaders; i++) {
501 h = sprintf2("HTTP_%s", req->headers[i][0]);
502 for(p = h; *p; p++) {
508 bufcatkv(dst, h, req->headers[i][1]);
513 static struct hthead *parseresp(FILE *in)
519 resp->ver = sstrdup("HTTP/1.1");
520 if(parseheaders(resp, in)) {
524 if((st = getheader(resp, "Status")) != NULL) {
525 if((p = strchr(st, ' ')) != NULL) {
527 resp->code = atoi(st);
528 resp->msg = sstrdup(p);
530 resp->code = atoi(st);
531 resp->msg = sstrdup(httpdefstatus(resp->code));
533 headrmheader(resp, "Status");
534 } else if(getheader(resp, "Location")) {
536 resp->msg = sstrdup("See Other");
539 resp->msg = sstrdup("OK");
544 #define fputc2(b, f) if(fputc((b), (f)) == EOF) return(-1);
546 static int sendrec(FILE *out, int type, int rid, char *data, size_t dlen)
554 cl = min(dlen - off, 65535);
555 p = (8 - (cl % 8)) % 8;
558 fputc2((rid & 0xff00) >> 8, out);
559 fputc2(rid & 0x00ff, out);
560 fputc2((cl & 0xff00) >> 8, out);
561 fputc2(cl & 0x00ff, out);
564 if(fwrite(data + off, 1, cl, out) != cl)
568 } while((off += cl) < dlen);
572 #define fgetc2(f) ({int __c__ = fgetc(f); if(__c__ == EOF) return(-1); __c__;})
574 static int recvrec(FILE *in, int *type, int *rid, char **data, size_t *dlen)
583 *rid = (b1 << 8) | b2;
586 *dlen = (b1 << 8) | b2;
590 *data = smalloc(max(*dlen, 1));
591 if(fread(*data, 1, *dlen, in) != *dlen) {
595 for(; pl > 0; pl--) {
596 if(fgetc(in) == EOF) {
604 static int begreq(FILE *out, int rid)
606 char rec[] = {0, 1, 0, 0, 0, 0, 0, 0};
608 return(sendrec(out, FCGI_BEGIN_REQUEST, rid, rec, 8));
611 static void outplex(struct muth *muth, va_list args)
628 while((ch.s = va_arg(args, FILE *)) != NULL) {
629 ch.id = va_arg(args, int);
634 if(recvrec(sk, &type, &rid, &data, &dlen))
638 for(i = 0; i < outs.d; i++) {
639 if(outs.b[i].id == type) {
640 if(outs.b[i].s != NULL) {
645 if(fwrite(data, 1, dlen, outs.b[i].s) != dlen)
659 for(i = 0; i < outs.d; i++) {
660 if(outs.b[i].s != NULL)
667 static void errhandler(struct muth *muth, va_list args)
674 while(fgets(buf, sizeof(buf), in) != NULL) {
675 p = buf + strlen(buf) - 1;
676 while((p >= buf) && (*p == '\n'))
679 flog(LOG_INFO, "child said: %s", buf);
684 static void serve(struct muth *muth, va_list args)
686 vavar(struct hthead *, req);
689 FILE *is, *os, *outi, *outo, *erri, *erro;
696 is = mtstdopen(fd, 1, 60, "r+");
697 os = mtstdopen(sfd, 1, 600, "r+");
700 mtiopipe(&outi, &outo); mtiopipe(&erri, &erro);
701 mustart(outplex, mtstdopen(dup(sfd), 1, 600, "r+"), outo, FCGI_STDOUT, erro, FCGI_STDERR, NULL);
702 mustart(errhandler, erri);
707 mkcgienv(req, &head);
708 if(sendrec(os, FCGI_PARAMS, 1, head.b, head.d))
710 if(sendrec(os, FCGI_PARAMS, 1, NULL, 0))
717 read = fread(buf, 1, sizeof(buf), is);
720 if(sendrec(os, FCGI_STDIN, 1, buf, read))
723 if(sendrec(os, FCGI_STDIN, 1, NULL, 0))
728 if((resp = parseresp(outi)) == NULL)
733 if(passdata(outi, is) < 0)
739 shutdown(sfd, SHUT_RDWR);
746 static void listenloop(struct muth *muth, va_list args)
753 block(0, EV_READ, 0);
754 if((fd = recvreq(lfd, &req)) < 0) {
756 flog(LOG_ERR, "recvreq: %s", strerror(errno));
759 mustart(serve, req, fd);
763 static void sigign(int sig)
767 static void sigexit(int sig)
769 shutdown(0, SHUT_RDWR);
773 static void usage(FILE *out)
775 fprintf(out, "usage: callfcgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
778 int main(int argc, char **argv)
782 while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
788 nolisten = atoi(optarg);
804 progspec = argv + optind;
805 if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
806 flog(LOG_ERR, "callfcgi: at most one of -i, -u or -t may be given");
809 signal(SIGCHLD, SIG_IGN);
810 signal(SIGPIPE, sigign);
811 signal(SIGINT, sigexit);
812 signal(SIGTERM, sigexit);
813 mustart(listenloop, 0);