1 |
/* Main HTTP server module. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
#include "services.h" |
11 |
#include "modules.h" |
12 |
#include "conffile.h" |
13 |
#include "timeout.h" |
14 |
#include "http.h" |
15 |
|
16 |
#include <netdb.h> |
17 |
#include <netinet/in.h> |
18 |
#include <arpa/inet.h> |
19 |
|
20 |
/*************************************************************************/ |
21 |
|
22 |
static int cb_auth = -1; |
23 |
static int cb_request = -1; |
24 |
|
25 |
|
26 |
static int32 ListenBacklog; |
27 |
static int32 RequestBufferSize; |
28 |
static int32 MaxConnections; |
29 |
static int32 MaxRequests; |
30 |
static time_t IdleTimeout; |
31 |
static int LogConnections; |
32 |
|
33 |
/* List of ports to listen to (set by ListenTo configuration directive) */ |
34 |
static struct listento_ { |
35 |
char ip[16]; /* aaa.bbb.ccc.ddd\0 */ |
36 |
uint16 port; |
37 |
} *ListenTo; |
38 |
static int ListenTo_count; |
39 |
#define MAX_LISTENTO 32767 |
40 |
|
41 |
|
42 |
/* Array of listen sockets (corresponding to ListenTo[] entries) */ |
43 |
static Socket **listen_sockets; |
44 |
|
45 |
/* Array of clients */ |
46 |
static Client *clients; |
47 |
int clients_count; |
48 |
|
49 |
/*************************************************************************/ |
50 |
|
51 |
static void do_accept(Socket *listener, void *param); |
52 |
static void do_disconnect(Socket *socket, void *param_unused); |
53 |
static void do_readline(Socket *s, void *param_unused); |
54 |
static void do_readdata(Socket *s, void *param); |
55 |
static void do_timeout(Timeout *t); |
56 |
|
57 |
static Client *find_client(Socket *s); |
58 |
static void set_timeout(Client *c); |
59 |
static void clear_timeout(Client *c); |
60 |
static void parse_header(Client *c, char *linestart); |
61 |
static void handle_request(Client *c); |
62 |
|
63 |
/*************************************************************************/ |
64 |
/*************************** HTTP server core ****************************/ |
65 |
/*************************************************************************/ |
66 |
|
67 |
/* Accept a connection and perform IP address checks on it; if it doesn't |
68 |
* pass, disconnect it. |
69 |
*/ |
70 |
|
71 |
static void do_accept(Socket *listener, void *param) |
72 |
{ |
73 |
Socket *new = param; |
74 |
struct sockaddr_in sin; |
75 |
int sin_len = sizeof(sin); |
76 |
|
77 |
if (sock_remote(new, (struct sockaddr *)&sin, &sin_len) < 0) { |
78 |
module_log_perror("sock_remote() failed"); |
79 |
} else if (sin_len > sizeof(sin)) { |
80 |
module_log("sock_remote() returned oversize address (%d)", sin_len); |
81 |
} else if (sin.sin_family != AF_INET) { |
82 |
module_log("sock_remote() returned bad address family (%d)", |
83 |
sin.sin_family); |
84 |
} else { |
85 |
int i = clients_count; |
86 |
ARRAY_EXTEND(clients); |
87 |
snprintf(clients[i].address, sizeof(clients[i].address), "%s:%u", |
88 |
unpack_ip((uint8 *)&sin.sin_addr), ntohs(sin.sin_port)); |
89 |
clients[i].socket = new; |
90 |
clients[i].ip = sin.sin_addr.s_addr; |
91 |
clients[i].port = sin.sin_port; |
92 |
clients[i].timeout = NULL; |
93 |
clients[i].request_count = 0; |
94 |
clients[i].in_request = 0; |
95 |
clients[i].request_buf = smalloc(RequestBufferSize); |
96 |
clients[i].request_len = 0; |
97 |
clients[i].version_major = 0; |
98 |
clients[i].version_minor = 0; |
99 |
clients[i].method = -1; |
100 |
clients[i].url = NULL; |
101 |
clients[i].data = NULL; |
102 |
clients[i].data_len = 0; |
103 |
clients[i].headers = NULL; |
104 |
clients[i].headers_count = 0; |
105 |
clients[i].variables = NULL; |
106 |
clients[i].variables_count = 0; |
107 |
if (clients_count >= MaxConnections) { |
108 |
module_log("Dropping connection (exceeded MaxConnections: %d)" |
109 |
" from %s", MaxConnections, clients[i].address); |
110 |
http_error(&clients[i], HTTP_F_SERVICE_UNAVAILABLE, NULL); |
111 |
/* http_error() closes socket for us, so don't try to |
112 |
* disconnect it below */ |
113 |
} else { |
114 |
set_timeout(&clients[i]); |
115 |
sock_setcb(new, SCB_READLINE, do_readline); |
116 |
sock_setcb(new, SCB_DISCONNECT, do_disconnect); |
117 |
sock_set_blocking(new, 1); |
118 |
if (LogConnections) |
119 |
module_log("Accepted connection from %s", |
120 |
clients[i].address); |
121 |
} |
122 |
return; |
123 |
} |
124 |
disconn(new); |
125 |
} |
126 |
|
127 |
/*************************************************************************/ |
128 |
|
129 |
static void do_disconnect(Socket *socket, void *param_unused) |
130 |
{ |
131 |
Client *c = find_client(socket); |
132 |
int index = c - clients; |
133 |
|
134 |
if (!c) { |
135 |
module_log("BUG: unexpected disconnect callback for socket %p",socket); |
136 |
return; |
137 |
} |
138 |
clear_timeout(c); |
139 |
free(c->headers); |
140 |
free(c->variables); |
141 |
free(c->request_buf); |
142 |
ARRAY_REMOVE(clients, index); |
143 |
} |
144 |
|
145 |
/*************************************************************************/ |
146 |
|
147 |
/* Read a line from a client socket. If the end of the request data is |
148 |
* reached, the request is passed to handle_request(); if the request data |
149 |
* length exceeds the buffer size, an error is sent to the client and the |
150 |
* connection is closed. |
151 |
*/ |
152 |
|
153 |
static void do_readline(Socket *socket, void *param_unused) |
154 |
{ |
155 |
Client *c = find_client(socket); |
156 |
char line[HTTP_LINEMAX], *linestart, *s; |
157 |
int32 i; |
158 |
|
159 |
if (!c) { |
160 |
module_log("BUG: unexpected readline callback for socket %p", socket); |
161 |
disconn(socket); |
162 |
return; |
163 |
} |
164 |
|
165 |
if (!sgets(line, sizeof(line), socket) || *line == 0) { |
166 |
module_log("BUG: sgets() failed in readline callback for socket %p", |
167 |
socket); |
168 |
return; |
169 |
} |
170 |
i = strlen(line); |
171 |
if (line[i-1] != '\n') { |
172 |
module_log("%s: Request/header line too long, closing connection", |
173 |
c->address); |
174 |
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL); |
175 |
return; |
176 |
} |
177 |
line[--i] = 0; |
178 |
if (i > 0 && line[i-1] == '\r') |
179 |
line[--i] = 0; |
180 |
i++; /* include trailing \0 */ |
181 |
if (c->request_len + i > RequestBufferSize) { |
182 |
module_log("%s: Request too large, closing connection", c->address); |
183 |
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL); |
184 |
return; |
185 |
} |
186 |
linestart = c->request_buf + c->request_len; |
187 |
memcpy(linestart, line, i); |
188 |
c->request_len += i; |
189 |
|
190 |
if (!c->url) { |
191 |
char *method, *url, *version; |
192 |
if (!*linestart) { |
193 |
/* RFC2616 4.2: servers SHOULD ignore initial empty lines (OK) */ |
194 |
c->request_len = 0; |
195 |
set_timeout(c); |
196 |
return; |
197 |
} |
198 |
method = strtok(linestart, " "); |
199 |
url = strtok(NULL, " "); |
200 |
version = strtok(NULL, " "); |
201 |
if (!method || !url || !version) { |
202 |
/* Note that we don't support REALLY old clients (HTTP 0.9) |
203 |
* which don't send version strings */ |
204 |
/* RFC2616 10.4: SHOULD ensure client has received error before |
205 |
* closing connection (NG) -- in this case the client is so |
206 |
* broken that it doesn't deserve to be worried about */ |
207 |
module_log("%s: Invalid HTTP request", c->address); |
208 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
209 |
return; |
210 |
} |
211 |
if (strcmp(method, "GET") == 0) { |
212 |
c->method = METHOD_GET; |
213 |
} else if (strcmp(method, "HEAD") == 0) { |
214 |
c->method = METHOD_HEAD; |
215 |
} else if (strcmp(method, "POST") == 0) { |
216 |
c->method = METHOD_POST; |
217 |
} else { |
218 |
module_log("%s: Unimplemented/unsupported method `%s' requested", |
219 |
c->address, method); |
220 |
http_error(c, HTTP_F_NOT_IMPLEMENTED, NULL); |
221 |
return; |
222 |
} |
223 |
if (strncmp(version, "HTTP/", 5) != 0 || !(s = strchr(version+5,'.'))){ |
224 |
module_log("%s: Bad HTTP version string: %s", c->address, version); |
225 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
226 |
return; |
227 |
} |
228 |
*s++ = 0; |
229 |
c->version_major = (int)atolsafe(version+5, 0, INT_MAX); |
230 |
c->version_minor = (int)atolsafe(s, 0, INT_MAX); |
231 |
if (c->version_major < 0 || c->version_minor < 0) { |
232 |
module_log("%s: Bad HTTP version string: %s.%s", |
233 |
c->address, version, s); |
234 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
235 |
return; |
236 |
} |
237 |
if (c->version_major != 1) { |
238 |
module_log("%s: Unsupported HTTP version: %d.%d", |
239 |
c->address, c->version_major, c->version_minor); |
240 |
http_error(c, HTTP_F_HTTP_VER_NOT_SUPPORTED, NULL); |
241 |
return; |
242 |
} |
243 |
if (strnicmp(url, "http://", 7) == 0) { |
244 |
/* RFC2616 5.1.2: MUST accept absolute URIs (OK, but we ignore |
245 |
* the hostname and just pretend it's us) */ |
246 |
s = strchr(url+7, '/'); |
247 |
if (s) { |
248 |
strmove(url, s); |
249 |
} else { |
250 |
url[0] = '/'; |
251 |
url[1] = 0; |
252 |
} |
253 |
} |
254 |
c->url = url; |
255 |
set_timeout(c); |
256 |
return; |
257 |
} /* if (!url) */ |
258 |
|
259 |
/* We already have the URL, so this must be a header or blank line. */ |
260 |
if (*linestart) { |
261 |
/* Header line: process it */ |
262 |
parse_header(c, linestart); |
263 |
set_timeout(c); |
264 |
return; |
265 |
} |
266 |
|
267 |
/* End of headers. For GET/HEAD, handle any query string present in |
268 |
* the URL and process the request immediately. For POST, handle |
269 |
* Expect: 100-continue, then deal with the body. */ |
270 |
|
271 |
if (c->method == METHOD_GET || c->method == METHOD_HEAD) { |
272 |
char *s = strchr(c->url, '?'); |
273 |
if (s) { |
274 |
*s++ = 0; |
275 |
c->data = s; |
276 |
c->data_len = strlen(s); |
277 |
} |
278 |
handle_request(c); |
279 |
} else if (c->method == METHOD_POST) { |
280 |
long length; |
281 |
s = http_get_header(c, "Content-Length"); |
282 |
if (!s) { |
283 |
module_log("%s: Missing Content-Length header for POST", |
284 |
c->address); |
285 |
http_error(c, HTTP_E_LENGTH_REQUIRED, NULL); |
286 |
return; |
287 |
} |
288 |
errno = 0; |
289 |
length = atolsafe(s, 0, LONG_MAX); |
290 |
if (length < 0) { |
291 |
module_log("%s: Invalid Content-Length header: %s", c->address, s); |
292 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
293 |
return; |
294 |
} else if (c->request_len+length>RequestBufferSize) { |
295 |
module_log("%s: Request too large, closing connection",c->address); |
296 |
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL); |
297 |
return; |
298 |
} |
299 |
c->data = c->request_buf + c->request_len; |
300 |
c->data_len = (int32)length; |
301 |
if (length > 0) { |
302 |
s = http_get_header(c, "Expect"); |
303 |
for (s = strtok(s, ", \t"); s; s = strtok(NULL, ", \t")) { |
304 |
if (strcmp(s, "100-continue") == 0) { |
305 |
sockprintf(socket, "HTTP/1.1 100 Continue\r\n\r\n"); |
306 |
break; |
307 |
} |
308 |
} |
309 |
/* Set up to read POST data */ |
310 |
sock_setcb(socket, SCB_READ, do_readdata); |
311 |
sock_setcb(socket, SCB_READLINE, NULL); |
312 |
set_timeout(c); |
313 |
} else { |
314 |
/* length == 0: do it just like GET */ |
315 |
handle_request(c); |
316 |
} |
317 |
} else { |
318 |
module_log("BUG: do_readline(): unsupported method %d", c->method); |
319 |
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL); |
320 |
} |
321 |
|
322 |
} |
323 |
|
324 |
/*************************************************************************/ |
325 |
|
326 |
/* Read data from a client socket. When the end of the data is reached, |
327 |
* reached, the request is passed to handle_request(). The request is |
328 |
* assumed to have already been checked for exceeding the buffer size. |
329 |
*/ |
330 |
|
331 |
static void do_readdata(Socket *socket, void *param) |
332 |
{ |
333 |
Client *c = find_client(socket); |
334 |
int32 available = (int32)(long)param, needed, nread; |
335 |
|
336 |
if (!c) { |
337 |
module_log("BUG: unexpected readdata callback for socket %p", socket); |
338 |
disconn(socket); |
339 |
return; |
340 |
} |
341 |
|
342 |
needed = c->data_len - (c->request_len - (c->data - c->request_buf)); |
343 |
if (available > needed) |
344 |
available = needed; |
345 |
if (c->request_len + available > RequestBufferSize) { |
346 |
module_log("BUG: do_readdata(%s[%s]): data size exceeded buffer limit", |
347 |
c->address, c->url); |
348 |
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL); |
349 |
return; |
350 |
} |
351 |
nread = sread(socket, c->request_buf + c->request_len, available); |
352 |
if (nread != available) { |
353 |
module_log("BUG: do_readdata(%s[%s]): nread (%d) != available (%d)", |
354 |
c->address, c->url, nread, available); |
355 |
} |
356 |
c->request_len += nread; |
357 |
needed -= nread; |
358 |
if (needed <= 0) { |
359 |
/* Prepare for next request, if any */ |
360 |
sock_setcb(socket, SCB_READ, NULL); |
361 |
sock_setcb(socket, SCB_READLINE, do_readline); |
362 |
/* Process this request */ |
363 |
handle_request(c); |
364 |
} |
365 |
} |
366 |
|
367 |
/*************************************************************************/ |
368 |
|
369 |
/* Handle an idle timeout on a client. */ |
370 |
|
371 |
static void do_timeout(Timeout *t) |
372 |
{ |
373 |
Client *c = find_client(t->data); |
374 |
if (!c) { |
375 |
module_log("BUG: do_timeout(): client not found for timeout %p!", t); |
376 |
return; |
377 |
} |
378 |
c->timeout = NULL; |
379 |
disconn(c->socket); |
380 |
} |
381 |
|
382 |
/*************************************************************************/ |
383 |
/*************************************************************************/ |
384 |
|
385 |
/* Return the client structure corresponding to the given socket, or NULL |
386 |
* if not found. |
387 |
*/ |
388 |
|
389 |
static Client *find_client(Socket *s) |
390 |
{ |
391 |
int i; |
392 |
|
393 |
ARRAY_FOREACH (i, clients) { |
394 |
if (clients[i].socket == s) |
395 |
return &clients[i]; |
396 |
} |
397 |
return NULL; |
398 |
} |
399 |
|
400 |
/*************************************************************************/ |
401 |
|
402 |
/* Start an idle timer running on the given client. If a timer was already |
403 |
* running, clear it and start a new one. |
404 |
*/ |
405 |
|
406 |
static void set_timeout(Client *c) |
407 |
{ |
408 |
if (!c->socket) { |
409 |
module_log("BUG: attempt to set timeout for client %d with no" |
410 |
" socket!", (int)(c-clients)); |
411 |
return; |
412 |
} |
413 |
if (IdleTimeout) { |
414 |
clear_timeout(c); |
415 |
c->timeout = add_timeout(IdleTimeout, do_timeout, 0); |
416 |
c->timeout->data = c->socket; |
417 |
} |
418 |
} |
419 |
|
420 |
/*************************************************************************/ |
421 |
|
422 |
/* Cancel the idle timer for the given client. */ |
423 |
|
424 |
static void clear_timeout(Client *c) |
425 |
{ |
426 |
if (c->timeout) { |
427 |
del_timeout(c->timeout); |
428 |
c->timeout = NULL; |
429 |
} |
430 |
} |
431 |
|
432 |
/*************************************************************************/ |
433 |
|
434 |
/* Do appropriate things with the given header line. */ |
435 |
|
436 |
static void parse_header(Client *c, char *linestart) |
437 |
{ |
438 |
char *s; |
439 |
|
440 |
/* Check for whitespace-started line and no headers yet */ |
441 |
if ((*linestart == ' ' || *linestart == '\t') && !c->headers_count) { |
442 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
443 |
return; |
444 |
} |
445 |
|
446 |
/* Remove all trailing whitespace */ |
447 |
s = linestart + strlen(linestart) - 1; |
448 |
while (s > linestart && (*s == ' ' || *s == '\t')) { |
449 |
*s-- = 0; |
450 |
c->request_len--; |
451 |
} |
452 |
|
453 |
if (*linestart == ' ' || *linestart == '\t') { |
454 |
/* If it starts with whitespace, just tack it onto the end of the |
455 |
* previous line (convert all leading whitespace to a single space) */ |
456 |
linestart[-1] = ' '; |
457 |
s = linestart; |
458 |
while (*s == ' ' || *s == '\t') |
459 |
s++; |
460 |
strmove(linestart, s); |
461 |
c->request_len -= s-linestart; |
462 |
} else { |
463 |
/* New header: split into name and value, and create a new |
464 |
* headers[] entry */ |
465 |
int i = c->headers_count; |
466 |
ARRAY_EXTEND(c->headers); |
467 |
c->headers[i] = linestart; |
468 |
s = strchr(linestart, ':'); |
469 |
if (!s) { |
470 |
http_error(c, HTTP_E_BAD_REQUEST, NULL); |
471 |
return; |
472 |
} |
473 |
*s++ = 0; |
474 |
linestart = s; |
475 |
while (*s == ' ' || *s == '\t') |
476 |
s++; |
477 |
strmove(linestart, s); |
478 |
c->request_len -= s - linestart; |
479 |
} |
480 |
} |
481 |
|
482 |
/*************************************************************************/ |
483 |
|
484 |
/* Parse the data given by `buf' (null-terminated) into variables and |
485 |
* create an array of them in c->variables. Assume standard |
486 |
* x-www-form-urlencoding encoding (for multipart data, use |
487 |
* parse_data_multipart() below). |
488 |
*/ |
489 |
|
490 |
static void parse_data(Client *c, char *buf) |
491 |
{ |
492 |
char *start; |
493 |
int found_equals = 0; |
494 |
char hexbuf[3]; |
495 |
|
496 |
hexbuf[2] = 0; |
497 |
free(c->variables); |
498 |
c->variables = NULL; |
499 |
c->variables_count = 0; |
500 |
start = buf; |
501 |
|
502 |
ARRAY_EXTEND(c->variables); |
503 |
c->variables[0] = start; |
504 |
while (*buf) { |
505 |
switch (*buf) { |
506 |
case '=': |
507 |
if (!found_equals) { |
508 |
*buf = 0; |
509 |
http_unquote_url(start); |
510 |
found_equals = 1; |
511 |
start = buf+1; |
512 |
} |
513 |
break; |
514 |
case '&': |
515 |
*buf = 0; |
516 |
http_unquote_url(start); |
517 |
found_equals = 0; |
518 |
start = buf+1; |
519 |
ARRAY_EXTEND(c->variables); |
520 |
c->variables[c->variables_count-1] = start; |
521 |
break; |
522 |
} |
523 |
buf++; |
524 |
} |
525 |
} |
526 |
|
527 |
/*************************************************************************/ |
528 |
|
529 |
/* Parse the data given by `buf' (null-terminated) into variables and |
530 |
* create an array of them in c->variables, using the boundary string |
531 |
* given by `boundary'. |
532 |
*/ |
533 |
|
534 |
static void parse_data_multipart(Client *c, char *buf, const char *boundary) |
535 |
{ |
536 |
char *dest = buf; |
537 |
int boundarylen = strlen(boundary); |
538 |
char *varname = NULL; |
539 |
|
540 |
free(c->variables); |
541 |
c->variables = NULL; |
542 |
c->variables_count = 0; |
543 |
|
544 |
buf = strstr(buf, boundary); |
545 |
if (!buf) |
546 |
return; /* boundary string not found */ |
547 |
|
548 |
while (*buf && (buf[boundarylen+2] != '-' || buf[boundarylen+3] != '-')) { |
549 |
char *s; |
550 |
|
551 |
/* Read in header for this part */ |
552 |
s = buf + strcspn(buf, "\r\n"); |
553 |
if (!*s) |
554 |
return; |
555 |
buf = s + strspn(s, "\r") + 1; |
556 |
while (*buf != '\r' && *buf != '\n') { |
557 |
s = buf + strcspn(buf, "\r\n"); |
558 |
if (!*s) |
559 |
return; |
560 |
if (*s == '\r') |
561 |
*s++ = 0; |
562 |
*s++ = 0; |
563 |
if (strnicmp(buf,"Content-Disposition:",20) == 0) { |
564 |
buf += 20; |
565 |
while (*buf && isspace(*buf)) |
566 |
buf++; |
567 |
if (*buf && strnicmp(buf,"form-data;",10) == 0) { |
568 |
buf += 10; |
569 |
while (*buf && isspace(*buf)) |
570 |
buf++; |
571 |
if (*buf && strnicmp(buf,"name=",5) == 0) { |
572 |
buf += 5; |
573 |
if (*buf == '"') { |
574 |
char *t = strchr(++buf, '"'); |
575 |
if (t) |
576 |
*t = 0; |
577 |
} else { |
578 |
char *t = strchr(buf, ';'); |
579 |
if (t) |
580 |
*t = 0; |
581 |
} |
582 |
varname = dest; |
583 |
strmove(dest, buf); |
584 |
dest += strlen(buf)+1; |
585 |
} |
586 |
} |
587 |
} |
588 |
buf = s; |
589 |
} /* while (*buf != '\r' && *buf != '\n') */ |
590 |
if (*buf == '\r') |
591 |
buf++; |
592 |
buf++; |
593 |
|
594 |
/* Read in data (variable contents) */ |
595 |
if (varname) { |
596 |
ARRAY_EXTEND(c->variables); |
597 |
c->variables[c->variables_count-1] = varname; |
598 |
varname = NULL; |
599 |
} |
600 |
s = buf + strcspn(buf, "\r\n"); |
601 |
if (s > buf) { |
602 |
memmove(dest, buf, s-buf); |
603 |
dest += s-buf; |
604 |
} |
605 |
if (*s == '\r') |
606 |
s++; |
607 |
buf = s; |
608 |
/* *buf is always pointing to a \n or \0 at the top of this loop */ |
609 |
while (*buf && (buf[1] != '-' || buf[2] != '-' |
610 |
|| strncmp(buf+3, boundary, boundarylen) != 0)) { |
611 |
s = buf+1 + strcspn(buf+1, "\r\n"); |
612 |
if (!s) |
613 |
s = buf + strlen(buf); |
614 |
memmove(dest, buf, s-buf); |
615 |
dest += s-buf; |
616 |
if (*s == '\r') |
617 |
s++; |
618 |
buf = s; |
619 |
} |
620 |
/* Null-terminate variable contents */ |
621 |
*dest++ = 0; |
622 |
/* Skip over newline */ |
623 |
if (*buf) |
624 |
buf++; |
625 |
} /* while not final boundary line */ |
626 |
} |
627 |
|
628 |
/*************************************************************************/ |
629 |
|
630 |
static void handle_request(Client *c) |
631 |
{ |
632 |
int res; |
633 |
int close = 0; |
634 |
|
635 |
/* Parse GET query string or POST data into variables */ |
636 |
if (c->data && c->data_len) { |
637 |
char *s; |
638 |
if (c->method == METHOD_POST) { |
639 |
/* There were at least two newlines before the beginning of the |
640 |
* data, so it's safe to move it back a byte (to add a trailing |
641 |
* null) */ |
642 |
memmove(c->data-1, c->data, c->data_len); |
643 |
c->data--; |
644 |
c->data[c->data_len] = 0; |
645 |
} |
646 |
/* Check the content type, and extract the boundary string if it's |
647 |
* multipart data */ |
648 |
s = http_get_header(c, "Content-Type"); |
649 |
if (s && strnicmp(s, "multipart/form-data;", 20) == 0) { |
650 |
s += 20; |
651 |
while (isspace(*s)) |
652 |
s++; |
653 |
if (strnicmp(s, "boundary=", 9) == 0) { |
654 |
s += 9; |
655 |
if (*s == '"') { |
656 |
char *t = strchr(++s, '"'); |
657 |
if (t) |
658 |
*t = 0; |
659 |
} |
660 |
} else { |
661 |
s = NULL; |
662 |
} |
663 |
} else { |
664 |
s = NULL; |
665 |
} |
666 |
/* Parse data into variables */ |
667 |
if (s) |
668 |
parse_data_multipart(c, c->data, s); |
669 |
else |
670 |
parse_data(c, c->data); |
671 |
} |
672 |
|
673 |
c->request_count++; |
674 |
c->in_request = 1; |
675 |
|
676 |
if (c->version_major == 1 && c->version_minor == 0) { |
677 |
close = 1; |
678 |
} else { |
679 |
const char *s = http_get_header(c, "Connection"); |
680 |
if (s && strstr(s, "close")) { |
681 |
/* This might accidentally trigger on something like "abcloseyz", |
682 |
* but that's okay; all it means is the client has to reconnect */ |
683 |
close = 1; |
684 |
} |
685 |
} |
686 |
|
687 |
res = call_callback_2(cb_auth, c, &close); |
688 |
if (res < 0) { |
689 |
module_log("handle_request(): call_callback(cb_request) failed"); |
690 |
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL); |
691 |
close = 1; |
692 |
} else if (res != HTTP_AUTH_DENY) { |
693 |
res = call_callback_2(cb_request, c, &close); |
694 |
if (res < 0) { |
695 |
module_log("handle_request(): call_callback(cb_request) failed"); |
696 |
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL); |
697 |
close = 1; |
698 |
} else if (res == 0) { |
699 |
http_error(c, HTTP_E_NOT_FOUND, NULL); |
700 |
} |
701 |
} |
702 |
|
703 |
if (close || (MaxRequests && c->request_count >= MaxRequests) |
704 |
|| c->in_request < 0 /* flag from http_error */ |
705 |
) { |
706 |
disconn(c->socket); |
707 |
} else { |
708 |
free(c->headers); |
709 |
free(c->variables); |
710 |
c->in_request = 0; |
711 |
c->request_len = 0; |
712 |
c->version_major = 0; |
713 |
c->version_minor = 0; |
714 |
c->method = -1; |
715 |
c->url = NULL; |
716 |
c->data = NULL; |
717 |
c->data_len = 0; |
718 |
c->headers = NULL; |
719 |
c->headers_count = 0; |
720 |
c->variables = NULL; |
721 |
c->variables_count = 0; |
722 |
set_timeout(c); |
723 |
} |
724 |
} |
725 |
|
726 |
/*************************************************************************/ |
727 |
/***************************** Module stuff ******************************/ |
728 |
/*************************************************************************/ |
729 |
|
730 |
static int do_ListenTo(const char *filename, int linenum, char *param); |
731 |
|
732 |
ConfigDirective module_config[] = { |
733 |
{ "IdleTimeout", { { CD_TIME, 0, &IdleTimeout } } }, |
734 |
{ "ListenBacklog", { { CD_POSINT, CF_DIRREQ, &ListenBacklog } } }, |
735 |
{ "ListenTo", { { CD_FUNC, CF_DIRREQ, do_ListenTo } } }, |
736 |
{ "LogConnections", { { CD_SET, 0, &LogConnections } } }, |
737 |
{ "MaxConnections", { { CD_POSINT, 0, &MaxConnections } } }, |
738 |
{ "MaxRequests", { { CD_POSINT, 0, &MaxRequests } } }, |
739 |
{ "RequestBufferSize",{ { CD_POSINT, 0, &RequestBufferSize } } }, |
740 |
{ NULL } |
741 |
}; |
742 |
|
743 |
/*************************************************************************/ |
744 |
|
745 |
static int do_ListenTo(const char *filename, int linenum, char *param) |
746 |
{ |
747 |
char *s; |
748 |
int port; |
749 |
uint8 *ip; |
750 |
char *ipstr; |
751 |
char ipbuf[15+1]; /* aaa.bbb.ccc.ddd\0 */ |
752 |
int recursing = 0, i; |
753 |
static struct listento_ *new_ListenTo; |
754 |
static int new_ListenTo_count; |
755 |
|
756 |
if (!filename) { |
757 |
/* filename == NULL, perform special operations */ |
758 |
switch (linenum) { |
759 |
case CDFUNC_INIT: /* prepare to read new data */ |
760 |
free(new_ListenTo); |
761 |
new_ListenTo = NULL; |
762 |
new_ListenTo_count = 0; |
763 |
break; |
764 |
case CDFUNC_SET: /* store new data in config variable */ |
765 |
free(ListenTo); |
766 |
ListenTo = new_ListenTo; |
767 |
ListenTo_count = new_ListenTo_count; |
768 |
new_ListenTo = NULL; |
769 |
new_ListenTo_count = 0; |
770 |
break; |
771 |
case CDFUNC_DECONFIG: /* clear any stored data */ |
772 |
free(ListenTo); |
773 |
ListenTo = NULL; |
774 |
ListenTo_count = 0; |
775 |
break; |
776 |
} /* switch (linenum) */ |
777 |
return 1; |
778 |
} /* if (!filename) */ |
779 |
|
780 |
/* filename != NULL, process directive */ |
781 |
|
782 |
if (linenum < 0) { |
783 |
recursing = 1; |
784 |
linenum = -linenum; |
785 |
} |
786 |
|
787 |
if (ListenTo_count >= MAX_LISTENTO) { |
788 |
config_error(filename, linenum, |
789 |
"Too many ListenTo addresses (maximum %d)", |
790 |
MAX_LISTENTO); |
791 |
return 0; |
792 |
} |
793 |
|
794 |
s = strchr(param, ':'); |
795 |
if (!s) { |
796 |
config_error(filename, linenum, |
797 |
"ListenTo address requires both address and port"); |
798 |
return 0; |
799 |
} |
800 |
|
801 |
*s++ = 0; |
802 |
port = atolsafe(s, 1, 65535); |
803 |
if (port < 1) { |
804 |
config_error(filename, linenum, "Invalid port number `%s'", s); |
805 |
return 0; |
806 |
} |
807 |
|
808 |
if (strcmp(param, "*") == 0) { |
809 |
/* "*" -> all addresses (NULL string) */ |
810 |
ipstr = NULL; |
811 |
} else if ((ip = pack_ip(param)) != NULL) { |
812 |
/* IP address -> normalize (no leading zeros, etc.) */ |
813 |
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u", |
814 |
ip[0], ip[1], ip[2], ip[3]); |
815 |
if (strlen(ipbuf) > 15) { |
816 |
config_error(filename, linenum, "BUG: strlen(ipbuf) > 15 [%s]", |
817 |
ipbuf); |
818 |
return 0; |
819 |
} |
820 |
ipstr = ipbuf; |
821 |
} else { |
822 |
/* hostname -> check for double recursion, then look up and |
823 |
* recursively add addresses */ |
824 |
#ifdef HAVE_GETHOSTBYNAME |
825 |
struct hostent *hp; |
826 |
#endif |
827 |
if (recursing) { |
828 |
config_error(filename, linenum, "BUG: double recursion (param=%s)", |
829 |
param); |
830 |
return 0; |
831 |
} |
832 |
#ifdef HAVE_GETHOSTBYNAME |
833 |
if ((hp = gethostbyname(param)) != NULL) { |
834 |
if (hp->h_addrtype == AF_INET) { |
835 |
for (i = 0; hp->h_addr_list[i]; i++) { |
836 |
ip = (uint8 *)hp->h_addr_list[i]; |
837 |
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u", |
838 |
ip[0], ip[1], ip[2], ip[3]); |
839 |
if (strlen(ipbuf) > 15) { |
840 |
config_error(filename, linenum, |
841 |
"BUG: strlen(ipbuf) > 15 [%s]", ipbuf); |
842 |
return 0; |
843 |
} |
844 |
if (!do_ListenTo(filename, -linenum, ipbuf)) |
845 |
return 0; |
846 |
} |
847 |
return 1; /* Success */ |
848 |
} else { |
849 |
config_error(filename, linenum, "%s: no IPv4 addresses found", |
850 |
param); |
851 |
} |
852 |
} else { |
853 |
config_error(filename, linenum, "%s: %s", param, |
854 |
hstrerror(h_errno)); |
855 |
} |
856 |
#else |
857 |
config_error(filename, linenum, |
858 |
"gethostbyname() not available, hostnames may not be" |
859 |
" used"); |
860 |
#endif |
861 |
return 0; |
862 |
} |
863 |
|
864 |
i = new_ListenTo_count; |
865 |
ARRAY_EXTEND(new_ListenTo); |
866 |
if (ipstr) |
867 |
strcpy(new_ListenTo[i].ip, ipstr);/*safe: strlen(ip)<16 checked above*/ |
868 |
else |
869 |
memset(new_ListenTo[i].ip, 0, sizeof(new_ListenTo[i].ip)); |
870 |
new_ListenTo[i].port = port; |
871 |
return 1; |
872 |
} |
873 |
|
874 |
/*************************************************************************/ |
875 |
|
876 |
int init_module() |
877 |
{ |
878 |
int i, opencount; |
879 |
|
880 |
cb_auth = register_callback("auth"); |
881 |
cb_request = register_callback("request"); |
882 |
if (cb_auth < 0 || cb_request < 0) { |
883 |
module_log("Unable to register callbacks"); |
884 |
exit_module(0); |
885 |
return 0; |
886 |
} |
887 |
|
888 |
listen_sockets = smalloc(sizeof(*listen_sockets) * ListenTo_count); |
889 |
opencount = 0; |
890 |
ARRAY_FOREACH (i, ListenTo) { |
891 |
listen_sockets[i] = sock_new(); |
892 |
if (listen_sockets[i]) { |
893 |
if (open_listener(listen_sockets[i], |
894 |
*ListenTo[i].ip ? ListenTo[i].ip : NULL, |
895 |
ListenTo[i].port, ListenBacklog) == 0) { |
896 |
sock_setcb(listen_sockets[i], SCB_ACCEPT, do_accept); |
897 |
module_log("Listening on %s:%u", |
898 |
ListenTo[i].ip, ListenTo[i].port); |
899 |
opencount++; |
900 |
} else { |
901 |
module_log_perror("Failed to open listen socket for %s:%u", |
902 |
ListenTo[i].ip, ListenTo[i].port); |
903 |
} |
904 |
} else { |
905 |
module_log("Failed to create listen socket for %s:%u", |
906 |
*ListenTo[i].ip ? ListenTo[i].ip : "*", |
907 |
ListenTo[i].port); |
908 |
} |
909 |
} |
910 |
if (!opencount) { |
911 |
module_log("No ports could be opened, aborting"); |
912 |
return 0; |
913 |
} |
914 |
|
915 |
return 1; |
916 |
} |
917 |
|
918 |
/*************************************************************************/ |
919 |
|
920 |
int exit_module(int shutdown_unused) |
921 |
{ |
922 |
int i; |
923 |
|
924 |
ARRAY_FOREACH (i, ListenTo) { |
925 |
if (listen_sockets[i]) { |
926 |
close_listener(listen_sockets[i]); |
927 |
sock_free(listen_sockets[i]); |
928 |
} |
929 |
} |
930 |
free(ListenTo); |
931 |
ListenTo = NULL; |
932 |
ListenTo_count = 0; |
933 |
free(listen_sockets); |
934 |
listen_sockets = NULL; |
935 |
|
936 |
unregister_callback(cb_request); |
937 |
unregister_callback(cb_auth); |
938 |
|
939 |
return 1; |
940 |
} |
941 |
|
942 |
/*************************************************************************/ |
943 |
|
944 |
/* |
945 |
* Local variables: |
946 |
* c-file-style: "stroustrup" |
947 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
948 |
* indent-tabs-mode: nil |
949 |
* End: |
950 |
* |
951 |
* vim: expandtab shiftwidth=4: |
952 |
*/ |