ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/rpc/httpd.C
Revision: 1.10
Committed: Sat Sep 22 14:27:28 2007 UTC (19 years ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +3 -2 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# User Rev Content
1 pippijn 1.9 /**
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 pippijn 1.3 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
10     * Copyright © 2005-2007 Jilles Tjoelker et al.
11 pippijn 1.2 * Rights to this code are as documented in doc/pod/license.pod.
12 pippijn 1.1 *
13 pippijn 1.10 * $Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $
14 pippijn 1.1 */
15    
16     #include "atheme.h"
17 pippijn 1.10 #include <libermyth.h>
18 pippijn 1.3 #include <ermyth/module.h>
19 pippijn 1.1 #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 pippijn 1.10 static char const rcsid[] = "$Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $";
27 pippijn 1.1
28 pippijn 1.3 static void on_config_ready (void);
29 pippijn 1.1
30 pippijn 1.3 REGISTER_MODULE ("rpc/httpd", false, "The Ermyth Team <http://ermyth.one09.net>");
31 pippijn 1.1
32 pippijn 1.3 static connection_t *listener;
33 pippijn 1.1 pathvec pathhandlers;
34    
35     /* conf stuff */
36 pippijn 1.8 ConfTable::list_type conf_httpd_table;
37    
38 pippijn 1.3 static struct httpd_configuration
39 pippijn 1.1 {
40     char *host;
41     char *www_root;
42     int port;
43 pippijn 1.3
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 pippijn 1.1 } httpd_config;
54    
55     static int
56     conf_httpd_host (config_entry_t *ce)
57     {
58 pippijn 1.4 if (!ce->valid ())
59 pippijn 1.1 return -1;
60    
61     if (httpd_config.host != NULL)
62 pippijn 1.3 sfree (httpd_config.host);
63 pippijn 1.4 httpd_config.host = sstrdup (ce->vardata<char *> ());
64 pippijn 1.1
65     return 0;
66     }
67    
68     static int
69     conf_httpd_www_root (config_entry_t *ce)
70     {
71 pippijn 1.4 if (!ce->valid ())
72 pippijn 1.1 return -1;
73    
74     if (httpd_config.www_root != NULL)
75 pippijn 1.3 sfree (httpd_config.www_root);
76 pippijn 1.4 httpd_config.www_root = sstrdup (ce->vardata<char *> ());
77 pippijn 1.1
78     return 0;
79     }
80    
81     static int
82     conf_httpd_port (config_entry_t *ce)
83     {
84 pippijn 1.4 if (!ce->valid ())
85 pippijn 1.1 return -1;
86    
87 pippijn 1.4 httpd_config.port = ce->vardata<int> ();
88 pippijn 1.1
89     return 0;
90     }
91    
92     static int
93     conf_httpd (config_entry_t *ce)
94     {
95 pippijn 1.8 subblock_handler (ce, conf_httpd_table);
96 pippijn 1.1 return 0;
97     }
98    
99 pippijn 1.3 void
100     httpddata::clear (void)
101 pippijn 1.1 {
102 pippijn 1.3 method[0] = '\0';
103     filename[0] = '\0';
104     if (requestbuf != NULL)
105 pippijn 1.1 {
106 pippijn 1.3 sfree (requestbuf);
107     requestbuf = NULL;
108 pippijn 1.1 }
109 pippijn 1.3 if (replybuf != NULL)
110 pippijn 1.1 {
111 pippijn 1.3 sfree (replybuf);
112     replybuf = NULL;
113 pippijn 1.1 }
114 pippijn 1.3 length = 0;
115     lengthdone = 0;
116     correct_content_type = false;
117     expect_100_continue = false;
118     sent_reply = false;
119 pippijn 1.1 }
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 pippijn 1.3 httpddata *hd;
138 pippijn 1.1 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 pippijn 1.3 hd->length = atoi (p);
163 pippijn 1.1 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 pippijn 1.3 hd->expect_100_continue = !strcasecmp (p, "100-continue");
170 pippijn 1.1 }
171    
172     static void
173     check_close (connection_t *cptr)
174     {
175 pippijn 1.3 httpddata *hd;
176 pippijn 1.1
177     hd = static_cast<httpddata *> (cptr->userdata);
178     if (hd->connection_close)
179     sendq_add_eof (cptr);
180     }
181    
182     static void
183 pippijn 1.3 send_error (connection_t *cptr, int errorcode, char const * const text, bool const sendentity)
184 pippijn 1.1 {
185 pippijn 1.3 httpddata *hd;
186 pippijn 1.1 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 pippijn 1.5 "Content-Length: %" PRIsize "\r\n\r\n%s",
197 pippijn 1.1 errorcode, text, version, strlen (buf2), sendentity ? buf2 : "");
198     sendq_add (cptr, buf1, strlen (buf1));
199     }
200    
201 pippijn 1.3 static char const * const
202     content_type (char const * const filename)
203 pippijn 1.1 {
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 pippijn 1.3 httpddata *hd;
233 pippijn 1.1 char *p;
234     int in;
235     struct stat sb;
236     off_t count1;
237 pippijn 1.3 path_handler_t ph;
238     bool is_get, is_post, handling_done = false;
239 pippijn 1.1 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 pippijn 1.3 ph.handler (cptr, hd->requestbuf);
263 pippijn 1.1
264 pippijn 1.3 hd->clear ();
265 pippijn 1.1 return;
266     }
267     }
268    
269     count = recvq_getline (cptr, buf, sizeof buf - 1);
270     if (count <= 0)
271     return;
272 pippijn 1.7 if (cptr->flags & CF_NONEWLINE)
273 pippijn 1.1 {
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 pippijn 1.7 cptr->flags |= CF_DEAD;
357 pippijn 1.1 }
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 pippijn 1.6 hd->requestbuf = salloc<char> (hd->length + 1);
389 pippijn 1.1 }
390     }
391     else
392     process_header (cptr, buf);
393     }
394    
395     static void
396     httpd_closehandler (connection_t *cptr)
397     {
398 pippijn 1.3 httpddata *hd;
399 pippijn 1.1
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 pippijn 1.3 sfree (hd->requestbuf);
405     delete hd;
406 pippijn 1.1 }
407     cptr->userdata = NULL;
408     }
409    
410     static void
411     do_listen (connection_t *cptr)
412     {
413     connection_t *newptr;
414 pippijn 1.3 httpddata *hd;
415 pippijn 1.1
416 pippijn 1.6 newptr = connection_t::accept_tcp (cptr, recvq_put, sendq_flush);
417 pippijn 1.1 slog (LG_DEBUG, "do_listen(): accepted httpd from %s fd %d", newptr->hbuf, newptr->fd);
418 pippijn 1.3 hd = new httpddata;
419 pippijn 1.1 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 pippijn 1.7 connection_t::list_type::iterator it, it_end = connection_t::list.end ();
428 pippijn 1.1 connection_t *cptr;
429    
430     if (listener == NULL)
431     return;
432 pippijn 1.3
433 pippijn 1.7 for (it = connection_t::list.begin (); it != it_end; ++it)
434 pippijn 1.3 {
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 pippijn 1.6 * connection_t::close_soon() may take quite
443     * a while, and connection_t::close() is safe
444 pippijn 1.3 * -- jilles */
445 pippijn 1.6 cptr->close ();
446 pippijn 1.3 }
447     }
448 pippijn 1.1 }
449    
450 pippijn 1.3 static void
451     on_config_ready (void)
452 pippijn 1.1 {
453     if (httpd_config.host != NULL && httpd_config.port != 0)
454     {
455 pippijn 1.6 listener = connection_t::open_listener_tcp (httpd_config.host, httpd_config.port, do_listen);
456 pippijn 1.1 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 pippijn 1.3 httpd_config.clear ();
462 pippijn 1.1 }
463    
464 pippijn 1.3 bool
465     _modinit (module *m)
466 pippijn 1.1 {
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 pippijn 1.3 ConfTable::callback.ready.attach (on_config_ready);
472 pippijn 1.1
473     add_top_conf ("HTTPD", conf_httpd);
474 pippijn 1.8 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 pippijn 1.3
478     return true;
479 pippijn 1.1 }
480    
481     void
482     _moddeinit (void)
483     {
484     event_delete (httpd_checkidle, NULL);
485 pippijn 1.6 listener->close_soon_children ();
486 pippijn 1.8 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 pippijn 1.1 del_top_conf ("HTTPD");
490 pippijn 1.3 httpd_config.clear ();
491 pippijn 1.1
492 pippijn 1.3 ConfTable::callback.ready.detach (on_config_ready);
493 pippijn 1.1 }