callcgi: Don't log errors about broken pipes.
[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>
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
60static char **progspec;
61static char *sockid, *unspec, *inspec;
62static int nolisten;
63static struct sockaddr *curaddr;
64static size_t caddrlen;
65static int cafamily, isanon;
66static pid_t child;
67
68static 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
96static 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
106static 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
126static 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));
107aa2ef 211 _exit(127);
5c2040da
FT
212 }
213 close(fd);
214}
215
216static 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));
107aa2ef 229 _exit(127);
5c2040da
FT
230 }
231 dup2(fd, 0);
232 close(fd);
233 execvp(*progspec, progspec);
234 flog(LOG_ERR, "callfcgi: %s: %s", *progspec, strerror(errno));
107aa2ef 235 _exit(127);
5c2040da
FT
236 }
237}
238
239static 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
265static 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;
274retry:
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
333static 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
346static 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
359static 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
371static 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
389static 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
413static 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
425static 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. */
441static void mkcgienv(struct hthead *req, struct charbuf *dst)
442{
443 int i;
53d666ca 444 char *url, *pi, *tmp, *qp, *h, *p;
5c2040da
FT
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);
5c2040da
FT
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))) {
53d666ca
FT
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);
5c2040da 468 }
53d666ca
FT
469 bufaddenv(dst, "PATH_INFO", pi);
470 bufaddenv(dst, "SCRIPT_NAME", url);
5c2040da 471 bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:"");
53d666ca 472 free(pi);
b77076c4 473 free(url);
5c2040da
FT
474 if((h = getheader(req, "Host")) != NULL)
475 bufaddenv(dst, "SERVER_NAME", "%s", h);
1af656d2
FT
476 if((h = getheader(req, "X-Ash-Server-Address")) != NULL)
477 bufaddenv(dst, "SERVER_ADDR", "%s", h);
5c2040da
FT
478 if((h = getheader(req, "X-Ash-Server-Port")) != NULL)
479 bufaddenv(dst, "SERVER_PORT", "%s", h);
381f9919
FT
480 if((h = getheader(req, "X-Ash-Remote-User")) != NULL)
481 bufaddenv(dst, "REMOTE_USER", "%s", h);
5c2040da
FT
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);
1af656d2
FT
486 if((h = getheader(req, "X-Ash-Port")) != NULL)
487 bufaddenv(dst, "REMOTE_PORT", "%s", h);
5c2040da
FT
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");
e25bf4f9
FT
494 if((h = getheader(req, "X-Ash-File")) != NULL) {
495 h = absolutify(h);
496 bufaddenv(dst, "SCRIPT_FILENAME", "%s", h);
497 free(h);
498 }
5c2040da
FT
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
512static 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
552static 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
585static 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
613static 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) {
49b8b4d2 631 free(*data);
5c2040da
FT
632 return(-1);
633 }
634 for(; pl > 0; pl--) {
635 if(fgetc(in) == EOF) {
49b8b4d2 636 free(*data);
5c2040da
FT
637 return(-1);
638 }
639 }
640 return(0);
641}
642
643static 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
650static 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
659static 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
704out:
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
715static 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
732static 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
784out:
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
794static 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
811static void sigign(int sig)
812{
813}
814
815static void sigexit(int sig)
816{
254eb937 817 shutdown(0, SHUT_RDWR);
5c2040da
FT
818 exit(0);
819}
820
821static 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
826int 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}