Slightly more sensible argument parsing for htparser.
[ashd.git] / src / htparser.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 <unistd.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <sys/socket.h>
24 #include <pwd.h>
25 #include <errno.h>
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 #include <utils.h>
31 #include <mt.h>
32 #include <mtio.h>
33 #include <log.h>
34 #include <req.h>
35 #include <proc.h>
36
37 #include "htparser.h"
38
39 static int plex;
40
41 static struct hthead *parsereq(FILE *in)
42 {
43     struct hthead *req;
44     struct charbuf method, url, ver;
45     int c;
46     
47     req = NULL;
48     bufinit(method);
49     bufinit(url);
50     bufinit(ver);
51     while(1) {
52         c = getc(in);
53         if(c == ' ') {
54             break;
55         } else if((c == EOF) || (c < 32) || (c >= 128)) {
56             goto fail;
57         } else {
58             bufadd(method, c);
59         }
60     }
61     while(1) {
62         c = getc(in);
63         if(c == ' ') {
64             break;
65         } else if((c == EOF) || (c < 32)) {
66             goto fail;
67         } else {
68             bufadd(url, c);
69         }
70     }
71     while(1) {
72         c = getc(in);
73         if(c == 10) {
74             break;
75         } else if(c == 13) {
76         } else if((c == EOF) || (c < 32) || (c >= 128)) {
77             goto fail;
78         } else {
79             bufadd(ver, c);
80         }
81     }
82     bufadd(method, 0);
83     bufadd(url, 0);
84     bufadd(ver, 0);
85     req = mkreq(method.b, url.b, ver.b);
86     if(parseheaders(req, in))
87         goto fail;
88     goto out;
89     
90 fail:
91     if(req != NULL) {
92         freehthead(req);
93         req = NULL;
94     }
95 out:
96     buffree(method);
97     buffree(url);
98     buffree(ver);
99     return(req);
100 }
101
102 static struct hthead *parseresp(FILE *in)
103 {
104     struct hthead *req;
105     int code;
106     struct charbuf ver, msg;
107     int c;
108     
109     req = NULL;
110     bufinit(ver);
111     bufinit(msg);
112     code = 0;
113     while(1) {
114         c = getc(in);
115         if(c == ' ') {
116             break;
117         } else if((c == EOF) || (c < 32) || (c >= 128)) {
118             goto fail;
119         } else {
120             bufadd(ver, c);
121         }
122     }
123     while(1) {
124         c = getc(in);
125         if(c == ' ') {
126             break;
127         } else if((c == EOF) || (c < '0') || (c > '9')) {
128             goto fail;
129         } else {
130             code = (code * 10) + (c - '0');
131         }
132     }
133     while(1) {
134         c = getc(in);
135         if(c == 10) {
136             break;
137         } else if(c == 13) {
138         } else if((c == EOF) || (c < 32)) {
139             goto fail;
140         } else {
141             bufadd(msg, c);
142         }
143     }
144     bufadd(msg, 0);
145     bufadd(ver, 0);
146     req = mkresp(code, msg.b, ver.b);
147     if(parseheaders(req, in))
148         goto fail;
149     goto out;
150     
151 fail:
152     if(req != NULL) {
153         freehthead(req);
154         req = NULL;
155     }
156 out:
157     buffree(msg);
158     buffree(ver);
159     return(req);
160 }
161
162 static off_t passdata(FILE *in, FILE *out, off_t max)
163 {
164     size_t read;
165     off_t total;
166     char buf[8192];
167     
168     total = 0;
169     while(!feof(in) && ((max < 0) || (total < max))) {
170         read = sizeof(buf);
171         if(max >= 0)
172             read = min(max - total, read);
173         read = fread(buf, 1, read, in);
174         if(ferror(in))
175             return(-1);
176         if(fwrite(buf, 1, read, out) != read)
177             return(-1);
178         total += read;
179     }
180     return(total);
181 }
182
183 static int passchunks(FILE *in, FILE *out)
184 {
185     char buf[8192];
186     size_t read;
187     
188     do {
189         read = fread(buf, 1, sizeof(buf), in);
190         if(ferror(in))
191             return(-1);
192         fprintf(out, "%zx\r\n", read);
193         if(fwrite(buf, 1, read, out) != read)
194             return(-1);
195         fprintf(out, "\r\n");
196     } while(read > 0);
197     return(0);
198 }
199
200 static int hasheader(struct hthead *head, char *name, char *val)
201 {
202     char *hd;
203     
204     if((hd = getheader(head, name)) == NULL)
205         return(0);
206     return(!strcasecmp(hd, val));
207 }
208
209 void serve(FILE *in, struct conn *conn)
210 {
211     int pfds[2];
212     FILE *out;
213     struct hthead *req, *resp;
214     char *hd, *p;
215     off_t dlen;
216     
217     out = NULL;
218     req = resp = NULL;
219     while(1) {
220         if((req = parsereq(in)) == NULL)
221             break;
222         replrest(req, req->url);
223         if(req->rest[0] == '/')
224             replrest(req, req->rest + 1);
225         if((p = strchr(req->rest, '?')) != NULL)
226             *p = 0;
227         
228         if((conn->initreq != NULL) && conn->initreq(conn, req))
229             break;
230         
231         if(block(plex, EV_WRITE, 60) <= 0)
232             break;
233         if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
234             break;
235         if(sendreq(plex, req, pfds[0]))
236             break;
237         close(pfds[0]);
238         out = mtstdopen(pfds[1], 1, 600, "r+");
239
240         if((hd = getheader(req, "content-length")) != NULL) {
241             dlen = atoo(hd);
242             if(dlen > 0) {
243                 if(passdata(in, out, dlen) != dlen)
244                     break;
245             }
246         }
247         if(fflush(out))
248             break;
249         /* Make sure to send EOF */
250         shutdown(pfds[1], SHUT_WR);
251         
252         if((resp = parseresp(out)) == NULL)
253             break;
254         replstr(&resp->ver, req->ver);
255
256         if(!strcmp(req->ver, "HTTP/1.0")) {
257             writeresp(in, resp);
258             fprintf(in, "\r\n");
259             if((hd = getheader(resp, "content-length")) != NULL) {
260                 dlen = passdata(out, in, -1);
261                 if(dlen != atoo(hd))
262                     break;
263                 if(!hasheader(req, "connection", "keep-alive"))
264                     break;
265             } else {
266                 passdata(out, in, -1);
267                 break;
268             }
269             if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
270                 break;
271         } else if(!strcmp(req->ver, "HTTP/1.1")) {
272             if((hd = getheader(resp, "content-length")) != NULL) {
273                 writeresp(in, resp);
274                 fprintf(in, "\r\n");
275                 dlen = passdata(out, in, -1);
276                 if(dlen != atoo(hd))
277                     break;
278             } else if(!getheader(resp, "transfer-encoding")) {
279                 headappheader(resp, "Transfer-Encoding", "chunked");
280                 writeresp(in, resp);
281                 fprintf(in, "\r\n");
282                 if(passchunks(out, in))
283                     break;
284             } else {
285                 writeresp(in, resp);
286                 fprintf(in, "\r\n");
287                 passdata(out, in, -1);
288                 break;
289             }
290             if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
291                 break;
292         } else {
293             break;
294         }
295
296         fclose(out);
297         out = NULL;
298         freehthead(req);
299         freehthead(resp);
300         req = resp = NULL;
301     }
302     
303     if(out != NULL)
304         fclose(out);
305     if(req != NULL)
306         freehthead(req);
307     if(resp != NULL)
308         freehthead(resp);
309     fclose(in);
310 }
311
312 static void plexwatch(struct muth *muth, va_list args)
313 {
314     vavar(int, fd);
315     char *buf;
316     int ret;
317     
318     while(1) {
319         block(fd, EV_READ, 0);
320         buf = smalloc(65536);
321         ret = recv(fd, buf, 65536, 0);
322         if(ret < 0) {
323             flog(LOG_WARNING, "received error on rootplex read channel: %s", strerror(errno));
324             exit(1);
325         } else if(ret == 0) {
326             exit(0);
327         }
328         /* Maybe I'd like to implement some protocol in this direction
329          * some day... */
330         free(buf);
331     }
332 }
333
334 static void usage(FILE *out)
335 {
336     fprintf(out, "usage: htparser [-hSf] [-u USER] [-r ROOT] PORTSPEC... -- ROOT [ARGS...]\n");
337     fprintf(out, "\twhere PORTSPEC is HANDLER[:PAR[=VAL][(,PAR[=VAL])...]] (try HANDLER:help)\n");
338     fprintf(out, "\tavailable handlers are `plain'.\n");
339 }
340
341 static void addport(char *spec)
342 {
343     char *nm, *p, *p2, *n;
344     struct charvbuf pars, vals;
345     
346     bufinit(pars);
347     bufinit(vals);
348     if((p = strchr(spec, ':')) == NULL) {
349         nm = spec;
350     } else {
351         nm = spec;
352         *(p++) = 0;
353         do {
354             if((n = strchr(p, ',')) != NULL)
355                 *(n++) = 0;
356             if((p2 = strchr(p, '=')) != NULL)
357                 *(p2++) = 0;
358             if(!*p) {
359                 usage(stderr);
360                 exit(1);
361             }
362             bufadd(pars, p);
363             if(p2)
364                 bufadd(vals, p2);
365             else
366                 bufadd(vals, "");
367         } while((p = n) != NULL);
368     }
369     
370     /* XXX: It would be nice to decentralize this, but, meh... */
371     if(!strcmp(nm, "plain")) {
372         handleplain(pars.d, pars.b, vals.b);
373     } else {
374         flog(LOG_ERR, "htparser: unknown port handler `%s'", nm);
375         exit(1);
376     }
377     
378     buffree(pars);
379     buffree(vals);
380 }
381
382 int main(int argc, char **argv)
383 {
384     int c;
385     int i, s1;
386     int daemonize, logsys;
387     char *root;
388     struct passwd *pwent;
389     
390     daemonize = logsys = 0;
391     root = NULL;
392     pwent = NULL;
393     while((c = getopt(argc, argv, "+hSfu:r:")) >= 0) {
394         switch(c) {
395         case 'h':
396             usage(stdout);
397             exit(0);
398         case 'f':
399             daemonize = 1;
400             break;
401         case 'S':
402             logsys = 1;
403             break;
404         case 'u':
405             if((pwent = getpwnam(optarg)) == NULL) {
406                 flog(LOG_ERR, "could not find user %s", optarg);
407                 exit(1);
408             }
409             break;
410         case 'r':
411             root = optarg;
412             break;
413         default:
414             usage(stderr);
415             exit(1);
416         }
417     }
418     s1 = 0;
419     for(i = optind; i < argc; i++) {
420         if(!strcmp(argv[i], "--"))
421             break;
422         s1 = 1;
423         addport(argv[i]);
424     }
425     if(!s1 || (i == argc)) {
426         usage(stderr);
427         exit(1);
428     }
429     if((plex = stdmkchild(argv + ++i)) < 0) {
430         flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
431         return(1);
432     }
433     mustart(plexwatch, plex);
434     if(logsys)
435         opensyslog();
436     if(root) {
437         if(chroot(root)) {
438             flog(LOG_ERR, "could not chroot to %s: %s", root, strerror(errno));
439             exit(1);
440         }
441     }
442     if(pwent) {
443         if(setgid(pwent->pw_gid)) {
444             flog(LOG_ERR, "could not switch group to %i: %s", (int)pwent->pw_gid, strerror(errno));
445             exit(1);
446         }
447         if(setuid(pwent->pw_uid)) {
448             flog(LOG_ERR, "could not switch user to %i: %s", (int)pwent->pw_uid, strerror(errno));
449             exit(1);
450         }
451     }
452     if(daemonize) {
453         daemon(0, 0);
454     }
455     ioloop();
456     return(0);
457 }