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: 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.
31 #include <sys/socket.h>
33 #include <netinet/in.h>
35 #include <sys/signal.h>
47 static char **progspec;
48 static char *sockid, *unspec, *inspec;
50 static struct sockaddr *curaddr;
51 static size_t caddrlen;
52 static int cafamily, isanon;
55 static struct addrinfo *resolv(int flags)
58 struct addrinfo *ai, h;
61 if((p = strchr(inspec, ':')) != NULL) {
62 name = smalloc(p - inspec + 1);
63 memcpy(name, inspec, p - inspec);
67 name = sstrdup("localhost");
70 memset(&h, 0, sizeof(h));
71 h.ai_family = AF_UNSPEC;
72 h.ai_socktype = SOCK_STREAM;
74 ret = getaddrinfo(name, srv, &h, &ai);
77 flog(LOG_ERR, "could not resolve TCP specification `%s': %s", inspec, gai_strerror(ret));
83 static char *mksockid(char *sockid)
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));
93 static char *mkanonid(void)
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);
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));
113 static void startlisten(void)
116 struct addrinfo *ai, *cai;
118 struct sockaddr_un unm;
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)
127 if(bind(fd, cai->ai_addr, cai->ai_addrlen)) {
132 if(listen(fd, 128)) {
141 flog(LOG_ERR, "could not bind to specified TCP address: %s", strerror(errno));
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));
152 unpath = mksockid(sockid);
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));
160 if(listen(fd, 128)) {
161 flog(LOG_ERR, "listen: %s", strerror(errno));
165 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
166 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
169 memset(&unm, 0, sizeof(unm));
171 unm.sun_family = AF_UNIX;
172 strcpy(unm.sun_path, 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));
178 if(listen(fd, 128)) {
179 flog(LOG_ERR, "listen: %s", strerror(errno));
183 curaddr = smalloc(caddrlen = sizeof(unm));
184 memcpy(curaddr, &unm, sizeof(unm));
188 if((child = fork()) < 0) {
189 flog(LOG_ERR, "could not fork: %s", strerror(errno));
194 for(i = 3; i < FD_SETSIZE; i++)
196 execvp(*progspec, progspec);
197 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
203 static void startnolisten(void)
207 if((child = fork()) < 0) {
208 flog(LOG_ERR, "could not fork: %s", strerror(errno));
212 for(i = 3; i < FD_SETSIZE; i++)
214 if((fd = open("/dev/null", O_RDONLY)) < 0) {
215 flog(LOG_ERR, "/dev/null: %s", strerror(errno));
220 execvp(*progspec, progspec);
221 flog(LOG_ERR, "callscgi: %s: %s", *progspec, strerror(errno));
226 static int sconnect(void)
232 fd = socket(cafamily, SOCK_STREAM, 0);
233 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
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)) {
252 static int econnect(void)
255 struct addrinfo *ai, *cai;
258 struct sockaddr_un unm;
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)
267 if(connect(fd, cai->ai_addr, cai->ai_addrlen)) {
275 if(tries++ < nolisten) {
279 flog(LOG_ERR, "could not connect to specified TCP address: %s", strerror(errno));
282 curaddr = smalloc(caddrlen = cai->ai_addrlen);
283 memcpy(curaddr, cai->ai_addr, caddrlen);
284 cafamily = cai->ai_family;
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));
296 unpath = mksockid(sockid);
298 unm.sun_family = AF_UNIX;
299 strcpy(unm.sun_path, unpath);
300 if(connect(fd, (struct sockaddr *)&unm, sizeof(unm))) {
302 if(tries++ < nolisten) {
306 flog(LOG_ERR, "could not connect to Unix socket `%s': %s", unspec, strerror(errno));
309 curaddr = smalloc(caddrlen = sizeof(unm));
310 memcpy(curaddr, &unm, sizeof(unm));
315 flog(LOG_ERR, "callscgi: cannot use an anonymous socket without a program to start");
320 static int startconn(void)
333 static void killcuraddr(void)
338 unlink(((struct sockaddr_un *)curaddr)->sun_path);
340 kill(child, SIGTERM);
346 static int reconn(void)
350 if(curaddr != NULL) {
351 if((fd = sconnect()) >= 0)
358 static off_t passdata(FILE *in, FILE *out)
366 read = fread(buf, 1, sizeof(buf), in);
369 if(fwrite(buf, 1, read, out) != read)
376 static void bufaddenv(struct charbuf *dst, char *name, char *fmt, ...)
380 bufcatstr2(*dst, name);
382 bvprintf(dst, fmt, args);
387 static char *absolutify(char *file)
389 static int inited = 0;
390 static char cwd[1024];
394 getcwd(cwd, sizeof(cwd));
397 return(sprintf2("%s/%s", cwd, file));
399 return(sstrdup(file));
402 /* Mostly copied from callcgi. */
403 static void mkcgienv(struct hthead *req, struct charbuf *dst)
406 char *url, *unq, *qp, *h, *p;
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 if((unq = unquoteurl(req->rest)) != NULL) {
415 bufaddenv(dst, "PATH_INFO", unq);
418 bufaddenv(dst, "PATH_INFO", req->rest);
420 url = sstrdup(req->url);
421 if((qp = strchr(url, '?')) != NULL)
423 /* XXX: This is an ugly hack (I think), but though I can think of
424 * several alternatives, none seem to be better. */
425 if(*req->rest && (strlen(url) >= strlen(req->rest)) &&
426 !strcmp(req->rest, url + strlen(url) - strlen(req->rest))) {
427 bufaddenv(dst, "SCRIPT_NAME", "%.*s", (int)(strlen(url) - strlen(req->rest)), url);
429 bufaddenv(dst, "SCRIPT_NAME", "%s", url);
431 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
433 if((h = getheader(req, "Host")) != NULL)
434 bufaddenv(dst, "SERVER_NAME", "%s", h);
435 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
436 bufaddenv(dst, "SERVER_PORT", "%s", h);
437 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
438 bufaddenv(dst, "REMOTE_USER", "%s", h);
439 if(((h = getheader(req, "X-Ash-Protocol")) != NULL) && !strcmp(h, "https"))
440 bufaddenv(dst, "HTTPS", "on");
441 if((h = getheader(req, "X-Ash-Address")) != NULL)
442 bufaddenv(dst, "REMOTE_ADDR", "%s", h);
443 if((h = getheader(req, "Content-Type")) != NULL)
444 bufaddenv(dst, "CONTENT_TYPE", "%s", h);
445 if((h = getheader(req, "Content-Length")) != NULL)
446 bufaddenv(dst, "CONTENT_LENGTH", "%s", h);
448 bufaddenv(dst, "CONTENT_LENGTH", "0");
449 if((h = getheader(req, "X-Ash-File")) != NULL) {
451 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
454 for(i = 0; i < req->noheaders; i++) {
455 h = sprintf2("HTTP_%s", req->headers[i][0]);
456 for(p = h; *p; p++) {
464 bufcatstr2(*dst, req->headers[i][1]);
468 static char *defstatus(int code)
477 return("No Content");
479 return("Multiple Choices");
481 return("Moved Permanently");
487 return("Not Modified");
489 return("Moved Temporarily");
491 return("Bad Request");
493 return("Unauthorized");
499 return("Internal Server Error");
501 return("Not Implemented");
503 return("Service Unavailable");
505 return("Unknown status");
508 static struct hthead *parseresp(FILE *in)
514 resp->ver = sstrdup("HTTP/1.1");
515 if(parseheaders(resp, in)) {
519 if((st = getheader(resp, "Status")) != NULL) {
520 if((p = strchr(st, ' ')) != NULL) {
522 resp->code = atoi(st);
523 resp->msg = sstrdup(p);
525 resp->code = atoi(st);
526 resp->msg = sstrdup(defstatus(resp->code));
528 headrmheader(resp, "Status");
529 } else if(getheader(resp, "Location")) {
531 resp->msg = sstrdup("See Other");
534 resp->msg = sstrdup("OK");
539 static void serve(struct muth *muth, va_list args)
541 vavar(struct hthead *, req);
549 is = mtstdopen(fd, 1, 60, "r+");
550 os = mtstdopen(sfd, 1, 600, "r+");
553 mkcgienv(req, &head);
554 fprintf(os, "%zi:", head.d);
555 fwrite(head.b, head.d, 1, os);
558 if(passdata(is, os) < 0)
561 if((resp = parseresp(os)) == NULL)
566 if(passdata(os, is) < 0)
575 static void listenloop(struct muth *muth, va_list args)
582 block(0, EV_READ, 0);
583 if((fd = recvreq(lfd, &req)) < 0) {
585 flog(LOG_ERR, "recvreq: %s", strerror(errno));
588 mustart(serve, req, fd);
592 static void sigign(int sig)
596 static void sigexit(int sig)
601 static void usage(FILE *out)
603 fprintf(out, "usage: callscgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
606 int main(int argc, char **argv)
610 while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
616 nolisten = atoi(optarg);
632 progspec = argv + optind;
633 if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
634 flog(LOG_ERR, "callscgi: at most one of -i, -u or -t may be given");
637 signal(SIGCHLD, SIG_IGN);
638 signal(SIGPIPE, sigign);
639 signal(SIGINT, sigexit);
640 signal(SIGTERM, sigexit);
641 mustart(listenloop, 0);