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