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