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