9f30f00178040493b5b814c37a4e300b9e393eba
[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 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));
212         _exit(127);
213     }
214     close(fd);
215 }
216
217 static 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));
230             _exit(127);
231         }
232         dup2(fd, 0);
233         close(fd);
234         execvp(*progspec, progspec);
235         flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
236         _exit(127);
237     }
238 }
239
240 static 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
266 static 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;
275 retry:
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
334 static 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
347 static 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
360 static 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
372 static 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
390 static 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
414 static 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
426 static 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. */
442 static void mkcgienv(struct hthead *req, struct charbuf *dst)
443 {
444     int i;
445     char *url, *pi, *tmp, *qp, *h, *p;
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);
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))) {
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);
469     }
470     bufaddenv(dst, "PATH_INFO", pi);
471     bufaddenv(dst, "SCRIPT_NAME", url);
472     bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
473     free(pi);
474     free(url);
475     if((h = getheader(req, "Host")) != NULL)
476         bufaddenv(dst, "SERVER_NAME", "%s", h);
477     if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
478         bufaddenv(dst, "SERVER_ADDR", "%s", h);
479     if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
480         bufaddenv(dst, "SERVER_PORT", "%s", h);
481     if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
482         bufaddenv(dst, "REMOTE_USER", "%s", h);
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);
487     if((h = getheader(req, "X-Ash-Port")) != NULL)
488         bufaddenv(dst, "REMOTE_PORT", "%s", h);
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");
495     if((h = getheader(req, "X-Ash-File")) != NULL) {
496         h = absolutify(h);
497         bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
498         free(h);
499     }
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
513 static 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);
531             resp->msg = sstrdup(httpdefstatus(resp->code));
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
546 static 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
574 static 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) {
592         free(*data);
593         return(-1);
594     }
595     for(; pl > 0; pl--) {
596         if(fgetc(in) == EOF) {
597             free(*data);
598             return(-1);
599         }
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 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
620 static 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
665 out:
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
676 static 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
693 static 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     
745 out:
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
755 static 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
772 static void sigign(int sig)
773 {
774 }
775
776 static void sigexit(int sig)
777 {
778     shutdown(0, SHUT_RDWR);
779     exit(0);
780 }
781
782 static 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
787 int 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 }