5 Copyright © 1999,2000 by Jef Poskanzer <jef@acme.com>.
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
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
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
59 #include <netinet/in.h>
60 #include <sys/types.h>
61 #include <sys/socket.h>
62 #include <arpa/inet.h>
67 #include <sys/signal.h>
68 #include <netinet/tcp.h>
74 #include "../mssl/mssl.h"
81 struct sockaddr_in clientsai
;
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
)
108 return "Invalid Request";
110 return "Unauthorized";
114 return "Not Implemented";
119 void send_header(int status
, const char* header
, const char* mime
, int cache
)
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"
129 status
, http_status_desc(status
),
132 if (mime
) web_printf("Content-Type: %s\r\n", mime
);
134 web_printf("Cache-Control: max-age=%d\r\n", cache
* 60);
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");
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);
153 "<head><title>Error</title></head>"
157 status
, s
, text
? text
: s
);
160 void redirect(const char *path
)
164 snprintf(s
, sizeof(s
), "Location: %s", path
);
165 send_header(302, s
, mime_html
, 0);
168 _dprintf("Redirect: %s\n", path
);
171 int skip_header(int *len
)
176 if (!web_getline(buf
, MIN(*len
, sizeof(buf
)))) {
180 if ((strcmp(buf
, "\n") == 0) || (strcmp(buf
, "\r\n") == 0)) {
188 // -----------------------------------------------------------------------------
190 static void eat_garbage(void)
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)
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
)
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);
235 if ((pass
= strchr(buf
, ':')) != NULL
) {
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) {
251 static void auth_fail(int clen
)
253 if (post
) web_eat(clen
);
258 int check_wlaccess(void)
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
);
280 // -----------------------------------------------------------------------------
282 static int match_one(const char* pattern
, int patternlen
, const char* string
)
286 for (p
= pattern
; p
- pattern
< patternlen
; ++p
, ++string
) {
287 if (*p
== '?' && *string
!= '\0')
293 /* Double-wildcard matches anything. */
297 /* Single-wildcard matches anything but slash. */
298 i
= strcspn(string
, "/");
299 pl
= patternlen
- (p
- pattern
);
301 if (match_one(p
, pl
, &(string
[i
])))
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
)
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;
328 void do_file(char *path
)
333 if ((f
= fopen(path
, "r")) != NULL
) {
334 while ((nr
= fread(buf
, 1, sizeof(buf
), f
)) > 0)
340 static void handle_request(void)
342 char line
[10000], *cur
;
343 char *method
, *path
, *protocol
, *authorization
, *boundary
;
346 const struct mime_handler
*handler
;
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
);
361 _dprintf("%s\n", line
);
363 method
= path
= line
;
365 if (!path
) { // Avoid http server crash, added by honor 2003-12-08
366 send_error(400, NULL
, NULL
);
369 while (*path
== ' ') path
++;
371 if ((strcasecmp(method
, "GET") != 0) && (strcasecmp(method
, "POST") != 0)) {
372 send_error(501, NULL
, NULL
);
377 strsep(&protocol
, " ");
378 if (!protocol
) { // Avoid http server crash, added by honor 2003-12-08
379 send_error(400, NULL
, NULL
);
382 while (*protocol
== ' ') protocol
++;
384 if (path
[0] != '/') {
385 send_error(400, NULL
, NULL
);
394 hid
= nvram_safe_get("http_id");
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] == '/')) {
402 cprintf("OK path=%s file=%s\n", path
, file
);
406 if ((cp
= strchr(file
, '?')) != NULL
) {
411 if ((file
[0] == '/') || (strncmp(file
, "..", 2) == 0) || (strstr(file
, "/../") != NULL
) ||
412 (strcmp(file
+ (strlen(file
) - 3), "/..") == 0)) {
413 send_error(400, NULL
, NULL
);
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";
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)) {
433 if (strncasecmp(cur
, "Authorization:", 14) == 0) {
435 cp
+= strspn(cp
, " \t");
437 cur
= cp
+ strlen(cp
) + 1;
439 else if (strncasecmp(cur
, "Content-Length:", 15) == 0) {
441 cp
+= strspn(cp
, " \t");
442 cl
= strtoul(cp
, NULL
, 0);
443 if ((cl
< 0) || (cl
>= INT_MAX
)) {
444 send_error(400, NULL
, NULL
);
448 else if ((strncasecmp(cur
, "Content-Type:", 13) == 0) && ((cp
= strstr(cur
, "boundary=")))) {
450 for (cp
= cp
+ 9; *cp
&& *cp
!= '\r' && *cp
!= '\n'; 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
);
462 post
= (strcasecmp(method
, "post") == 0);
464 auth
= auth_check(authorization
);
467 cprintf("UserAgent: %s\n", user_agent
);
470 cprintf("AUTH_NONE\n");
473 cprintf("AUTH_OK\n");
476 cprintf("AUTH_BAD\n");
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
496 if (strcmp(file
, "logout") == 0) { // special case
497 wi_generic(file
, cl
, boundary
);
500 if (strstr(user_agent
, "Chrome/") != NULL
) {
501 if (auth
!= AUTH_BAD
) {
507 if (auth
== AUTH_OK
) {
512 send_error(404, NULL
, "Goodbye");
516 if (auth
== AUTH_BAD
) {
521 for (handler
= &mime_handlers
[0]; handler
->pattern
; handler
++) {
522 if (match(handler
->pattern
, file
)) {
523 if ((handler
->auth
) && (auth
!= AUTH_OK
)) {
528 if (handler
->input
) handler
->input(file
, cl
, boundary
);
530 if (handler
->mime_type
!= NULL
) send_header(200, NULL
, handler
->mime_type
, handler
->cache
);
531 if (handler
->output
) handler
->output(file
);
536 if (auth
!= AUTH_OK
) {
542 if ((!post) && (strchr(file, '.') == NULL)) {
544 if ((cl > 1) && (cl < 64)) {
551 if (*cp == '/') *cp = 0;
553 if ((cp = strrchr(path, '/')) != NULL) *cp = '-';
555 strcat(path, ".asp");
556 if (f_exists(path)) {
565 send_error(404, NULL
, NULL
);
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)) {
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)
592 unsigned long long sn
;
595 if (nvram_match("https_crt_gen", "1")) {
601 save
= nvram_match("https_crt_save", "1");
603 if ((!f_exists("/etc/cert.pem")) || (!f_exists("/etc/key.pem"))) {
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)
609 unlink("/tmp/cert.tgz");
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) {
628 if (ssl_init("/etc/cert.pem", "/etc/key.pem")) return;
632 syslog(retry
? LOG_WARNING
: LOG_ERR
, "Unable to start SSL");
639 static void init_id(void)
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", ""))) {
657 const char *hid
= nvram_safe_get("http_id");
662 // http://xxx/yyy/TID/zzz
663 if (((a
= strrchr(url
, '/')) != NULL
) && (a
!= url
)) {
666 if ((a
> url
) && (*(a
- 1) == '/')) {
667 if (strncmp(a
, hid
, n
) == 0) return;
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
));
690 if ((i
!= NULL
) && (u
!= NULL
)) {
691 syslog(LOG_WARNING
, "Invalid ID '%s' from %s for /%s", i
, a
, u
);
702 int main(int argc
, char **argv
)
708 while ((c
= getopt(argc
, argv
, "hdp:s")) != -1) {
712 "Usage: %s [options]\n"
713 " -d Debug mode / do not demonize\n"
714 " -p <port> Port to listen on\n"
724 server_port
= atoi(optarg
);
734 if (server_port
== 0) {
737 server_port
= nvram_get_int("https_lanport");
738 if (server_port
<= 0) server_port
= 443;
741 server_port
= nvram_get_int("http_lanport");
742 if (server_port
<= 0) server_port
= 80;
745 server_port
= nvram_get_int("http_lanport");
746 if (server_port
<= 0) server_port
= 80;
750 openlog("httpd", LOG_PID
, LOG_DAEMON
);
753 if (daemon(1, 1) == -1) {
754 syslog(LOG_ERR
, "daemon: %m");
760 sprintf(s
, "%d", getpid());
761 f_write_string(do_ssl
? "/var/run/httpsd.pid" : "/var/run/httpd.pid", s
, 0, 0644);
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
);
773 if (do_ssl
) start_ssl();
779 if ((listenfd
= socket(AF_INET
, SOCK_STREAM
, 0)) < 0) {
780 syslog(LOG_ERR
, "create listening socket: %m");
783 fcntl(listenfd
, F_SETFD
, FD_CLOEXEC
);
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");
796 if (listen(listenfd
, 64) < 0) {
797 syslog(LOG_ERR
, "listen: %m");
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");
817 if (!wait_action_idle(10)) {
818 // syslog(LOG_WARNING, "router is busy");
822 if (!check_wlaccess()) {
831 setsockopt(connfd
, SOL_SOCKET
, SO_SNDTIMEO
, &tv
, sizeof(tv
));
832 setsockopt(connfd
, SOL_SOCKET
, SO_RCVTIMEO
, &tv
, sizeof(tv
));
835 setsockopt(connfd
, IPPROTO_TCP
, TCP_NODELAY
, (char *)&n
, sizeof(n
));
837 fcntl(connfd
, F_SETFD
, FD_CLOEXEC
);
839 if (web_open()) handle_request();