Added a simple CGI endcap.
[ashd.git] / src / htparser.c
CommitLineData
f0bbedf7
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#include <stdlib.h>
20#include <unistd.h>
21#include <stdio.h>
f4cdf919 22#include <string.h>
f4cdf919
FT
23#include <sys/socket.h>
24#include <netinet/in.h>
9d87a119 25#include <arpa/inet.h>
f4cdf919 26#include <errno.h>
f0bbedf7
FT
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <utils.h>
f4cdf919 32#include <mt.h>
83723896 33#include <mtio.h>
f4cdf919 34#include <log.h>
66987955 35#include <req.h>
9d87a119 36#include <proc.h>
f4cdf919 37
83723896 38static int plex;
f4cdf919
FT
39
40static 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
64static 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
c9955b14 88static size_t readhead(int fd, struct charbuf *buf)
66987955 89{
66987955 90 int nl;
c9955b14 91 size_t off;
66987955 92
c9955b14
FT
93 int get1(void)
94 {
95 int ret;
96
97 while(!(off < buf->d)) {
98 sizebuf(*buf, buf->d + 1024);
99 ret = recv(fd, buf->b + buf->d, buf->s - buf->d, MSG_DONTWAIT);
100 if(ret <= 0) {
101 if((ret < 0) && (errno == EAGAIN)) {
9d87a119
FT
102 if(block(fd, EV_READ, 60) <= 0)
103 return(-1);
c9955b14
FT
104 continue;
105 }
106 return(-1);
66987955 107 }
c9955b14 108 buf->d += ret;
66987955 109 }
c9955b14
FT
110 return(buf->b[off++]);
111 }
112
113 nl = 0;
114 off = 0;
115 while(1) {
116 switch(get1()) {
117 case '\n':
66987955 118 if(nl)
c9955b14 119 return(off);
66987955 120 nl = 1;
c9955b14
FT
121 break;
122 case '\r':
123 break;
124 case -1:
125 return(-1);
126 default:
66987955 127 nl = 0;
c9955b14 128 break;
66987955
FT
129 }
130 }
66987955
FT
131}
132
133#define SKIPNL(ptr) ({ \
134 int __buf__; \
135 if(*(ptr) == '\r') \
136 *((ptr)++) = 0; \
137 if(*(ptr) != '\n') { \
138 __buf__ = 0; \
139 } else { \
140 *((ptr)++) = 0; \
141 __buf__ = 1; \
142 } \
143 __buf__;})
9d87a119 144static struct hthead *parserawreq(char *buf)
66987955
FT
145{
146 char *p, *p2, *nl;
147 char *method, *url, *ver;
9d87a119 148 struct hthead *req;
66987955
FT
149
150 if((nl = strchr(buf, '\n')) == NULL)
151 return(NULL);
152 if(((p = strchr(buf, ' ')) == NULL) || (p > nl))
153 return(NULL);
154 method = buf;
155 *(p++) = 0;
156 if(((p2 = strchr(p, ' ')) == NULL) || (p2 > nl))
157 return(NULL);
158 url = p;
159 p = p2;
160 *(p++) = 0;
161 if(strncmp(p, "HTTP/", 5))
162 return(NULL);
163 ver = (p += 5);
164 for(; ((*p >= '0') && (*p <= '9')) || (*p == '.'); p++);
165 if(!SKIPNL(p))
166 return(NULL);
167
168 req = mkreq(method, url, ver);
169 while(1) {
170 if(SKIPNL(p)) {
171 if(*p)
c9955b14 172 goto fail;
66987955
FT
173 break;
174 }
175 if((nl = strchr(p, '\n')) == NULL)
c9955b14 176 goto fail;
66987955 177 if(((p2 = strchr(p, ':')) == NULL) || (p2 > nl))
c9955b14 178 goto fail;
66987955
FT
179 *(p2++) = 0;
180 for(; (*p2 == ' ') || (*p2 == '\t'); p2++);
181 for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
182 if(!SKIPNL(nl))
c9955b14 183 goto fail;
9d87a119
FT
184 if(strncasecmp(p, "x-ash-", 6))
185 headappheader(req, p, p2);
66987955
FT
186 p = nl;
187 }
188 return(req);
c9955b14
FT
189
190fail:
9d87a119 191 freehthead(req);
c9955b14 192 return(NULL);
66987955
FT
193}
194
9d87a119
FT
195static struct hthead *parserawresp(char *buf)
196{
197 char *p, *p2, *nl;
198 char *msg, *ver;
199 int code;
200 struct hthead *resp;
201
202 if((nl = strchr(buf, '\n')) == NULL)
203 return(NULL);
204 p = strchr(buf, '\r');
205 if((p != NULL) && (p < nl))
206 nl = p;
207 if(strncmp(buf, "HTTP/", 5))
208 return(NULL);
209 ver = p = buf + 5;
210 for(; ((*p >= '0') && (*p <= '9')) || (*p == '.'); p++);
211 if(*p != ' ')
212 return(NULL);
213 *(p++) = 0;
214 if(((p2 = strchr(p, ' ')) == NULL) || (p2 > nl))
215 return(NULL);
216 *(p2++) = 0;
217 code = atoi(p);
218 if((code < 100) || (code >= 600))
219 return(NULL);
220 if(p2 >= nl)
221 return(NULL);
222 msg = p2;
223 p = nl;
224 if(!SKIPNL(p))
225 return(NULL);
226
227 resp = mkresp(code, msg, ver);
228 while(1) {
229 if(SKIPNL(p)) {
230 if(*p)
231 goto fail;
232 break;
233 }
234 if((nl = strchr(p, '\n')) == NULL)
235 goto fail;
236 if(((p2 = strchr(p, ':')) == NULL) || (p2 > nl))
237 goto fail;
238 *(p2++) = 0;
239 for(; (*p2 == ' ') || (*p2 == '\t'); p2++);
240 for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
241 if(!SKIPNL(nl))
242 goto fail;
243 headappheader(resp, p, p2);
244 p = nl;
245 }
246 return(resp);
247
248fail:
249 freehthead(resp);
250 return(NULL);
251}
252
253static off_t passdata(int src, int dst, struct charbuf *buf, off_t max)
254{
255 size_t dataoff, smax;
256 off_t sent;
257 int eof, ret;
258
259 sent = 0;
260 eof = 0;
d93d9a05 261 while((!eof || (buf->d > 0)) && ((max < 0) || (sent < max))) {
9d87a119
FT
262 if(!eof && (buf->d < buf->s) && ((max < 0) || (sent + buf->d < max))) {
263 while(1) {
264 ret = recv(src, buf->b + buf->d, buf->s - buf->d, MSG_DONTWAIT);
265 if((ret < 0) && (errno == EAGAIN)) {
266 } else if(ret < 0) {
267 return(-1);
268 } else if(ret == 0) {
269 eof = 1;
270 break;
271 } else {
272 buf->d += ret;
273 break;
274 }
275 if(buf->d > 0)
276 break;
277 if(block(src, EV_READ, 0) <= 0)
278 return(-1);
279 }
280 }
281 for(dataoff = 0; (dataoff < buf->d) && ((max < 0) || (sent < max));) {
282 if(block(dst, EV_WRITE, 120) <= 0)
283 return(-1);
284 smax = buf->d - dataoff;
285 if(sent + smax > max)
286 smax = max - sent;
287 ret = send(dst, buf->b + dataoff, smax, MSG_NOSIGNAL | MSG_DONTWAIT);
288 if(ret < 0)
289 return(-1);
290 dataoff += ret;
291 sent += ret;
292 }
293 bufeat(*buf, dataoff);
294 }
295 return(sent);
296}
297
66987955
FT
298static void serve(struct muth *muth, va_list args)
299{
300 vavar(int, fd);
9d87a119
FT
301 vavar(struct sockaddr_storage, name);
302 int cfd;
af34331c 303 int pfds[2];
9d87a119 304 char old;
edad3c6a 305 char *hd, *p;
9d87a119
FT
306 struct charbuf inbuf, outbuf;
307 struct hthead *req, *resp;
a0327573 308 off_t dlen, sent;
d93d9a05 309 ssize_t headoff;
9d87a119 310 char nmbuf[256];
66987955 311
9d87a119
FT
312 bufinit(inbuf);
313 bufinit(outbuf);
314 cfd = -1;
3c296bd4 315 req = resp = NULL;
66987955 316 while(1) {
9d87a119
FT
317 /*
318 * First, find and decode the header:
319 */
320 if((headoff = readhead(fd, &inbuf)) < 0)
321 goto out;
322 if(headoff > 65536) {
323 /* We cannot handle arbitrarily large headers, as they
324 * need to fit within a single Unix datagram. This is
325 * probably a safe limit, and larger packets than this are
326 * most likely erroneous (or malicious) anyway. */
327 goto out;
328 }
329 old = inbuf.b[headoff];
330 inbuf.b[headoff] = 0;
331 if((req = parserawreq(inbuf.b)) == NULL)
66987955 332 goto out;
9d87a119
FT
333 inbuf.b[headoff] = old;
334 bufeat(inbuf, headoff);
edad3c6a
FT
335 /* We strip off the leading slash and any param string from
336 * the rest string, so that multiplexers can parse
337 * coherently. */
9e9eca79
FT
338 if(req->rest[0] == '/')
339 replrest(req, req->rest + 1);
edad3c6a
FT
340 if((p = strchr(req->rest, '?')) != NULL)
341 *p = 0;
9d87a119
FT
342
343 /*
344 * Add metainformation and then send the request to the root
345 * multiplexer:
346 */
347 if(name.ss_family == AF_INET) {
348 headappheader(req, "X-Ash-Address", inet_ntop(AF_INET, &((struct sockaddr_in *)&name)->sin_addr, nmbuf, sizeof(nmbuf)));
349 headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&name)->sin_port)));
350 } else if(name.ss_family == AF_INET6) {
351 headappheader(req, "X-Ash-Address", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&name)->sin6_addr, nmbuf, sizeof(nmbuf)));
352 headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&name)->sin6_port)));
353 }
46c3d430
FT
354 if(block(plex, EV_WRITE, 60) <= 0)
355 goto out;
af34331c 356 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
3c296bd4 357 goto out;
af34331c
FT
358 if(sendreq(plex, req, pfds[0]))
359 goto out;
360 close(pfds[0]);
361 cfd = pfds[1];
a0327573
FT
362
363 /*
364 * If there is message data, pass it:
365 */
366 if((hd = getheader(req, "content-length")) != NULL) {
367 dlen = atoo(hd);
368 if(dlen > 0)
369 passdata(fd, cfd, &inbuf, dlen);
370 }
d93d9a05
FT
371 /* Make sure to send EOF */
372 shutdown(cfd, SHUT_WR);
9d87a119
FT
373
374 /*
375 * Find and decode the response header:
376 */
377 outbuf.d = 0;
3c296bd4
FT
378 if((headoff = readhead(cfd, &outbuf)) < 0)
379 goto out;
9d87a119
FT
380 hd = memcpy(smalloc(headoff + 1), outbuf.b, headoff);
381 hd[headoff] = 0;
382 if((resp = parserawresp(hd)) == NULL)
66987955 383 goto out;
9d87a119
FT
384
385 /*
386 * Pass the actual output:
387 */
388 sizebuf(outbuf, 65536);
389 sent = passdata(cfd, fd, &outbuf, -1);
390 sent -= headoff;
391
392 /*
393 * Check for connection expiry
394 */
395 if(strcasecmp(req->method, "head")) {
396 if((hd = getheader(resp, "content-length")) != NULL) {
397 if(sent != atoo(hd)) {
398 /* Exit because of error */
399 goto out;
400 }
401 } else {
402 if(((hd = getheader(resp, "transfer-encoding")) == NULL) || !strcasecmp(hd, "identity"))
403 break;
404 }
405 if(((hd = getheader(req, "connection")) != NULL) && !strcasecmp(hd, "close"))
406 break;
407 if(((hd = getheader(resp, "connection")) != NULL) && !strcasecmp(hd, "close"))
408 break;
409 }
410
411 close(cfd);
412 cfd = -1;
413 freehthead(req);
414 req = NULL;
415 freehthead(resp);
416 resp = NULL;
66987955
FT
417 }
418
419out:
9d87a119
FT
420 if(cfd >= 0)
421 close(cfd);
422 if(req != NULL)
423 freehthead(req);
424 if(resp != NULL)
425 freehthead(resp);
426 buffree(inbuf);
427 buffree(outbuf);
66987955
FT
428 close(fd);
429}
430
f4cdf919
FT
431static void listenloop(struct muth *muth, va_list args)
432{
433 vavar(int, ss);
434 int ns;
435 struct sockaddr_storage name;
436 socklen_t namelen;
437
438 while(1) {
439 namelen = sizeof(name);
9d87a119 440 block(ss, EV_READ, 0);
f4cdf919 441 ns = accept(ss, (struct sockaddr *)&name, &namelen);
66987955
FT
442 if(ns < 0) {
443 flog(LOG_ERR, "accept: %s", strerror(errno));
444 goto out;
445 }
9d87a119 446 mustart(serve, ns, name);
f4cdf919 447 }
66987955
FT
448
449out:
450 close(ss);
f4cdf919
FT
451}
452
f0bbedf7
FT
453int main(int argc, char **argv)
454{
f4cdf919
FT
455 int fd;
456
9d87a119
FT
457 if(argc < 2) {
458 fprintf(stderr, "usage: htparser ROOT [ARGS...]\n");
459 exit(1);
460 }
461 if((plex = stdmkchild(argv + 1)) < 0) {
462 flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
463 return(1);
464 }
f4cdf919
FT
465 if((fd = listensock6(8080)) < 0) {
466 flog(LOG_ERR, "could not listen on IPv6: %s", strerror(errno));
467 return(1);
468 }
469 mustart(listenloop, fd);
470 if((fd = listensock4(8080)) < 0) {
471 if(errno != EADDRINUSE) {
472 flog(LOG_ERR, "could not listen on IPv4: %s", strerror(errno));
473 return(1);
474 }
475 } else {
476 mustart(listenloop, fd);
477 }
478 ioloop();
479 return(0);
f0bbedf7 480}