lib: Moved default status strings from call[fs]cgi to library.
[ashd.git] / src / callcgi.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 #include <stdlib.h>
20 #include <string.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <sys/poll.h>
27 #include <sys/wait.h>
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <utils.h>
33 #include <log.h>
34 #include <req.h>
35
36 static char **environ;
37
38 static int passdata(FILE *in, FILE *out)
39 {
40     int ret;
41     char buf[65536];
42     struct pollfd pfds[2];
43     
44     while(!feof(in)) {
45         memset(pfds, 0, sizeof(struct pollfd) * 2);
46         pfds[0].fd = fileno(in);
47         pfds[0].events = POLLIN;
48         pfds[1].fd = fileno(out);
49         pfds[1].events = POLLHUP;
50         ret = poll(pfds, 2, -1);
51         if(ret < 0) {
52             if(errno != EINTR) {
53                 flog(LOG_ERR, "callcgi: error in poll: %s", strerror(errno));
54                 return(1);
55             }
56         }
57         if(ret > 0) {
58             if(pfds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
59                 ret = fread(buf, 1, 65536, in);
60                 if(ferror(in)) {
61                     flog(LOG_ERR, "callcgi: could not read input: %s", strerror(errno));
62                     return(1);
63                 }
64                 if(fwrite(buf, 1, ret, out) != ret) {
65                     if(errno != EPIPE)
66                         flog(LOG_ERR, "callcgi: could not write output: %s", strerror(errno));
67                     return(1);
68                 }
69             }
70             if(pfds[1].revents & POLLHUP)
71                 return(1);
72         }
73     }
74     return(0);
75 }
76
77 static char *absolutify(char *file)
78 {
79     char cwd[1024];
80     
81     if(*file != '/') {
82         getcwd(cwd, sizeof(cwd));
83         return(sprintf2("%s/%s", cwd, file));
84     }
85     return(sstrdup(file));
86 }
87
88 static pid_t forkchild(int inpath, char *prog, char *file, char *method, char *url, char *rest, int *infd, int *outfd)
89 {
90     char *qp, **env, *name;
91     int inp[2], outp[2];
92     pid_t pid;
93     char *pi;
94
95     pipe(inp);
96     pipe(outp);
97     if((pid = fork()) < 0) {
98         flog(LOG_ERR, "callcgi: could not fork");
99         exit(1);
100     }
101     if(pid == 0) {
102         dup2(inp[0], 0);
103         dup2(outp[1], 1);
104         close(inp[0]);
105         close(inp[1]);
106         close(outp[0]);
107         close(outp[1]);
108         if((qp = strchr(url, '?')) != NULL)
109             *(qp++) = 0;
110         putenv(sprintf2("SERVER_SOFTWARE=ashd/%s", VERSION));
111         putenv("GATEWAY_INTERFACE=CGI/1.1");
112         if(getenv("HTTP_VERSION"))
113             putenv(sprintf2("SERVER_PROTOCOL=%s", getenv("HTTP_VERSION")));
114         putenv(sprintf2("REQUEST_METHOD=%s", method));
115         name = url;
116         /* XXX: This is an ugly hack (I think), but though I can think
117          * of several alternatives, none seem to be better. */
118         if(*rest && (strlen(url) >= strlen(rest)) &&
119            !strcmp(rest, url + strlen(url) - strlen(rest))) {
120             name = sprintf2("%.*s", (int)(strlen(url) - strlen(rest)), url);
121         }
122         if((pi = unquoteurl(rest)) == NULL)
123             pi = rest;
124         if(!strcmp(name, "/")) {
125             /*
126              * Normal CGI behavior appears to be to always let
127              * PATH_INFO begin with a slash and never let SCRIPT_NAME
128              * end with one. That conflicts, however, with some
129              * behaviors, such as "mounting" CGI applications on a
130              * directory element of the URI space -- a handler
131              * responding to "/foo/" would not be able to tell that it
132              * is not called "/foo", which makes a large difference,
133              * not least in relation to URI reconstruction and
134              * redirections. A common practical case is CGI-handled
135              * index files in directories. Therefore, this only
136              * handles the nonconditional case of the root directory
137              * and leaves other decisions to the previous handler
138              * handing over the request to callcgi. It is unclear if
139              * there is a better way to handle the problem.
140              */
141             name[0] = 0;
142             pi = sprintf2("/%s", pi);
143         }
144         putenv(sprintf2("PATH_INFO=%s", pi));
145         putenv(sprintf2("SCRIPT_NAME=%s", name));
146         putenv(sprintf2("QUERY_STRING=%s", qp?qp:""));
147         if(getenv("REQ_HOST"))
148             putenv(sprintf2("SERVER_NAME=%s", getenv("REQ_HOST")));
149         if(getenv("REQ_X_ASH_SERVER_ADDRESS"))
150             putenv(sprintf2("SERVER_ADDR=%s", getenv("REQ_X_ASH_SERVER_ADDRESS")));
151         if(getenv("REQ_X_ASH_SERVER_PORT"))
152             putenv(sprintf2("SERVER_PORT=%s", getenv("REQ_X_ASH_SERVER_PORT")));
153         if(getenv("REQ_X_ASH_PROTOCOL") && !strcmp(getenv("REQ_X_ASH_PROTOCOL"), "https"))
154             putenv("HTTPS=on");
155         if(getenv("REQ_X_ASH_ADDRESS"))
156             putenv(sprintf2("REMOTE_ADDR=%s", getenv("REQ_X_ASH_ADDRESS")));
157         if(getenv("REQ_X_ASH_PORT"))
158             putenv(sprintf2("REMOTE_PORT=%s", getenv("REQ_X_ASH_PORT")));
159         if(getenv("REQ_X_ASH_REMOTE_USER"))
160             putenv(sprintf2("REMOTE_USER=%s", getenv("REQ_X_ASH_REMOTE_USER")));
161         if(getenv("REQ_CONTENT_TYPE"))
162             putenv(sprintf2("CONTENT_TYPE=%s", getenv("REQ_CONTENT_TYPE")));
163         if(getenv("REQ_CONTENT_LENGTH"))
164             putenv(sprintf2("CONTENT_LENGTH=%s", getenv("REQ_CONTENT_LENGTH")));
165         for(env = environ; *env; env++) {
166             if(!strncmp(*env, "REQ_", 4))
167                 putenv(sprintf2("HTTP_%s", (*env) + 4));
168         }
169         /*
170          * This is (understandably) missing from the CGI
171          * specification, but PHP seems to require it.
172          */
173         putenv(sprintf2("SCRIPT_FILENAME=%s", absolutify(file)));
174         if(inpath)
175             execlp(prog, prog, file, NULL);
176         else
177             execl(prog, prog, file, NULL);
178         exit(127);
179     }
180     close(inp[0]);
181     close(outp[1]);
182     *infd = inp[1];
183     *outfd = outp[0];
184     return(pid);
185 }
186
187 static void trim(struct charbuf *buf)
188 {
189     char *p;
190     
191     for(p = buf->b; (p - buf->b < buf->d) && isspace(*p); p++);
192     memmove(buf->b, p, buf->d -= (p - buf->b));
193     for(p = buf->b + buf->d - 1; (p > buf->b) && isspace(*p); p--, buf->d--);
194 }
195
196 static char **parsecgiheaders(FILE *s)
197 {
198     int c, state;
199     struct charvbuf hbuf;
200     struct charbuf buf;
201     
202     bufinit(hbuf);
203     bufinit(buf);
204     state = 0;
205     while(1) {
206         c = fgetc(s);
207     again:
208         if(state == 0) {
209             if(c == '\r') {
210             } else if(c == '\n') {
211                 break;
212             } else if(c == EOF) {
213                 goto fail;
214             } else {
215                 state = 1;
216                 goto again;
217             }
218         } else if(state == 1) {
219             if(c == ':') {
220                 trim(&buf);
221                 bufadd(buf, 0);
222                 bufadd(hbuf, buf.b);
223                 bufinit(buf);
224                 state = 2;
225             } else if(c == '\r') {
226             } else if(c == '\n') {
227                 goto fail;
228             } else if(c == EOF) {
229                 goto fail;
230             } else {
231                 bufadd(buf, c);
232             }
233         } else if(state == 2) {
234             if(c == '\r') {
235             } else if(c == '\n') {
236                 trim(&buf);
237                 bufadd(buf, 0);
238                 bufadd(hbuf, buf.b);
239                 bufinit(buf);
240                 state = 0;
241             } else if(c == EOF) {
242                 goto fail;
243             } else {
244                 bufadd(buf, c);
245             }
246         }
247     }
248     bufadd(hbuf, NULL);
249     return(hbuf.b);
250     
251 fail:
252     buffree(hbuf);
253     buffree(buf);
254     return(NULL);
255 }
256
257 static char *defstatus(int code)
258 {
259     if(code == 200)
260         return("OK");
261     else if(code == 201)
262         return("Created");
263     else if(code == 202)
264         return("Accepted");
265     else if(code == 204)
266         return("No Content");
267     else if(code == 300)
268         return("Multiple Choices");
269     else if(code == 301)
270         return("Moved Permanently");
271     else if(code == 302)
272         return("Found");
273     else if(code == 303)
274         return("See Other");
275     else if(code == 304)
276         return("Not Modified");
277     else if(code == 307)
278         return("Moved Temporarily");
279     else if(code == 400)
280         return("Bad Request");
281     else if(code == 401)
282         return("Unauthorized");
283     else if(code == 403)
284         return("Forbidden");
285     else if(code == 404)
286         return("Not Found");
287     else if(code == 500)
288         return("Internal Server Error");
289     else if(code == 501)
290         return("Not Implemented");
291     else if(code == 503)
292         return("Service Unavailable");
293     else
294         return("Unknown status");
295 }
296
297 static void sendstatus(char **headers, FILE *out)
298 {
299     char **hp;
300     char *status, *location;
301     
302     hp = headers;
303     status = location = NULL;
304     while(*hp) {
305         if(!strcasecmp(hp[0], "status")) {
306             status = hp[1];
307             /* Clear this header, so that it is not transmitted by sendheaders. */
308             **hp = 0;
309         } else if(!strcasecmp(hp[0], "location")) {
310             location = hp[1];
311             hp += 2;
312         } else {
313             hp += 2;
314         }
315     }
316     if(status) {
317         if(strchr(status, ' '))
318             fprintf(out, "HTTP/1.1 %s\n", status);
319         else
320             fprintf(out, "HTTP/1.1 %i %s\n", atoi(status), defstatus(atoi(status)));
321     } else if(location) {
322         fprintf(out, "HTTP/1.1 303 See Other\n");
323     } else {
324         fprintf(out, "HTTP/1.1 200 OK\n");
325     }
326 }
327
328 static void sendheaders(char **headers, FILE *out)
329 {
330     while(*headers) {
331         if(**headers)
332             fprintf(out, "%s: %s\n", headers[0], headers[1]);
333         headers += 2;
334     }
335 }
336
337 static void usage(void)
338 {
339     flog(LOG_ERR, "usage: callcgi [-c] [-p PROGRAM] METHOD URL REST");
340 }
341
342 int main(int argc, char **argv, char **envp)
343 {
344     int c;
345     char *file, *prog, *sp;
346     int inpath, cd;
347     int infd, outfd;
348     FILE *in, *out;
349     char **headers;
350     pid_t child;
351     int estat;
352     
353     environ = envp;
354     signal(SIGPIPE, SIG_IGN);
355     
356     prog = NULL;
357     inpath = 0;
358     cd = 0;
359     while((c = getopt(argc, argv, "cp:")) >= 0) {
360         switch(c) {
361         case 'c':
362             cd = 1;
363             break;
364         case 'p':
365             prog = optarg;
366             inpath = 1;
367             break;
368         default:
369             usage();
370             exit(1);
371         }
372     }
373     
374     if(argc - optind < 3) {
375         usage();
376         exit(1);
377     }
378     if((file = getenv("REQ_X_ASH_FILE")) == NULL) {
379         flog(LOG_ERR, "callcgi: needs to be called with the X-Ash-File header");
380         exit(1);
381     }
382     
383     if(cd) {
384         /* This behavior is encouraged by the CGI specification (RFC 3875, 7.2),
385          * but not strictly required, and I get the feeling it might break some
386          * relative paths here or there, so it's not the default for now. */
387         if((sp = strrchr(file, '/')) != NULL) {
388             *sp = 0;
389             if(chdir(file)) {
390                 *sp = '/';
391             } else {
392                 file = sp + 1;
393             }
394         }
395     }
396     
397     if(prog == NULL)
398         prog = file;
399     child = forkchild(inpath, prog, file, argv[optind], argv[optind + 1], argv[optind + 2], &infd, &outfd);
400     in = fdopen(infd, "w");
401     passdata(stdin, in);        /* Ignore errors, perhaps? */
402     fclose(in);
403     out = fdopen(outfd, "r");
404     if((headers = parsecgiheaders(out)) == NULL) {
405         flog(LOG_WARNING, "CGI handler returned invalid headers");
406         exit(1);
407     }
408     sendstatus(headers, stdout);
409     sendheaders(headers, stdout);
410     printf("\n");
411     if(passdata(out, stdout))
412         kill(child, SIGINT);
413     if(waitpid(child, &estat, 0) == child) {
414         if(WCOREDUMP(estat))
415             flog(LOG_WARNING, "CGI handler `%s' dumped core", prog);
416         if(WIFEXITED(estat) && !WEXITSTATUS(estat))
417             return(0);
418         else
419             return(1);
420     }
421     flog(LOG_WARNING, "could not wait for CGI handler: %s", strerror(errno));
422     return(1);
423 }