Merge branch 'master' of fludd.seatribe.se:/usr/local/src/ashd
[ashd.git] / src / callfcgi.c
CommitLineData
5c2040da
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: This program is mostly copied from callscgi. It may be
21 * reasonable to unify some of their shared code in a source file.
22 */
23
24/*
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.
28 */
29
30#include <stdlib.h>
31#include <stdio.h>
32#include <unistd.h>
33#include <string.h>
34#include <fcntl.h>
35#include <ctype.h>
36#include <sys/socket.h>
37#include <sys/un.h>
38#include <netinet/in.h>
39#include <netdb.h>
cc5adcf9 40#include <signal.h>
5c2040da
FT
41#include <errno.h>
42
43#ifdef HAVE_CONFIG_H
44#include <config.h>
45#endif
46#include <utils.h>
47#include <req.h>
62e76c42 48#include <resp.h>
5c2040da
FT
49#include <log.h>
50#include <mt.h>
51#include <mtio.h>
52
53#define FCGI_BEGIN_REQUEST 1
54#define FCGI_ABORT_REQUEST 2
55#define FCGI_END_REQUEST 3
56#define FCGI_PARAMS 4
57#define FCGI_STDIN 5
58#define FCGI_STDOUT 6
59#define FCGI_STDERR 7
60
61static char **progspec;
62static char *sockid, *unspec, *inspec;
63static int nolisten;
64static struct sockaddr *curaddr;
65static size_t caddrlen;
66static int cafamily, isanon;
67static pid_t child;
68
69static struct addrinfo *resolv(int flags)
70{
71 int ret;
72 struct addrinfo *ai, h;
73 char *name, *srv, *p;
74
75 if((p = strchr(inspec, ':')) != NULL) {
76 name = smalloc(p - inspec + 1);
77 memcpy(name, inspec, p - inspec);
78 name[p - inspec] = 0;
79 srv = p + 1;
80 } else {
81 name = sstrdup("localhost");
82 srv = inspec;
83 }
84 memset(&h, 0, sizeof(h));
85 h.ai_family = AF_UNSPEC;
86 h.ai_socktype = SOCK_STREAM;
87 h.ai_flags = flags;
88 ret = getaddrinfo(name, srv, &h, &ai);
89 free(name);
90 if(ret != 0) {
91 flog(LOG_ERR, "could not resolve TCP specification `%s': %s", inspec, gai_strerror(ret));
92 exit(1);
93 }
94 return(ai);
95}
96
97static char *mksockid(char *sockid)
98{
99 char *home;
100
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));
105}
106
107static char *mkanonid(void)
108{
109 char *home;
110 char *tmpl;
111 int fd;
112
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);
116 else
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));
120 exit(1);
121 }
122 close(fd);
123 unlink(tmpl);
124 return(tmpl);
125}
126
127static void startlisten(void)
128{
129 int i, fd;
130 struct addrinfo *ai, *cai;
131 char *unpath;
132 struct sockaddr_un unm;
133 char *aname;
134
135 isanon = 0;
136 if(inspec != NULL) {
137 fd = -1;
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)
140 continue;
141 if(bind(fd, cai->ai_addr, cai->ai_addrlen)) {
142 close(fd);
143 fd = -1;
144 continue;
145 }
146 if(listen(fd, 128)) {
147 close(fd);
148 fd = -1;
149 continue;
150 }
151 break;
152 }
153 freeaddrinfo(ai);
154 if(fd < 0) {
155 flog(LOG_ERR, "could not bind to specified TCP address: %s", strerror(errno));
156 exit(1);
157 }
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));
161 exit(1);
162 }
163 if(unspec != NULL)
164 unpath = unspec;
165 else
166 unpath = mksockid(sockid);
167 unlink(unpath);
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));
172 exit(1);
173 }
174 if(listen(fd, 128)) {
175 flog(LOG_ERR, "listen: %s", strerror(errno));
176 exit(1);
177 }
178 } else {
179 if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
180 flog(LOG_ERR, "could not create Unix socket: %s", strerror(errno));
181 exit(1);
182 }
183 memset(&unm, 0, sizeof(unm));
184 aname = mkanonid();
185 unm.sun_family = AF_UNIX;
186 strcpy(unm.sun_path, aname);
187 free(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));
190 exit(1);
191 }
192 if(listen(fd, 128)) {
193 flog(LOG_ERR, "listen: %s", strerror(errno));
194 exit(1);
195 }
196
197 curaddr = smalloc(caddrlen = sizeof(unm));
198 memcpy(curaddr, &unm, sizeof(unm));
199 cafamily = AF_UNIX;
200 isanon = 1;
201 }
202 if((child = fork()) < 0) {
203 flog(LOG_ERR, "could not fork: %s", strerror(errno));
204 exit(1);
205 }
206 if(child == 0) {
207 dup2(fd, 0);
208 for(i = 3; i < FD_SETSIZE; i++)
209 close(i);
210 execvp(*progspec, progspec);
211 flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
107aa2ef 212 _exit(127);
5c2040da
FT
213 }
214 close(fd);
215}
216
217static void startnolisten(void)
218{
219 int i, fd;
220
221 if((child = fork()) < 0) {
222 flog(LOG_ERR, "could not fork: %s", strerror(errno));
223 exit(1);
224 }
225 if(child == 0) {
226 for(i = 3; i < FD_SETSIZE; i++)
227 close(i);
228 if((fd = open("/dev/null", O_RDONLY)) < 0) {
229 flog(LOG_ERR, "/dev/null: %s", strerror(errno));
107aa2ef 230 _exit(127);
5c2040da
FT
231 }
232 dup2(fd, 0);
233 close(fd);
234 execvp(*progspec, progspec);
235 flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
107aa2ef 236 _exit(127);
5c2040da
FT
237 }
238}
239
240static int sconnect(void)
241{
242 int fd;
243 int err;
244 socklen_t errlen;
245
246 fd = socket(cafamily, SOCK_STREAM, 0);
247 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
248 while(1) {
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)) {
254 close(fd);
255 return(-1);
256 }
257 return(fd);
258 }
259 close(fd);
260 return(-1);
261 }
262 return(fd);
263 }
264}
265
266static int econnect(void)
267{
268 int fd;
269 struct addrinfo *ai, *cai;
270 int tries;
271 char *unpath;
272 struct sockaddr_un unm;
273
274 tries = 0;
275retry:
276 if(inspec != NULL) {
277 fd = -1;
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)
280 continue;
281 if(connect(fd, cai->ai_addr, cai->ai_addrlen)) {
282 close(fd);
283 fd = -1;
284 continue;
285 }
286 break;
287 }
288 if(fd < 0) {
289 if(tries++ < nolisten) {
290 sleep(1);
291 goto retry;
292 }
293 flog(LOG_ERR, "could not connect to specified TCP address: %s", strerror(errno));
294 exit(1);
295 }
296 curaddr = smalloc(caddrlen = cai->ai_addrlen);
297 memcpy(curaddr, cai->ai_addr, caddrlen);
298 cafamily = cai->ai_family;
299 isanon = 0;
300 freeaddrinfo(ai);
301 return(fd);
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));
305 exit(1);
306 }
307 if(unspec != NULL)
308 unpath = unspec;
309 else
310 unpath = mksockid(sockid);
311 unlink(unpath);
312 unm.sun_family = AF_UNIX;
313 strcpy(unm.sun_path, unpath);
314 if(connect(fd, (struct sockaddr *)&unm, sizeof(unm))) {
315 close(fd);
316 if(tries++ < nolisten) {
317 sleep(1);
318 goto retry;
319 }
320 flog(LOG_ERR, "could not connect to Unix socket `%s': %s", unspec, strerror(errno));
321 exit(1);
322 }
323 curaddr = smalloc(caddrlen = sizeof(unm));
324 memcpy(curaddr, &unm, sizeof(unm));
325 cafamily = AF_UNIX;
326 isanon = 0;
327 return(fd);
328 } else {
329 flog(LOG_ERR, "callfcgi: cannot use an anonymous socket without a program to start");
330 exit(1);
331 }
332}
333
334static int startconn(void)
335{
336 if(*progspec) {
337 if(nolisten == 0)
338 startlisten();
339 else
340 startnolisten();
341 }
342 if(curaddr != NULL)
343 return(sconnect());
344 return(econnect());
345}
346
347static void killcuraddr(void)
348{
349 if(curaddr == NULL)
350 return;
351 if(isanon) {
352 unlink(((struct sockaddr_un *)curaddr)->sun_path);
353 if(child > 0)
354 kill(child, SIGTERM);
355 }
356 free(curaddr);
357 curaddr = NULL;
358}
359
360static int reconn(void)
361{
362 int fd;
363
364 if(curaddr != NULL) {
365 if((fd = sconnect()) >= 0)
366 return(fd);
367 killcuraddr();
368 }
369 return(startconn());
370}
371
372static off_t passdata(FILE *in, FILE *out)
373{
374 size_t read;
375 off_t total;
376 char buf[8192];
377
378 total = 0;
379 while(!feof(in)) {
380 read = fread(buf, 1, sizeof(buf), in);
381 if(ferror(in))
382 return(-1);
383 if(fwrite(buf, 1, read, out) != read)
384 return(-1);
385 total += read;
386 }
387 return(total);
388}
389
390static void bufcatkv(struct charbuf *dst, char *key, char *val)
391{
392 size_t kl, vl;
393
394 if((kl = strlen(key)) < 128) {
395 bufadd(*dst, kl);
396 } else {
397 bufadd(*dst, ((kl & 0x7f000000) >> 24) | 0x80);
398 bufadd(*dst, (kl & 0x00ff0000) >> 16);
399 bufadd(*dst, (kl & 0x0000ff00) >> 8);
400 bufadd(*dst, kl & 0x000000ff);
401 }
402 if((vl = strlen(val)) < 128) {
403 bufadd(*dst, vl);
404 } else {
405 bufadd(*dst, ((vl & 0x7f000000) >> 24) | 0x80);
406 bufadd(*dst, (vl & 0x00ff0000) >> 16);
407 bufadd(*dst, (vl & 0x0000ff00) >> 8);
408 bufadd(*dst, vl & 0x000000ff);
409 }
410 bufcat(*dst, key, kl);
411 bufcat(*dst, val, vl);
412}
413
414static void bufaddenv(struct charbuf *dst, char *name, char *fmt, ...)
415{
416 va_list args;
417 char *val = NULL;
418
419 va_start(args, fmt);
420 val = vsprintf2(fmt, args);
421 va_end(args);
422 bufcatkv(dst, name, val);
423 free(val);
424}
425
426static char *absolutify(char *file)
427{
428 static int inited = 0;
429 static char cwd[1024];
430
431 if(*file != '/') {
432 if(!inited) {
433 getcwd(cwd, sizeof(cwd));
434 inited = 1;
435 }
436 return(sprintf2("%s/%s", cwd, file));
437 }
438 return(sstrdup(file));
439}
440
441/* Mostly copied from callcgi. */
442static void mkcgienv(struct hthead *req, struct charbuf *dst)
443{
444 int i;
53d666ca 445 char *url, *pi, *tmp, *qp, *h, *p;
5c2040da
FT
446
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);
5c2040da
FT
452 url = sstrdup(req->url);
453 if((qp = strchr(url, '?')) != NULL)
454 *(qp++) = 0;
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))) {
53d666ca
FT
459 url[strlen(url) - strlen(req->rest)] = 0;
460 }
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
465 * details. */
466 url[0] = 0;
467 pi = sprintf2("/%s", tmp = pi);
468 free(tmp);
5c2040da 469 }
53d666ca
FT
470 bufaddenv(dst, "PATH_INFO", pi);
471 bufaddenv(dst, "SCRIPT_NAME", url);
5c2040da 472 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
53d666ca 473 free(pi);
b77076c4 474 free(url);
5c2040da
FT
475 if((h = getheader(req, "Host")) != NULL)
476 bufaddenv(dst, "SERVER_NAME", "%s", h);
1af656d2
FT
477 if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
478 bufaddenv(dst, "SERVER_ADDR", "%s", h);
5c2040da
FT
479 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
480 bufaddenv(dst, "SERVER_PORT", "%s", h);
381f9919
FT
481 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
482 bufaddenv(dst, "REMOTE_USER", "%s", h);
5c2040da
FT
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);
1af656d2
FT
487 if((h = getheader(req, "X-Ash-Port")) != NULL)
488 bufaddenv(dst, "REMOTE_PORT", "%s", h);
5c2040da
FT
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);
493 else
494 bufaddenv(dst, "CONTENT_LENGTH", "0");
e25bf4f9
FT
495 if((h = getheader(req, "X-Ash-File")) != NULL) {
496 h = absolutify(h);
497 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
498 free(h);
499 }
5c2040da
FT
500 for(i = 0; i < req->noheaders; i++) {
501 h = sprintf2("HTTP_%s", req->headers[i][0]);
502 for(p = h; *p; p++) {
503 if(isalnum(*p))
504 *p = toupper(*p);
505 else
506 *p = '_';
507 }
508 bufcatkv(dst, h, req->headers[i][1]);
509 free(h);
510 }
511}
512
5c2040da
FT
513static struct hthead *parseresp(FILE *in)
514{
515 struct hthead *resp;
516 char *st, *p;
517
518 omalloc(resp);
519 resp->ver = sstrdup("HTTP/1.1");
520 if(parseheaders(resp, in)) {
521 freehthead(resp);
522 return(NULL);
523 }
524 if((st = getheader(resp, "Status")) != NULL) {
525 if((p = strchr(st, ' ')) != NULL) {
526 *(p++) = 0;
527 resp->code = atoi(st);
528 resp->msg = sstrdup(p);
529 } else {
530 resp->code = atoi(st);
62e76c42 531 resp->msg = sstrdup(httpdefstatus(resp->code));
5c2040da
FT
532 }
533 headrmheader(resp, "Status");
534 } else if(getheader(resp, "Location")) {
535 resp->code = 303;
536 resp->msg = sstrdup("See Other");
537 } else {
538 resp->code = 200;
539 resp->msg = sstrdup("OK");
540 }
541 return(resp);
542}
543
544#define fputc2(b, f) if(fputc((b), (f)) == EOF) return(-1);
545
546static int sendrec(FILE *out, int type, int rid, char *data, size_t dlen)
547{
548 off_t off;
549 size_t cl;
550 int p;
551
552 off = 0;
553 do {
554 cl = min(dlen - off, 65535);
555 p = (8 - (cl % 8)) % 8;
556 fputc2(1, out);
557 fputc2(type, out);
558 fputc2((rid & 0xff00) >> 8, out);
559 fputc2(rid & 0x00ff, out);
560 fputc2((cl & 0xff00) >> 8, out);
561 fputc2(cl & 0x00ff, out);
562 fputc2(p, out);
563 fputc2(0, out);
564 if(fwrite(data + off, 1, cl, out) != cl)
565 return(-1);
566 for(; p > 0; p--)
567 fputc2(0, out);
568 } while((off += cl) < dlen);
569 return(0);
570}
571
572#define fgetc2(f) ({int __c__ = fgetc(f); if(__c__ == EOF) return(-1); __c__;})
573
574static int recvrec(FILE *in, int *type, int *rid, char **data, size_t *dlen)
575{
576 int b1, b2, pl;
577
578 if(fgetc2(in) != 1)
579 return(-1);
580 *type = fgetc2(in);
581 b1 = fgetc2(in);
582 b2 = fgetc2(in);
583 *rid = (b1 << 8) | b2;
584 b1 = fgetc2(in);
585 b2 = fgetc2(in);
586 *dlen = (b1 << 8) | b2;
587 pl = fgetc2(in);
588 if(fgetc2(in) != 0)
589 return(-1);
590 *data = smalloc(max(*dlen, 1));
591 if(fread(*data, 1, *dlen, in) != *dlen) {
49b8b4d2 592 free(*data);
5c2040da
FT
593 return(-1);
594 }
595 for(; pl > 0; pl--) {
596 if(fgetc(in) == EOF) {
49b8b4d2 597 free(*data);
5c2040da
FT
598 return(-1);
599 }
600 }
601 return(0);
602}
603
604static int begreq(FILE *out, int rid)
605{
606 char rec[] = {0, 1, 0, 0, 0, 0, 0, 0};
607
608 return(sendrec(out, FCGI_BEGIN_REQUEST, rid, rec, 8));
609}
610
611static void mtiopipe(FILE **read, FILE **write)
612{
613 int fds[2];
614
615 pipe(fds);
616 *read = mtstdopen(fds[0], 0, 600, "r");
617 *write = mtstdopen(fds[1], 0, 600, "w");
618}
619
620static void outplex(struct muth *muth, va_list args)
621{
622 vavar(FILE *, sk);
623 struct {
624 struct ch {
625 FILE *s;
626 int id;
627 } *b;
628 size_t s, d;
629 } outs;
630 int i;
631 struct ch ch;
632 int type, rid;
633 char *data;
634 size_t dlen;
635
636 bufinit(outs);
637 while((ch.s = va_arg(args, FILE *)) != NULL) {
638 ch.id = va_arg(args, int);
639 bufadd(outs, ch);
640 }
641 data = NULL;
642 while(1) {
643 if(recvrec(sk, &type, &rid, &data, &dlen))
644 goto out;
645 if(rid != 1)
646 goto out;
647 for(i = 0; i < outs.d; i++) {
648 if(outs.b[i].id == type) {
649 if(outs.b[i].s != NULL) {
650 if(dlen == 0) {
651 fclose(outs.b[i].s);
652 outs.b[i].s = NULL;
653 } else {
654 if(fwrite(data, 1, dlen, outs.b[i].s) != dlen)
655 goto out;
656 }
657 }
658 break;
659 }
660 }
661 free(data);
662 data = NULL;
663 }
664
665out:
666 if(data != NULL)
667 free(data);
668 for(i = 0; i < outs.d; i++) {
669 if(outs.b[i].s != NULL)
670 fclose(outs.b[i].s);
671 }
672 buffree(outs);
673 fclose(sk);
674}
675
676static void errhandler(struct muth *muth, va_list args)
677{
678 vavar(FILE *, in);
679 char buf[1024];
680 char *p;
681
682 bufinit(buf);
683 while(fgets(buf, sizeof(buf), in) != NULL) {
684 p = buf + strlen(buf) - 1;
685 while((p >= buf) && (*p == '\n'))
686 *(p--) = 0;
687 if(buf[0])
688 flog(LOG_INFO, "child said: %s", buf);
689 }
690 fclose(in);
691}
692
693static void serve(struct muth *muth, va_list args)
694{
695 vavar(struct hthead *, req);
696 vavar(int, fd);
697 vavar(int, sfd);
698 FILE *is, *os, *outi, *outo, *erri, *erro;
699 struct charbuf head;
700 struct hthead *resp;
701 size_t read;
702 char buf[8192];
703
704 sfd = reconn();
705 is = mtstdopen(fd, 1, 60, "r+");
706 os = mtstdopen(sfd, 1, 600, "r+");
707
708 outi = NULL;
709 mtiopipe(&outi, &outo); mtiopipe(&erri, &erro);
710 mustart(outplex, mtstdopen(dup(sfd), 1, 600, "r+"), outo, FCGI_STDOUT, erro, FCGI_STDERR, NULL);
711 mustart(errhandler, erri);
712
713 if(begreq(os, 1))
714 goto out;
715 bufinit(head);
716 mkcgienv(req, &head);
717 if(sendrec(os, FCGI_PARAMS, 1, head.b, head.d))
718 goto out;
719 if(sendrec(os, FCGI_PARAMS, 1, NULL, 0))
720 goto out;
721 buffree(head);
722 if(fflush(os))
723 goto out;
724
725 while(!feof(is)) {
726 read = fread(buf, 1, sizeof(buf), is);
727 if(ferror(is))
728 goto out;
729 if(sendrec(os, FCGI_STDIN, 1, buf, read))
730 goto out;
731 }
732 if(sendrec(os, FCGI_STDIN, 1, NULL, 0))
733 goto out;
734 if(fflush(os))
735 goto out;
736
737 if((resp = parseresp(outi)) == NULL)
738 goto out;
739 writeresp(is, resp);
740 freehthead(resp);
741 fputc('\n', is);
742 if(passdata(outi, is) < 0)
743 goto out;
744
745out:
746 freehthead(req);
747 buffree(head);
748 shutdown(sfd, SHUT_RDWR);
749 if(outi != NULL)
750 fclose(outi);
751 fclose(is);
752 fclose(os);
753}
754
755static void listenloop(struct muth *muth, va_list args)
756{
757 vavar(int, lfd);
758 int fd;
759 struct hthead *req;
760
761 while(1) {
762 block(0, EV_READ, 0);
763 if((fd = recvreq(lfd, &req)) < 0) {
764 if(errno != 0)
765 flog(LOG_ERR, "recvreq: %s", strerror(errno));
766 break;
767 }
768 mustart(serve, req, fd);
769 }
770}
771
772static void sigign(int sig)
773{
774}
775
776static void sigexit(int sig)
777{
254eb937 778 shutdown(0, SHUT_RDWR);
5c2040da
FT
779 exit(0);
780}
781
782static void usage(FILE *out)
783{
784 fprintf(out, "usage: callfcgi [-h] [-N RETRIES] [-i ID] [-u UNIX-PATH] [-t [HOST:]TCP-PORT] [PROGRAM [ARGS...]]\n");
785}
786
787int main(int argc, char **argv)
788{
789 int c;
790
791 while((c = getopt(argc, argv, "+hN:i:u:t:")) >= 0) {
792 switch(c) {
793 case 'h':
794 usage(stdout);
795 exit(0);
796 case 'N':
797 nolisten = atoi(optarg);
798 break;
799 case 'i':
800 sockid = optarg;
801 break;
802 case 'u':
803 unspec = optarg;
804 break;
805 case 't':
806 inspec = optarg;
807 break;
808 default:
809 usage(stderr);
810 exit(1);
811 }
812 }
813 progspec = argv + optind;
814 if(((sockid != NULL) + (unspec != NULL) + (inspec != NULL)) > 1) {
815 flog(LOG_ERR, "callfcgi: at most one of -i, -u or -t may be given");
816 exit(1);
817 }
818 signal(SIGCHLD, SIG_IGN);
819 signal(SIGPIPE, sigign);
820 signal(SIGINT, sigexit);
821 signal(SIGTERM, sigexit);
822 mustart(listenloop, 0);
823 atexit(killcuraddr);
824 ioloop();
825 return(0);
826}