1 |
/** |
2 |
* httpd.C: Generic HTTPd |
3 |
* |
4 |
* Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team |
5 |
* Rights to this code are as documented in COPYING. |
6 |
* |
7 |
* |
8 |
* Portions of this file were derived from sources bearing the following license: |
9 |
* Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team |
10 |
* Copyright © 2005-2007 Jilles Tjoelker et al. |
11 |
* Rights to this code are as documented in doc/pod/license.pod. |
12 |
* |
13 |
* $Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $ |
14 |
*/ |
15 |
|
16 |
#include "atheme.h" |
17 |
#include <libermyth.h> |
18 |
#include <ermyth/module.h> |
19 |
#include "httpd.h" |
20 |
#include "datastream.h" |
21 |
#include "connection.h" |
22 |
#include "confparse.h" |
23 |
|
24 |
#define REQUEST_MAX 65536 /* maximum size of one call */ |
25 |
|
26 |
static char const rcsid[] = "$Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $"; |
27 |
|
28 |
static void on_config_ready (void); |
29 |
|
30 |
REGISTER_MODULE ("rpc/httpd", false, "The Ermyth Team <http://ermyth.one09.net>"); |
31 |
|
32 |
static connection_t *listener; |
33 |
pathvec pathhandlers; |
34 |
|
35 |
/* conf stuff */ |
36 |
ConfTable::list_type conf_httpd_table; |
37 |
|
38 |
static struct httpd_configuration |
39 |
{ |
40 |
char *host; |
41 |
char *www_root; |
42 |
int port; |
43 |
|
44 |
void clear () |
45 |
{ |
46 |
if (host != NULL) |
47 |
sfree (host); |
48 |
if (www_root != NULL) |
49 |
sfree (www_root); |
50 |
host = NULL; |
51 |
www_root = NULL; |
52 |
} |
53 |
} httpd_config; |
54 |
|
55 |
static int |
56 |
conf_httpd_host (config_entry_t *ce) |
57 |
{ |
58 |
if (!ce->valid ()) |
59 |
return -1; |
60 |
|
61 |
if (httpd_config.host != NULL) |
62 |
sfree (httpd_config.host); |
63 |
httpd_config.host = sstrdup (ce->vardata<char *> ()); |
64 |
|
65 |
return 0; |
66 |
} |
67 |
|
68 |
static int |
69 |
conf_httpd_www_root (config_entry_t *ce) |
70 |
{ |
71 |
if (!ce->valid ()) |
72 |
return -1; |
73 |
|
74 |
if (httpd_config.www_root != NULL) |
75 |
sfree (httpd_config.www_root); |
76 |
httpd_config.www_root = sstrdup (ce->vardata<char *> ()); |
77 |
|
78 |
return 0; |
79 |
} |
80 |
|
81 |
static int |
82 |
conf_httpd_port (config_entry_t *ce) |
83 |
{ |
84 |
if (!ce->valid ()) |
85 |
return -1; |
86 |
|
87 |
httpd_config.port = ce->vardata<int> (); |
88 |
|
89 |
return 0; |
90 |
} |
91 |
|
92 |
static int |
93 |
conf_httpd (config_entry_t *ce) |
94 |
{ |
95 |
subblock_handler (ce, conf_httpd_table); |
96 |
return 0; |
97 |
} |
98 |
|
99 |
void |
100 |
httpddata::clear (void) |
101 |
{ |
102 |
method[0] = '\0'; |
103 |
filename[0] = '\0'; |
104 |
if (requestbuf != NULL) |
105 |
{ |
106 |
sfree (requestbuf); |
107 |
requestbuf = NULL; |
108 |
} |
109 |
if (replybuf != NULL) |
110 |
{ |
111 |
sfree (replybuf); |
112 |
replybuf = NULL; |
113 |
} |
114 |
length = 0; |
115 |
lengthdone = 0; |
116 |
correct_content_type = false; |
117 |
expect_100_continue = false; |
118 |
sent_reply = false; |
119 |
} |
120 |
|
121 |
static int |
122 |
open_file (char const *filename) |
123 |
{ |
124 |
char fname[256]; |
125 |
|
126 |
if (strstr (filename, "..")) |
127 |
return -1; |
128 |
if (!strcmp (filename, "/")) |
129 |
filename = "/index.html"; |
130 |
snprintf (fname, sizeof fname, "%s/%s", httpd_config.www_root, filename); |
131 |
return open (fname, O_RDONLY); |
132 |
} |
133 |
|
134 |
static void |
135 |
process_header (connection_t *cptr, char *line) |
136 |
{ |
137 |
httpddata *hd; |
138 |
char *p; |
139 |
|
140 |
hd = static_cast<httpddata *> (cptr->userdata); |
141 |
p = strchr (line, ':'); |
142 |
if (p == NULL) |
143 |
return; |
144 |
*p = '\0'; |
145 |
p++; |
146 |
while (*p == ' ') |
147 |
p++; |
148 |
if (!strcasecmp (line, "Connection")) |
149 |
{ |
150 |
p = strtok (p, ", \t"); |
151 |
while (p != NULL) |
152 |
{ |
153 |
if (!strcasecmp (p, "close")) |
154 |
{ |
155 |
slog (LG_DEBUG, "process_header(): Connection: close requested by fd %d", cptr->fd); |
156 |
hd->connection_close = true; |
157 |
} |
158 |
p = strtok (NULL, ", \t"); |
159 |
} |
160 |
} |
161 |
else if (!strcasecmp (line, "Content-Length")) |
162 |
hd->length = atoi (p); |
163 |
else if (!strcasecmp (line, "Content-Type")) |
164 |
{ |
165 |
p = strtok (p, "; \t"); |
166 |
hd->correct_content_type = p != NULL && (!strcasecmp (p, "text/xml") || !strcasecmp (p, "application/json")); |
167 |
} |
168 |
else if (!strcasecmp (line, "Expect")) |
169 |
hd->expect_100_continue = !strcasecmp (p, "100-continue"); |
170 |
} |
171 |
|
172 |
static void |
173 |
check_close (connection_t *cptr) |
174 |
{ |
175 |
httpddata *hd; |
176 |
|
177 |
hd = static_cast<httpddata *> (cptr->userdata); |
178 |
if (hd->connection_close) |
179 |
sendq_add_eof (cptr); |
180 |
} |
181 |
|
182 |
static void |
183 |
send_error (connection_t *cptr, int errorcode, char const * const text, bool const sendentity) |
184 |
{ |
185 |
httpddata *hd; |
186 |
char buf1[300]; |
187 |
char buf2[700]; |
188 |
|
189 |
hd = static_cast<httpddata *> (cptr->userdata); |
190 |
if (errorcode < 100 || errorcode > 999) |
191 |
errorcode = 500; |
192 |
snprintf (buf2, sizeof buf2, "HTTP/1.1 %d %s\r\n", errorcode, text); |
193 |
snprintf (buf1, sizeof buf1, "HTTP/1.1 %d %s\r\n" |
194 |
"Server: " PACKAGE_NAME "/%s\r\n" |
195 |
"Content-Type: text/plain\r\n" |
196 |
"Content-Length: %" PRIsize "\r\n\r\n%s", |
197 |
errorcode, text, version, strlen (buf2), sendentity ? buf2 : ""); |
198 |
sendq_add (cptr, buf1, strlen (buf1)); |
199 |
} |
200 |
|
201 |
static char const * const |
202 |
content_type (char const * const filename) |
203 |
{ |
204 |
char const *p; |
205 |
|
206 |
if (!strcmp (filename, "/")) |
207 |
return "text/html"; |
208 |
|
209 |
p = strrchr (filename, '.'); |
210 |
if (p == NULL) |
211 |
return "text/plain"; |
212 |
p++; |
213 |
if (!strcasecmp (p, "html") || !strcasecmp (p, "htm")) |
214 |
return "text/html"; |
215 |
else if (!strcasecmp (p, "txt")) |
216 |
return "text/plain"; |
217 |
else if (!strcasecmp (p, "jpg") || !strcasecmp (p, "jpeg")) |
218 |
return "image/jpeg"; |
219 |
else if (!strcasecmp (p, "gif")) |
220 |
return "image/gif"; |
221 |
else if (!strcasecmp (p, "png")) |
222 |
return "image/png"; |
223 |
return "application/octet-stream"; |
224 |
} |
225 |
|
226 |
static void |
227 |
httpd_recvqhandler (connection_t *cptr) |
228 |
{ |
229 |
char buf[BUFSIZE * 2]; |
230 |
char outbuf[BUFSIZE * 2]; |
231 |
int count; |
232 |
httpddata *hd; |
233 |
char *p; |
234 |
int in; |
235 |
struct stat sb; |
236 |
off_t count1; |
237 |
path_handler_t ph; |
238 |
bool is_get, is_post, handling_done = false; |
239 |
pathvec::iterator found, pvend = pathhandlers.end (); |
240 |
|
241 |
hd = static_cast<httpddata *> (cptr->userdata); |
242 |
|
243 |
if ((found = (std::find_if (pathhandlers.begin (), pvend, pathhandler_eq (hd->filename)))) |
244 |
!= pvend) |
245 |
{ |
246 |
handling_done = true; |
247 |
ph = *found; |
248 |
} |
249 |
|
250 |
if (handling_done == true) |
251 |
{ |
252 |
if (hd->requestbuf != NULL) |
253 |
{ |
254 |
count = recvq_get (cptr, hd->requestbuf + hd->lengthdone, hd->length - hd->lengthdone); |
255 |
if (count <= 0) |
256 |
return; |
257 |
hd->lengthdone += count; |
258 |
if (hd->lengthdone != hd->length) |
259 |
return; |
260 |
hd->requestbuf[hd->length] = '\0'; |
261 |
|
262 |
ph.handler (cptr, hd->requestbuf); |
263 |
|
264 |
hd->clear (); |
265 |
return; |
266 |
} |
267 |
} |
268 |
|
269 |
count = recvq_getline (cptr, buf, sizeof buf - 1); |
270 |
if (count <= 0) |
271 |
return; |
272 |
if (cptr->flags & CF_NONEWLINE) |
273 |
{ |
274 |
slog (LG_INFO, "httpd_recvqhandler(): throwing out fd %d (%s) for excessive line length", cptr->fd, cptr->hbuf); |
275 |
send_error (cptr, 400, "Bad request", true); |
276 |
sendq_add_eof (cptr); |
277 |
return; |
278 |
} |
279 |
|
280 |
cnt.bin += count; |
281 |
if (buf[count - 1] == '\n') |
282 |
count--; |
283 |
if (count > 0 && buf[count - 1] == '\r') |
284 |
count--; |
285 |
buf[count] = '\0'; |
286 |
|
287 |
if (hd->method[0] == '\0') |
288 |
{ |
289 |
/* make sure they're not sending more requests after |
290 |
* declaring they're not sending any more */ |
291 |
if (hd->connection_close) |
292 |
return; |
293 |
p = strtok (buf, " "); |
294 |
if (p == NULL) |
295 |
return; |
296 |
strlcpy (hd->method, p, sizeof hd->method); |
297 |
p = strtok (NULL, " "); |
298 |
if (p == NULL) |
299 |
return; |
300 |
strlcpy (hd->filename, p, sizeof hd->filename); |
301 |
p = strtok (NULL, ""); |
302 |
if (p == NULL || !strcmp (p, "HTTP/1.0")) |
303 |
hd->connection_close = true; |
304 |
slog (LG_DEBUG, "httpd_recvqhandler(): request %s for %s", hd->method, hd->filename); |
305 |
} |
306 |
else if (count == 0) |
307 |
{ |
308 |
is_get = !strcmp (hd->method, "GET"); |
309 |
is_post = !strcmp (hd->method, "POST"); |
310 |
|
311 |
if (!is_post && !is_get) |
312 |
{ |
313 |
send_error (cptr, 501, "Method Not Implemented", true); |
314 |
sendq_add_eof (cptr); |
315 |
return; |
316 |
} |
317 |
|
318 |
hd->method[0] = '\0'; |
319 |
|
320 |
if (!handling_done) |
321 |
{ |
322 |
in = open_file (hd->filename); |
323 |
if (in == -1 || fstat (in, &sb) == -1 || !S_ISREG (sb.st_mode)) |
324 |
{ |
325 |
if (in != -1) |
326 |
close (in); |
327 |
slog (LG_INFO, "httpd_recvqhandler(): 404 for %s", hd->filename); |
328 |
send_error (cptr, 404, "Not Found", is_get); |
329 |
check_close (cptr); |
330 |
return; |
331 |
} |
332 |
slog (LG_INFO, "httpd_recvqhandler(): 200 for %s", hd->filename); |
333 |
snprintf (outbuf, sizeof outbuf, "HTTP/1.1 200 OK\r\n" |
334 |
"Server: " PACKAGE_NAME "/%s\r\n" |
335 |
"Content-Type: %s\r\n" |
336 |
"Content-Length: %lu\r\n" |
337 |
"\r\n", |
338 |
version, content_type (hd->filename), sb.st_size); |
339 |
sendq_add (cptr, outbuf, strlen (outbuf)); |
340 |
count1 = is_get ? sb.st_size : 0; |
341 |
while (count1 > 0) |
342 |
{ |
343 |
count = sizeof outbuf; |
344 |
if (count > count1) |
345 |
count = count1; |
346 |
count = read (in, outbuf, count); |
347 |
if (count <= 0) |
348 |
break; |
349 |
sendq_add (cptr, outbuf, count); |
350 |
count1 -= count; |
351 |
} |
352 |
close (in); |
353 |
if (count1 > 0) |
354 |
{ |
355 |
slog (LG_INFO, "httpd_recvqhandler(): disconnecting fd %d (%s), read failed on %s", cptr->fd, cptr->hbuf, hd->filename); |
356 |
cptr->flags |= CF_DEAD; |
357 |
} |
358 |
else |
359 |
check_close (cptr); |
360 |
} |
361 |
else |
362 |
{ |
363 |
if (hd->length <= 0) |
364 |
{ |
365 |
send_error (cptr, 411, "Length Required", true); |
366 |
sendq_add_eof (cptr); |
367 |
return; |
368 |
} |
369 |
if (hd->length > REQUEST_MAX) |
370 |
{ |
371 |
send_error (cptr, 413, "Request Entity Too Large", true); |
372 |
sendq_add_eof (cptr); |
373 |
return; |
374 |
} |
375 |
if (!hd->correct_content_type) |
376 |
{ |
377 |
send_error (cptr, 415, "Unsupported Media Type", true); |
378 |
sendq_add_eof (cptr); |
379 |
return; |
380 |
} |
381 |
if (hd->expect_100_continue) |
382 |
{ |
383 |
snprintf (outbuf, sizeof outbuf, "HTTP/1.1 100 Continue\r\n" |
384 |
"Server: " PACKAGE_NAME "/%s\r\n" |
385 |
"\r\n", version); |
386 |
sendq_add (cptr, outbuf, strlen (outbuf)); |
387 |
} |
388 |
hd->requestbuf = salloc<char> (hd->length + 1); |
389 |
} |
390 |
} |
391 |
else |
392 |
process_header (cptr, buf); |
393 |
} |
394 |
|
395 |
static void |
396 |
httpd_closehandler (connection_t *cptr) |
397 |
{ |
398 |
httpddata *hd; |
399 |
|
400 |
slog (LG_DEBUG, "httpd_closehandler(): fd %d (%s) closed", cptr->fd, cptr->hbuf); |
401 |
hd = static_cast<httpddata *> (cptr->userdata); |
402 |
if (hd != NULL) |
403 |
{ |
404 |
sfree (hd->requestbuf); |
405 |
delete hd; |
406 |
} |
407 |
cptr->userdata = NULL; |
408 |
} |
409 |
|
410 |
static void |
411 |
do_listen (connection_t *cptr) |
412 |
{ |
413 |
connection_t *newptr; |
414 |
httpddata *hd; |
415 |
|
416 |
newptr = connection_t::accept_tcp (cptr, recvq_put, sendq_flush); |
417 |
slog (LG_DEBUG, "do_listen(): accepted httpd from %s fd %d", newptr->hbuf, newptr->fd); |
418 |
hd = new httpddata; |
419 |
newptr->userdata = hd; |
420 |
newptr->recvq_handler = httpd_recvqhandler; |
421 |
newptr->close_handler = httpd_closehandler; |
422 |
} |
423 |
|
424 |
static void |
425 |
httpd_checkidle (void *arg) |
426 |
{ |
427 |
connection_t::list_type::iterator it, it_end = connection_t::list.end (); |
428 |
connection_t *cptr; |
429 |
|
430 |
if (listener == NULL) |
431 |
return; |
432 |
|
433 |
for (it = connection_t::list.begin (); it != it_end; ++it) |
434 |
{ |
435 |
cptr = *it; |
436 |
if (cptr->listener == listener && cptr->last_recv + 300 < NOW) |
437 |
{ |
438 |
if (sendq_nonempty (cptr)) |
439 |
cptr->last_recv = NOW; |
440 |
else |
441 |
/* from a timeout function, |
442 |
* connection_t::close_soon() may take quite |
443 |
* a while, and connection_t::close() is safe |
444 |
* -- jilles */ |
445 |
cptr->close (); |
446 |
} |
447 |
} |
448 |
} |
449 |
|
450 |
static void |
451 |
on_config_ready (void) |
452 |
{ |
453 |
if (httpd_config.host != NULL && httpd_config.port != 0) |
454 |
{ |
455 |
listener = connection_t::open_listener_tcp (httpd_config.host, httpd_config.port, do_listen); |
456 |
if (listener == NULL) |
457 |
slog (LG_ERROR, "httpd_config_ready(): failed to open listener on host %s port %d", httpd_config.host, httpd_config.port); |
458 |
} |
459 |
else |
460 |
slog (LG_ERROR, "httpd_config_ready(): httpd {} block missing or invalid"); |
461 |
httpd_config.clear (); |
462 |
} |
463 |
|
464 |
bool |
465 |
_modinit (module *m) |
466 |
{ |
467 |
event_add ("httpd_checkidle", httpd_checkidle, NULL, 60); |
468 |
|
469 |
/* This module needs a rehash to initialize fully if loaded |
470 |
* at run time */ |
471 |
ConfTable::callback.ready.attach (on_config_ready); |
472 |
|
473 |
add_top_conf ("HTTPD", conf_httpd); |
474 |
add_conf_item ("HOST", conf_httpd_table, conf_httpd_host); |
475 |
add_conf_item ("WWW_ROOT", conf_httpd_table, conf_httpd_www_root); |
476 |
add_conf_item ("PORT", conf_httpd_table, conf_httpd_port); |
477 |
|
478 |
return true; |
479 |
} |
480 |
|
481 |
void |
482 |
_moddeinit (void) |
483 |
{ |
484 |
event_delete (httpd_checkidle, NULL); |
485 |
listener->close_soon_children (); |
486 |
del_conf_item ("HOST", conf_httpd_table); |
487 |
del_conf_item ("WWW_ROOT", conf_httpd_table); |
488 |
del_conf_item ("PORT", conf_httpd_table); |
489 |
del_top_conf ("HTTPD"); |
490 |
httpd_config.clear (); |
491 |
|
492 |
ConfTable::callback.ready.detach (on_config_ready); |
493 |
} |