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