ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/httpd.C
Revision: 1.6
Committed: Thu Aug 30 19:56:24 2007 UTC (19 years, 1 month ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.5: +11 -11 lines
Log Message:
- put faultcodes into their own namespace
- removed old files
- limited header garbage in atheme.h
- macros to inline bools for connection_t::is_*
- put some connection_t functions into the connection_t class

File Contents

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