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