Tomato 1.28
[tomato.git] / release / src / router / httpd / httpd.c
blob034b74683b071544084d2e17002cc4b3713ecc8f
1 /*
3 micro_httpd/mini_httpd
5 Copyright © 1999,2000 by Jef Poskanzer <jef@acme.com>.
6 All rights reserved.
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11 1. Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 SUCH DAMAGE.
32 Copyright 2003, CyberTAN Inc. All Rights Reserved
34 This is UNPUBLISHED PROPRIETARY SOURCE CODE of CyberTAN Inc.
35 the contents of this file may not be disclosed to third parties,
36 copied or duplicated in any form without the prior written
37 permission of CyberTAN Inc.
39 This software should be used as a reference only, and it not
40 intended for production use!
42 THIS SOFTWARE IS OFFERED "AS IS", AND CYBERTAN GRANTS NO WARRANTIES OF ANY
43 KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. CYBERTAN
44 SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
45 FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE
50 Modified for Tomato Firmware
51 Portions, Copyright (C) 2006-2009 Jonathan Zarate
55 #include "tomato.h"
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <time.h>
59 #include <netinet/in.h>
60 #include <sys/types.h>
61 #include <sys/socket.h>
62 #include <arpa/inet.h>
63 #include <getopt.h>
64 #include <stdarg.h>
65 #include <sys/wait.h>
66 #include <error.h>
67 #include <sys/signal.h>
68 #include <netinet/tcp.h>
69 #include <sys/stat.h>
71 #include <wlutils.h>
74 #include "../mssl/mssl.h"
75 int do_ssl;
77 int post;
78 int listenfd;
79 int connfd = -1;
80 FILE *connfp = NULL;
81 struct sockaddr_in clientsai;
82 int header_sent;
83 char *user_agent;
84 // int hidok = 0;
86 const char mime_html[] = "text/html; charset=utf-8";
87 const char mime_plain[] = "text/plain";
88 const char mime_javascript[] = "text/javascript";
89 const char mime_binary[] = "application/tomato-binary-file"; // instead of "application/octet-stream" to make browser just "save as" and prevent automatic detection weirdness -- zzz
90 const char mime_octetstream[] = "application/octet-stream";
92 static int match(const char* pattern, const char* string);
93 static int match_one(const char* pattern, int patternlen, const char* string);
94 static void handle_request(void);
97 // --------------------------------------------------------------------------------------------------------------------
100 static const char *http_status_desc(int status)
102 switch (status) {
103 case 200:
104 return "OK";
105 case 302:
106 return "Found";
107 case 400:
108 return "Invalid Request";
109 case 401:
110 return "Unauthorized";
111 case 404:
112 return "Not Found";
113 case 501:
114 return "Not Implemented";
116 return "Unknown";
119 void send_header(int status, const char* header, const char* mime, int cache)
121 time_t now;
122 char tms[128];
124 now = time(NULL);
125 if (now < Y2K) now += Y2K;
126 strftime(tms, sizeof(tms), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); // RFC 1123
127 web_printf("HTTP/1.0 %d %s\r\n"
128 "Date: %s\r\n",
129 status, http_status_desc(status),
130 tms);
132 if (mime) web_printf("Content-Type: %s\r\n", mime);
133 if (cache > 0) {
134 web_printf("Cache-Control: max-age=%d\r\n", cache * 60);
136 else {
137 web_puts("Cache-Control: no-cache, no-store, must-revalidate, private\r\n"
138 "Expires: Thu, 31 Dec 1970 00:00:00 GMT\r\n"
139 "Pragma: no-cache\r\n");
141 if (header) web_printf("%s\r\n", header);
142 web_puts("Connection: close\r\n\r\n");
144 header_sent = 1;
147 void send_error(int status, const char *header, const char *text)
149 const char *s = http_status_desc(status);
150 send_header(status, header, mime_html, 0);
151 web_printf(
152 "<html>"
153 "<head><title>Error</title></head>"
154 "<body>"
155 "<h2>%d %s</h2> %s"
156 "</body></html>",
157 status, s, text ? text : s);
160 void redirect(const char *path)
162 char s[512];
164 snprintf(s, sizeof(s), "Location: %s", path);
165 send_header(302, s, mime_html, 0);
166 web_puts(s);
168 _dprintf("Redirect: %s\n", path);
171 int skip_header(int *len)
173 char buf[2048];
175 while (*len > 0) {
176 if (!web_getline(buf, MIN(*len, sizeof(buf)))) {
177 break;
179 *len -= strlen(buf);
180 if ((strcmp(buf, "\n") == 0) || (strcmp(buf, "\r\n") == 0)) {
181 return 1;
184 return 0;
188 // -----------------------------------------------------------------------------
190 static void eat_garbage(void)
192 int i;
193 int flags;
195 // eat garbage \r\n (IE6, ...) or browser ends up with a tcp reset error message
196 if ((!do_ssl) && (post)) {
197 if (((flags = fcntl(connfd, F_GETFL)) != -1) && (fcntl(connfd, F_SETFL, flags | O_NONBLOCK) != -1)) {
198 // if (fgetc(connfp) != EOF) fgetc(connfp);
199 for (i = 0; i < 1024; ++i) {
200 if (fgetc(connfp) == EOF) break;
202 fcntl(connfd, F_SETFL, flags);
207 // -----------------------------------------------------------------------------
210 static void send_authenticate(void)
212 char header[128];
213 const char *realm;
215 realm = nvram_get("router_name");
216 if ((realm == NULL) || (*realm == 0) || (strlen(realm) > 64)) realm = "unknown";
218 sprintf(header, "WWW-Authenticate: Basic realm=\"%s\"", realm);
219 send_error(401, header, NULL);
222 typedef enum { AUTH_NONE, AUTH_OK, AUTH_BAD } auth_t;
224 static auth_t auth_check(const char *authorization)
226 char buf[512];
227 const char *p;
228 char* pass;
229 int len;
231 if ((authorization != NULL) && (strncasecmp(authorization, "Basic ", 6) == 0)) {
232 if (base64_decoded_len(strlen(authorization + 6)) <= sizeof(buf)) {
233 len = base64_decode(authorization + 6, buf, strlen(authorization) - 6);
234 buf[len] = 0;
235 if ((pass = strchr(buf, ':')) != NULL) {
236 *pass++ = 0;
237 if ((strcmp(buf, "admin") == 0) || (strcmp(buf, "root") == 0)) {
238 p = nvram_get("http_passwd");
239 if (strcmp(pass, ((p == NULL) || (*p == 0)) ? "admin" : p) == 0) {
240 return AUTH_OK;
245 return AUTH_BAD;
248 return AUTH_NONE;
251 static void auth_fail(int clen)
253 if (post) web_eat(clen);
254 eat_garbage();
255 send_authenticate();
258 int check_wlaccess(void)
260 char mac[32];
261 char ifname[32];
262 sta_info_t sti;
264 if (nvram_match("web_wl_filter", "1")) {
265 if (get_client_info(mac, ifname)) {
266 memset(&sti, 0, sizeof(sti));
267 strcpy((char *)&sti, "sta_info"); // sta_info0<mac>
268 ether_atoe(mac, (char *)&sti + 9);
269 if (wl_ioctl(nvram_safe_get("wl_ifname"), WLC_GET_VAR, &sti, sizeof(sti)) == 0) {
270 if (nvram_match("debug_logwlac", "1")) {
271 syslog(LOG_WARNING, "Wireless access from %s blocked.", mac);
273 return 0;
277 return 1;
280 // -----------------------------------------------------------------------------
282 static int match_one(const char* pattern, int patternlen, const char* string)
284 const char* p;
286 for (p = pattern; p - pattern < patternlen; ++p, ++string) {
287 if (*p == '?' && *string != '\0')
288 continue;
289 if (*p == '*') {
290 int i, pl;
291 ++p;
292 if (*p == '*') {
293 /* Double-wildcard matches anything. */
294 ++p;
295 i = strlen(string);
296 } else
297 /* Single-wildcard matches anything but slash. */
298 i = strcspn(string, "/");
299 pl = patternlen - (p - pattern);
300 for (; i >= 0; --i)
301 if (match_one(p, pl, &(string[i])))
302 return 1;
303 return 0;
305 if (*p != *string)
306 return 0;
308 if (*string == '\0')
309 return 1;
310 return 0;
313 // Simple shell-style filename matcher. Only does ? * and **, and multiple
314 // patterns separated by |. Returns 1 or 0.
315 static int match(const char* pattern, const char* string)
317 const char* p;
319 for (;;) {
320 p = strchr(pattern, '|');
321 if (p == NULL) return match_one(pattern, strlen(pattern), string);
322 if (match_one(pattern, p - pattern, string)) return 1;
323 pattern = p + 1;
328 void do_file(char *path)
330 FILE *f;
331 char buf[1024];
332 int nr;
333 if ((f = fopen(path, "r")) != NULL) {
334 while ((nr = fread(buf, 1, sizeof(buf), f)) > 0)
335 web_write(buf, nr);
336 fclose(f);
340 static void handle_request(void)
342 char line[10000], *cur;
343 char *method, *path, *protocol, *authorization, *boundary;
344 char *cp;
345 char *file;
346 const struct mime_handler *handler;
347 int cl = 0;
348 auth_t auth;
350 user_agent = "";
351 header_sent = 0;
352 authorization = boundary = NULL;
353 bzero(line, sizeof(line));
355 // Parse the first line of the request.
356 if (!web_getline(line, sizeof(line))) {
357 send_error(400, NULL, NULL);
358 return;
361 _dprintf("%s\n", line);
363 method = path = line;
364 strsep(&path, " ");
365 if (!path) { // Avoid http server crash, added by honor 2003-12-08
366 send_error(400, NULL, NULL);
367 return;
369 while (*path == ' ') path++;
371 if ((strcasecmp(method, "GET") != 0) && (strcasecmp(method, "POST") != 0)) {
372 send_error(501, NULL, NULL);
373 return;
376 protocol = path;
377 strsep(&protocol, " ");
378 if (!protocol) { // Avoid http server crash, added by honor 2003-12-08
379 send_error(400, NULL, NULL);
380 return;
382 while (*protocol == ' ') protocol++;
384 if (path[0] != '/') {
385 send_error(400, NULL, NULL);
386 return;
388 file = path + 1;
390 #if 0
391 const char *hid;
392 int n;
394 hid = nvram_safe_get("http_id");
395 n = strlen(hid);
396 cprintf("file=%s hid=%s n=%d +n=%s\n", file, hid, n, path + n + 1);
397 if ((strncmp(file, hid, n) == 0) && (path[n + 1] == '/')) {
398 path += n + 1;
399 file = path + 1;
400 hidok = 1;
402 cprintf("OK path=%s file=%s\n", path, file);
404 #endif
406 if ((cp = strchr(file, '?')) != NULL) {
407 *cp = 0;
408 webcgi_init(cp + 1);
411 if ((file[0] == '/') || (strncmp(file, "..", 2) == 0) || (strstr(file, "/../") != NULL) ||
412 (strcmp(file + (strlen(file) - 3), "/..") == 0)) {
413 send_error(400, NULL, NULL);
414 return;
417 if ((file[0] == 0) || (strcmp(file, "index.asp") == 0)) {
418 file = "status-overview.asp";
420 else if ((strcmp(file, "ext/") == 0) || (strcmp(file, "ext") == 0)) {
421 file = "ext/index.asp";
424 cp = protocol;
425 strsep(&cp, " ");
426 cur = protocol + strlen(protocol) + 1;
428 while (web_getline(cur, line + sizeof(line) - cur)) {
429 if ((strcmp(cur, "\n") == 0) || (strcmp(cur, "\r\n") == 0)) {
430 break;
433 if (strncasecmp(cur, "Authorization:", 14) == 0) {
434 cp = &cur[14];
435 cp += strspn(cp, " \t");
436 authorization = cp;
437 cur = cp + strlen(cp) + 1;
439 else if (strncasecmp(cur, "Content-Length:", 15) == 0) {
440 cp = &cur[15];
441 cp += strspn(cp, " \t");
442 cl = strtoul(cp, NULL, 0);
443 if ((cl < 0) || (cl >= INT_MAX)) {
444 send_error(400, NULL, NULL);
445 return;
448 else if ((strncasecmp(cur, "Content-Type:", 13) == 0) && ((cp = strstr(cur, "boundary=")))) {
449 boundary = &cp[9];
450 for (cp = cp + 9; *cp && *cp != '\r' && *cp != '\n'; cp++);
451 *cp = '\0';
452 cur = ++cp;
454 else if (strncasecmp(cur, "User-Agent:", 11) == 0) {
455 user_agent = cur + 11;
456 user_agent += strspn(user_agent, " \t");
457 cur = user_agent + strlen(user_agent);
458 *cur++ = 0;
462 post = (strcasecmp(method, "post") == 0);
464 auth = auth_check(authorization);
466 #if 0
467 cprintf("UserAgent: %s\n", user_agent);
468 switch (auth) {
469 case AUTH_NONE:
470 cprintf("AUTH_NONE\n");
471 break;
472 case AUTH_OK:
473 cprintf("AUTH_OK\n");
474 break;
475 case AUTH_BAD:
476 cprintf("AUTH_BAD\n");
477 break;
478 default:
479 cprintf("AUTH_?\n");
480 break;
482 #endif
486 Chrome 1.0.154:
487 - User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.53 Safari/525.19
488 - It *always* sends an 'AUTH_NONE request' first. This results in a
489 send_authenticate, then finally sends an 'AUTH_OK request'.
490 - It does not clear the authentication even if AUTH_NONE request succeeds.
491 - If user doesn't enter anything (blank) for credential, it sends the
492 previous credential.
496 if (strcmp(file, "logout") == 0) { // special case
497 wi_generic(file, cl, boundary);
498 eat_garbage();
500 if (strstr(user_agent, "Chrome/") != NULL) {
501 if (auth != AUTH_BAD) {
502 send_authenticate();
503 return;
506 else {
507 if (auth == AUTH_OK) {
508 send_authenticate();
509 return;
512 send_error(404, NULL, "Goodbye");
513 return;
516 if (auth == AUTH_BAD) {
517 auth_fail(cl);
518 return;
521 for (handler = &mime_handlers[0]; handler->pattern; handler++) {
522 if (match(handler->pattern, file)) {
523 if ((handler->auth) && (auth != AUTH_OK)) {
524 auth_fail(cl);
525 return;
528 if (handler->input) handler->input(file, cl, boundary);
529 eat_garbage();
530 if (handler->mime_type != NULL) send_header(200, NULL, handler->mime_type, handler->cache);
531 if (handler->output) handler->output(file);
532 return;
536 if (auth != AUTH_OK) {
537 auth_fail(cl);
538 return;
542 if ((!post) && (strchr(file, '.') == NULL)) {
543 cl = strlen(file);
544 if ((cl > 1) && (cl < 64)) {
545 char alt[128];
547 path = alt + 1;
548 strcpy(path, file);
550 cp = path + cl - 1;
551 if (*cp == '/') *cp = 0;
553 if ((cp = strrchr(path, '/')) != NULL) *cp = '-';
555 strcat(path, ".asp");
556 if (f_exists(path)) {
557 alt[0] = '/';
558 redirect(alt);
559 return;
565 send_error(404, NULL, NULL);
568 #ifdef TCONFIG_HTTPS
569 static void save_cert(void)
571 if (eval("tar", "-C", "/", "-czf", "/tmp/cert.tgz", "etc/cert.pem", "etc/key.pem") == 0) {
572 if (nvram_set_file("https_crt_file", "/tmp/cert.tgz", 8192)) {
573 nvram_commit_x();
576 unlink("/tmp/cert.tgz");
579 static void erase_cert(void)
581 unlink("/etc/cert.pem");
582 unlink("/etc/key.pem");
583 nvram_unset("https_crt_file");
584 nvram_unset("https_crt_gen");
587 static void start_ssl(void)
589 int ok;
590 int save;
591 int retry;
592 unsigned long long sn;
593 char t[32];
595 if (nvram_match("https_crt_gen", "1")) {
596 erase_cert();
599 retry = 1;
600 while (1) {
601 save = nvram_match("https_crt_save", "1");
603 if ((!f_exists("/etc/cert.pem")) || (!f_exists("/etc/key.pem"))) {
604 ok = 0;
605 if (save) {
606 if (nvram_get_file("https_crt_file", "/tmp/cert.tgz", 8192)) {
607 if (eval("tar", "-xzf", "/tmp/cert.tgz", "-C", "/", "etc/cert.pem", "etc/key.pem") == 0)
608 ok = 1;
609 unlink("/tmp/cert.tgz");
612 if (!ok) {
613 erase_cert();
614 syslog(LOG_INFO, "Generating SSL certificate...");
616 // browsers seems to like this when the ip address moves... -- zzz
617 f_read("/dev/urandom", &sn, sizeof(sn));
619 sprintf(t, "%llu", sn & 0x7FFFFFFFFFFFFFFFUL);
620 eval("gencert.sh", t);
624 if ((save) && (*nvram_safe_get("https_crt_file")) == 0) {
625 save_cert();
628 if (ssl_init("/etc/cert.pem", "/etc/key.pem")) return;
630 erase_cert();
632 syslog(retry ? LOG_WARNING : LOG_ERR, "Unable to start SSL");
633 if (!retry) exit(1);
634 retry = 0;
637 #endif
639 static void init_id(void)
641 char s[128];
642 unsigned long long n;
644 if (strncmp(nvram_safe_get("http_id"), "TID", 3) != 0) {
645 f_read("/dev/urandom", &n, sizeof(n));
646 sprintf(s, "TID%llx", n);
647 nvram_set("http_id", s);
650 nvram_unset("http_id_warn");
653 void check_id(const char *url)
655 if (!nvram_match("http_id", webcgi_safeget("_http_id", ""))) {
656 #if 0
657 const char *hid = nvram_safe_get("http_id");
658 if (url) {
659 const char *a;
660 int n;
662 // http://xxx/yyy/TID/zzz
663 if (((a = strrchr(url, '/')) != NULL) && (a != url)) {
664 n = strlen(hid);
665 a -= n;
666 if ((a > url) && (*(a - 1) == '/')) {
667 if (strncmp(a, hid, n) == 0) return;
672 #endif
674 char s[48];
675 char *i;
676 char *u;
677 const char *a;
679 a = inet_ntoa(clientsai.sin_addr);
680 sprintf(s, "%s,%ld", a, time(NULL) & 0xFFC0);
681 if (!nvram_match("http_id_warn", s)) {
682 nvram_set("http_id_warn", s);
684 strlcpy(s, webcgi_safeget("_http_id", ""), sizeof(s));
685 i = js_string(s); // quicky scrub
687 strlcpy(s, url, sizeof(s));
688 u = js_string(s);
690 if ((i != NULL) && (u != NULL)) {
691 syslog(LOG_WARNING, "Invalid ID '%s' from %s for /%s", i, a, u);
694 // free(u);
695 // free(i);
698 exit(1);
702 int main(int argc, char **argv)
704 int c;
705 int debug = 0;
706 int server_port = 0;
708 while ((c = getopt(argc, argv, "hdp:s")) != -1) {
709 switch (c) {
710 case 'h':
711 printf(
712 "Usage: %s [options]\n"
713 " -d Debug mode / do not demonize\n"
714 " -p <port> Port to listen on\n"
715 #ifdef TCONFIG_HTTPS
716 " -s Use HTTPS\n"
717 #endif
718 , argv[0]);
719 return 1;
720 case 'd':
721 debug = 1;
722 break;
723 case 'p':
724 server_port = atoi(optarg);
725 break;
726 #ifdef TCONFIG_HTTPS
727 case 's':
728 do_ssl = 1;
729 break;
730 #endif
734 if (server_port == 0) {
735 #ifdef TCONFIG_HTTPS
736 if (do_ssl) {
737 server_port = nvram_get_int("https_lanport");
738 if (server_port <= 0) server_port = 443;
740 else {
741 server_port = nvram_get_int("http_lanport");
742 if (server_port <= 0) server_port = 80;
744 #else
745 server_port = nvram_get_int("http_lanport");
746 if (server_port <= 0) server_port = 80;
747 #endif
750 openlog("httpd", LOG_PID, LOG_DAEMON);
752 if (!debug) {
753 if (daemon(1, 1) == -1) {
754 syslog(LOG_ERR, "daemon: %m");
755 return 0;
759 char s[16];
760 sprintf(s, "%d", getpid());
761 f_write_string(do_ssl ? "/var/run/httpsd.pid" : "/var/run/httpd.pid", s, 0, 0644);
763 else {
764 printf("DEBUG mode, not daemonizing\n");
767 signal(SIGPIPE, SIG_IGN);
768 signal(SIGALRM, SIG_IGN);
769 signal(SIGHUP, SIG_IGN);
770 signal(SIGCHLD, SIG_IGN);
772 #ifdef TCONFIG_HTTPS
773 if (do_ssl) start_ssl();
774 #endif
776 struct timeval tv;
777 int n;
779 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
780 syslog(LOG_ERR, "create listening socket: %m");
781 return 1;
783 fcntl(listenfd, F_SETFD, FD_CLOEXEC);
784 n = 1;
785 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof(n));
787 struct sockaddr_in sai;
788 sai.sin_family = AF_INET;
789 sai.sin_port = htons(server_port);
790 sai.sin_addr.s_addr = INADDR_ANY;
791 if (bind(listenfd, (struct sockaddr *)&sai, sizeof(sai)) < 0) {
792 syslog(LOG_ERR, "bind: %m");
793 return 1;
796 if (listen(listenfd, 64) < 0) {
797 syslog(LOG_ERR, "listen: %m");
798 return 1;
801 init_id();
803 for (;;) {
804 webcgi_init(NULL);
805 if (connfd >= 0) close(connfd);
807 n = sizeof(clientsai);
808 if ((connfd = accept(listenfd, (struct sockaddr *)&clientsai, &n)) < 0) {
809 // if ((errno != EINTR) && (errno != EAGAIN)) {
810 // syslog(LOG_ERR, "accept: %m");
811 // return 1;
812 // }
813 sleep(1);
814 continue;
817 if (!wait_action_idle(10)) {
818 // syslog(LOG_WARNING, "router is busy");
819 continue;
822 if (!check_wlaccess()) {
823 continue;
826 if (fork() == 0) {
827 close(listenfd);
829 tv.tv_sec = 60;
830 tv.tv_usec = 0;
831 setsockopt(connfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
832 setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
834 n = 1;
835 setsockopt(connfd, IPPROTO_TCP, TCP_NODELAY, (char *)&n, sizeof(n));
837 fcntl(connfd, F_SETFD, FD_CLOEXEC);
839 if (web_open()) handle_request();
840 web_close();
841 exit(0);
845 close(listenfd);
846 return 0;