/* Package Name : twhttpd * File Name : twhttpd.c * Author : Sam NG * * This package is an secure HTTP application proxy writen by Sam Ng. * Copyright (C) 2001 SAM NG * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base64.h" #include "md5.h" #include "cfg_functions.h" #include "twhttpd.h" #include "httplog.h" #include "config.h" /* config parser variables */ extern void yyparse(); extern FILE *yyin; /* global variables */ gen_cfg cfg; srv_cfg tmp_sc; ////////////////////////////////////////////////////////////////////// // System related sub-routine ////////////////////////////////////////////////////////////////////// /* * daemonize * 1. make the parent exit * 2. create new session, setsid * 3. change working dir * 4. set umask * 5. close any stdin, stdout, stderr */ void daemonize(void) { pid_t rc; struct hostent *dummy_addr; char *dummy_passwd; if ( ( rc = fork() ) < 0 ) { syslog(LOG_ERR, "fork(): %s\n", strerror(errno)); exit(-1); } // drop parent if ( rc > 0 ) exit(0); if ( setsid() < 0 ) exit(-1); // do it twice if ( ( rc = fork() ) < 0 ) { syslog(LOG_ERR, "fork(): %s\n", strerror(errno)); exit(-1); } // drop parent if ( rc > 0 ) exit(0); umask(0); if ( NULL != cfg.work_dir ) { if ( chdir(cfg.work_dir) ) { syslog(LOG_ERR, "chdir(): %s\n", strerror(errno)); exit(-1); } } if ( cfg.enable_chroot ) { /* before really goes to chroot jail */ /* I have to load all the share lib in RAM */ /* this can be smiply be done by calling those functions */ /* here, result is not important */ dummy_addr = gethostbyname("www.yahoo.com"); dummy_passwd = (char *)crypt("password", "0a"); if ( chroot("./") ) { syslog(LOG_ERR, "chroot(): %s\n", strerror(errno)); exit(-1); } /* change dir to "/" immediately after chroot */ if ( chdir("/") ) { syslog(LOG_ERR, "chdir(): %s\n", strerror(errno)); exit(-1); } } // uid = 0 means root, will not setuid to root // have to setgid first if ( cfg.gid != (gid_t)0 ) { if ( setgid(cfg.gid) ) { syslog(LOG_ERR, "setgid(): %s\n", strerror(errno)); exit(-1); } } if ( cfg.uid != (uid_t)0 ) { if ( setuid(cfg.uid) ) { syslog(LOG_ERR, "setuid(): %s\n", strerror(errno)); exit(-1); } } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); } /* wait any defunct child process */ /* ref to Beginning Linux Programming, RickHard Stones & Neil Matthew, Ch 10 */ void handle_sigchld(int sig) { pid_t pid; /* loop until all zombie process are die */ while (1) { /* note opions is WNOHANG */ pid = waitpid(-1, NULL, WNOHANG ); if ( 0 == pid ) break; if ( pid < 0 ) break; // does not handle this error } return ; } /* just try to catch the write on RSTed socket */ void handle_sigpipe(int sig) { return ; } ////////////////////////////////////////////////////////////////////// // http services ////////////////////////////////////////////////////////////////////// void init_rab(rabuf *rab) { memset(rab->a, 0, MAX_RAB); rab->p = rab->a; rab->n = 0; } void init_lzb(lzbuf *lzb) { memset(lzb->a, 0, MAX_LZB); lzb->n = 0; } void init_http_header(http_header *hd) { /* config */ hd->sc = NULL; hd->forward = NULL; hd->forward_proxy = NULL; hd->return_code = -1; hd->wrap_301 = 0; /* addresses */ memset( &hd->srv_addr, 0, sizeof(struct sockaddr_in) ); memset( &hd->clt_addr, 0, sizeof(struct sockaddr_in) ); memset( &hd->remote_addr, 0, sizeof(struct sockaddr_in) ); /* sockets elements */ hd->fd = -1; init_rab(&hd->fd_rab); init_lzb(&hd->fd_lzb); hd->proxy = -1; init_rab(&hd->proxy_rab); init_lzb(&hd->proxy_lzb); /* must have these http headers */ hd->method = NULL; hd->version = NULL; memset( hd->host, '\0', sizeof(hd->host) ); hd->host_ent = NULL; hd->port = -1; memset( hd->opath, '\0', sizeof(hd->opath) ); memset( hd->path, '\0', sizeof(hd->path) ); hd->ext = NULL; memset( hd->abs_url, '\0', sizeof(hd->abs_url) ); memset( hd->hash, '\0', sizeof(hd->hash) ); hd->hashptr = NULL; memset( hd->location, '\0', sizeof(hd->hash) ); /* internal flags */ hd->local = -1; hd->cgi = -1; hd->no_cache = 0; hd->post_len = -1; hd->dir = -1; hd->byte_count = 0; /* option header */ hd->query = NULL; hd->sessionid = NULL; hd->cookie = NULL; hd->referer = NULL; hd->user_agent = NULL; hd->content_type = NULL; hd->accept = NULL; hd->accept_charset = NULL; hd->accept_encoding = NULL; hd->accept_language = NULL; hd->range = NULL; hd->if_mod_since = (time_t)-1; /* authorization */ hd->auth = NULL; memset( &hd->auth_username, '\0', sizeof(hd->auth_username) ); hd->auth_passwd = NULL; hd->proxy_auth = NULL; memset( &hd->pauth_username, '\0', sizeof(hd->pauth_username) ); hd->pauth_passwd = NULL; } void clean_http_header(http_header *hd) { int s; if ( 0 <= hd->fd ) { s = slow_close(hd->fd); } if ( 0 <= hd->proxy ) { s = slow_close(hd->proxy); } } void http_srv(http_header *hd) { /* general variables */ char *line; char *field_name, *field_value; int len=0, total_len=0, http_eof=0, empty=0, line_no=0; int errcode; // we are sure the socket is non-blocking // if ( set_nonblock(hd->fd) ) exit(-1); /* read line from file descriptor */ do { /* error if can't read line or line > max line */ line = read_line(hd->fd, &hd->fd_rab); if ( NULL == line ) { errcode = BAD_REQUEST; goto end; } else { len = strlen(line); } // header dump // syslog(LOG_DEBUG, "client >> %s\n", line); /* empty line means eof */ if ( 0 == len ) http_eof = 1 ; /* OK, we can take a look at what we have */ /* first line have to be request url */ else if ( !line_no ) { line_no++; errcode = parse_request(hd, line); if ( errcode ) goto end; } /* second line onwards */ else if ( line_no < MAX_HEADER_FIELDS ) { line_no++; /* all header field in the format of "name: value" */ /* if condition is not match, simply neglect this line */ field_name = line; /* all headers have to be in ascii only if clt_hd_chk is true */ if ( (hd->sc)->clt_hd_chk && non_ascii(line) ) { errcode = BAD_REQUEST; goto end; } if ( NULL != (field_value = strchr(field_name, ':')) ) { *field_value = '\0'; field_value++; if ( ' ' == *field_value ) field_value++; /* HOST: www.domain.com:80 */ if ( !strcasecmp(field_name, "host") ) { // cannot longer than MAX_LINE since hd->host buffer lenght limit // MAX_HOST is just a configuration limit if ( (strlen(field_value) >= MAX_LINE) || (((hd->sc)->clt_hd_chk) && (strlen(field_value) >= MAX_HOST)) ) { errcode = BAD_REQUEST; goto end; } if ( sscanf(field_value , "%[^:]:%5d", hd->host, &hd->port ) == 2 ) { ; } else if ( sscanf(field_value, "%[^:]", hd->host ) == 1 ) { ; // hd->port = 80; // no default value in here // because parse_request should have already set a default value // consider the case for HTTPS, // if the client send the HOST again w/o port, this won't fail } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); errcode = BAD_REQUEST; goto end; } } /* handle pragma: no cache */ else if ( !strcasecmp(field_name, "pragma") ) { if ( !strcasecmp(field_value, "no-cache") ) { // probably client push "refresh" button hd->no_cache=1; } } /* post form content length */ /* max = 1G byte */ else if ( !strcasecmp(field_name, "content-length") ) { if ( sscanf(field_value, "%9d", &hd->post_len) != 1 ) { hd->post_len = -1; syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* authorization */ else if ( !strcasecmp(field_name, "authorization") ) { /* disable cache if request _SEEMS_ needs authorization */ hd->no_cache = 1; if ( (hd->sc)->clt_hd_chk ) { /* if decode error, simply strip the line, don't return error */ if ( !htpasswd_decode(field_value, hd->auth_username, hd->auth_passwd, (hd->sc)->clt_hd_chk) ) { hd->auth = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } else { hd->auth = field_value; } } /* proxy-authorization */ else if ( !strcasecmp(field_name, "proxy-authorization") ) { /* disable cache if request needs authorization */ hd->no_cache = 1; if ( (hd->sc)->clt_hd_chk ) { /* if decode error, simply strip the line, don't return error */ if ( !htpasswd_decode(field_value, hd->pauth_username, hd->pauth_passwd, (hd->sc)->clt_hd_chk) ) { hd->proxy_auth = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } else { hd->proxy_auth = field_value; } } /* user-agent */ /* if hd->sc->browser_ver is not empty, use it */ /* else if hd->sc->browser_ver is empty, stripe this header */ /* else hd->sc->browser is NULL, return original header */ else if ( !strcasecmp(field_name, "user-agent") ) { if ( NULL != ((hd->sc)->browser_ver) ) { if ( strlen((hd->sc)->browser_ver) > 0 ) { // set user-agent to sc->browser_ver hd->user_agent = (hd->sc)->browser_ver; } else { // hd->user_agent = NULL ; } } else { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_STRING) ) { hd->user_agent = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } } /******************************************\ * belows are some other simple headers * * only check buffer length if clt_hd_chk * \******************************************/ /* cookie */ else if ( !strcasecmp(field_name, "cookie") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_COOKIE) ) { hd->cookie = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* referer */ else if ( !strcasecmp(field_name, "referer") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_LINE) ) { hd->referer = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* content-type */ else if ( !strcasecmp(field_name, "content-type") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_STRING) ) { hd->content_type = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* accept */ else if ( !strcasecmp(field_name, "accept") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_ACCEPT) ) { hd->accept = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* accept-charset */ else if ( !strcasecmp(field_name, "accept-charset") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_ACCEPT) ) { hd->accept_charset = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* accept-encoding */ else if ( !strcasecmp(field_name, "accept-encoding") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_ACCEPT) ) { hd->accept_encoding = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* accept-language */ else if ( !strcasecmp(field_name, "accept-language") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_ACCEPT) ) { hd->accept_language = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* range */ else if ( !strcasecmp(field_name, "range") ) { if ( !((hd->sc)->clt_hd_chk) || (strlen(field_value) < MAX_ACCEPT) ) { /* I have to disable cache in here */ /* otherwise, I may think I have the cache and send it out */ hd->no_cache = 1; hd->range = field_value; } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* if-modified-since */ else if ( !strcasecmp(field_name, "if-modified-since") ) { // don't worry, decode_time is buffer protected hd->if_mod_since = decode_time(field_value); // if-modified-since can't newer than server currrent time if ( hd->if_mod_since > time( (time_t *)NULL ) ) { hd->if_mod_since = (time_t)-1 ; syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* ignore all other header fields */ else { ; } } } /* too many header fields */ else { errcode = BAD_REQUEST; goto end; } } while ( !http_eof ); errcode = parse_path(hd); if ( errcode ) goto end; // call config file access control list in here if ( eval_dt(hd, hd->sc->dt) ) { // eval_dt is OK, but we still have to check return_code if ( hd->return_code != OK ) { errcode = hd->return_code; goto end; } // here ACL is allowed, other error may due to system/network if ( (errcode=resolv_remote(hd)) ) { // errcode = SERVICE_UNAVAILABLE; goto end; } } // eval_dt return 0, i.e. error else { syslog(LOG_ERR, "eval_dt() return 0\n"); errcode = INTERNAL_SERVER_ERROR; goto end; } if (hd->local) { if (!hd->cgi) { if (!hd->dir) { if (debug) syslog(LOG_DEBUG, "send_file(%s)\n", hd->path); errcode = send_file(hd, NO_CACHE); } else { if (debug) syslog(LOG_DEBUG, "send_dir(%s)\n", hd->path); errcode = send_dir(hd); } } // else cgi_handler } // proxy agent else { if ( hash(hd) ) { errcode = BAD_REQUEST; goto end; } /* hex encode URL */ if ( hex_encode(hd->path, sizeof(hd->path)) < 0 ) { errcode = BAD_REQUEST; goto end; } /* setup abs_url for send_request use */ /* note gen_abs_url return 0 is OK */ if ( gen_abs_url(hd) < 0 ) { errcode = BAD_REQUEST; goto end; } // this if-elseif statment can be merge into one // but I don't want to run is_cached() everytime, so better split into if-elseif if ( !(hd->sc)->enable_cache || hd->no_cache ) { if (debug) syslog(LOG_DEBUG, "http_proxy(%s:%d%s)\n", hd->host, hd->port, hd->path); errcode = http_proxy(hd); } else if ( is_cached(hd) ) { if (debug) syslog(LOG_DEBUG, "cached send_file(%s)\n", hd->path); errcode = send_file(hd, WITH_CACHE); } else { if (debug) syslog(LOG_DEBUG, "http_proxy(%s:%d%s)\n", hd->host, hd->port, hd->path); errcode = http_proxy(hd); } } end: /* errcode = 0 or 2?? means OK */ /* errcode > 1000 means socket error during process, simply shutdown the socket */ if ( errcode != 0 && !(errcode >= 200 && errcode < 300) && errcode < 1000 ) { send_error(hd, errcode); } http_log(hd, hd->byte_count, errcode); clean_http_header(hd); exit(errcode) ; } int http_proxy(http_header *hd) { struct hostent *rhost; char *line, *version, *ptr, *code; char buf[MAX_HEADER], timebuf[MAX_LINE]; char *field_name, *field_value; int len=0, total_len=0, http_eof=0, empty=0, line_no=0; int byte=0, content_length=-1; int cache_fd=-1, caching=0; int result, errcode, forward_proxy; lzbuf cache_lzb; time_t m_time; buf[sizeof(buf)-1] = '\0'; timebuf[sizeof(timebuf)-1] = '\0'; // setup socket if ( 0 > (hd->proxy = socket(PF_INET, SOCK_STREAM, 0)) ) { syslog(LOG_ERR, "socket(): %s\n", strerror(errno)); return INTERNAL_SERVER_ERROR; } else { if ( set_nonblock(hd->proxy) ) return INTERNAL_SERVER_ERROR; } /* if hd->forward is 0.0.0.0, then it is auto forward mode */ /* otherwise, hd->forward itself is the destination IP address */ if ( 0 != hd->forward->sin_addr.s_addr ) { if ( 0 != nonb_connect(hd->proxy, &(hd->remote_addr), sizeof(struct sockaddr_in)) ) { return INTERNAL_SERVER_ERROR; } } /* auto forward mode */ else { // here hd->host_ent must be valid // we will try to connect to the remote host ip(s) one by one rhost = hd->host_ent; while ( '\0' != *(rhost->h_addr_list) ) { hd->remote_addr.sin_family = AF_INET; memcpy(&hd->remote_addr.sin_addr, *(rhost->h_addr_list), 4); hd->remote_addr.sin_port = htons(hd->port); if ( 0 == nonb_connect(hd->proxy, &hd->remote_addr, sizeof(struct sockaddr_in)) ) break; syslog(LOG_ERR, "nonb_connect(): %s\n", inet_ntoa(hd->remote_addr.sin_addr), strerror(errno)); (rhost->h_addr_list)++; } if ( '\0' == *(rhost->h_addr_list) ) return GATEWAY_TIMEOUT; } /* if method is CONNECT, nothing has to be done */ if ( !strcmp(hd->method, "CONNECT") ) { ptr = "HTTP/1.0 200 Connection established\r\n\r\n"; if ( lz_write(hd->fd, ptr, strlen(ptr), &hd->fd_lzb) ) return E_SOCK_WRITE; else { if ( lz_flush(hd->fd, &hd->fd_lzb) ) { errcode = E_SOCK_WRITE; goto error_quit; } } result = relay_2way(hd->fd, hd->proxy); hd->byte_count = -1; return result; } // here socket is connected and valid if ( send_request(hd) ) return BAD_GATEWAY; // open cache header file *(hd->hashptr) = '.'; if ( (hd->sc)->enable_cache && !hd->no_cache ) { cache_fd = open(hd->hash, O_CREAT | O_TRUNC | O_WRONLY, 0600); // init cache lazy write buffer init_lzb(&cache_lzb); caching = 1; } // no_cache else { cache_fd = -1; caching = 0; } /* read return header from remote server */ do { if ( NULL == (line = read_line(hd->proxy, &hd->proxy_rab)) ) { errcode = BAD_GATEWAY; goto error_quit; } else { len = strlen(line); } // header dump // syslog(LOG_DEBUG, "proxy >> %s\n", line); /* empty line means eof */ if ( 0 == len ) http_eof = 1 ; /* OK, we can take a look at what we have */ /* first line have to be request status */ if ( !line_no ) { /* init hd->return_code */ hd->return_code = -1; /* format in "version code code-string" */ /* e.g. HTTP/1.0 200 OK */ line_no++; version = line; // find first space char if ( NULL == (ptr = strchr(line, ' ')) ) { errcode = BAD_GATEWAY; goto error_quit; } else { *ptr = '\0'; ptr++; } // find second space char // this is actually the code-string if ( NULL == (code = strchr(ptr, ' ')) ) { errcode = BAD_GATEWAY; goto error_quit; } else { *code = '\0'; code++; if ( ((hd->sc)->srv_hd_chk) && (strlen(code) >= MAX_STRING) ) { errcode = BAD_GATEWAY; goto error_quit; } } // OK, check if the format is correct if ( ( strcmp(version, "HTTP/0.9") && strcmp(version, "HTTP/1.0") && strcmp(version, "HTTP/1.1") ) || (sscanf(ptr, "%5d", &(hd->return_code)) != 1) ) { errcode = BAD_GATEWAY; goto error_quit; } else { // rewrite the data to our real client // I only support HTTP/1.0, so better rewrite HTTP/1.0 to the client // byte = snprintf(buf, MAX_HEADER, "%s %d %s\r\n", version, hd->return_code, code); byte = snprintf(buf, MAX_HEADER, "%s %d %s\r\n", "HTTP/1.0", hd->return_code, code); if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } // if server does not return 200, we dont have to cache anyway if ( hd->return_code != OK ) caching = 0; /* ####################################################### *\ NOTE IF 1. return is 301 or 302 2. $header_check = request (or both) 3. $forward != "auto" and $forward_proxy != enable (this means, only works for inbound only) THEN I have to rebuild the location field AND the html \* ####################################################### */ if ( MOVED_TEMPORARILY == hd->return_code || MOVED == hd->return_code ) { if ( NULL != hd->forward_proxy ) { forward_proxy = *(hd->forward_proxy); } else { forward_proxy = (hd->sc)->forward_proxy; } if ( (hd->sc)->clt_hd_chk && !forward_proxy && (0 != hd->forward->sin_addr.s_addr) ) { hd->wrap_301 = 1; } else { hd->wrap_301 = 0; } } } /***********************\ |* second line onwards *| \***********************/ else if ( line_no < MAX_HEADER_FIELDS ) { /* all header field in the format of "name: value" */ /* if condition is not match, simply neglect this line */ line_no++; field_name = line; /* all headers have to be in ascii only if srv_hd_chk is true */ if ( (hd->sc)->srv_hd_chk && non_ascii(line) ) { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); errcode = BAD_REQUEST; goto error_quit; } if ( NULL != (field_value = strchr(field_name, ':')) ) { *field_value = '\0'; field_value++; if ( ' ' == *field_value ) field_value++; /* server: server_version */ /* if hd->sc->srv_ver is not empty, use it */ /* else if hd->sc->srv_ver is empty, stripe this header */ /* else return original header */ if ( !strcasecmp(field_name, "server") ) { if ( NULL != ((hd->sc)->srv_ver) ) { if ( strlen((hd->sc)->srv_ver) > 0 ) { // send server: version to client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, (hd->sc)->srv_ver); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0' ; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } else { ; } } else { // send server: version to client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0' ; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } } /* content-length: 12345 */ else if ( !strcasecmp(field_name, "content-length") ) { /* max length is 1G, should be more than enough */ if ( sscanf(field_value , "%9d", &content_length ) == 1 ) { if ( hd->wrap_301 ) { /* do nothing */ ; } else { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) { errcode = E_SOCK_WRITE; goto error_quit; } // save header in header cache if ( caching ) { if (lz_write(cache_fd, buf, byte, &cache_lzb)) caching = 0; } } } else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); content_length = -1; } } /* Pragma: No-cache */ else if ( !strcasecmp(field_name, "pragma") ) { if ( !strcasecmp(field_value, "no-cache") ) { // upon receive of server response "pragma: no-cache" // we have to stop the caching immediately hd->no_cache=1; caching = 0; // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } } /* Cache-Control: [parameter not equal to "Public"] */ else if ( !strcasecmp(field_name, "cache-control") ) { /* note this match if NOT equal "public" */ if ( strcasecmp(field_value, "public") ) { /* stop the caching immediately */ hd->no_cache=1; caching = 0; /* rewrite the data to our real client */ byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } /* this is for field_value = "public" */ /* we don't need to change our cache control */ /* simply rewrite to client */ else { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } } /* set-cookie: key1=value1&key2=value2 */ /* don't save set-cookie to cache */ else if ( !strcasecmp(field_name, "set-cookie") ) { if ( ((hd->sc)->srv_hd_chk) && (strlen(field_value) >= MAX_COOKIE) ) { /* if srv_hd_chk and strlen(field_value) >= MAX_COOKIE */ syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } else { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } } /* last-modified or date */ else if ( !strcasecmp(field_name, "last-modified") || !strcasecmp(field_name, "date") ) { // note I will not decode the time, simply use local time // m_time = decode_time(field_value); m_time = time( (time_t *)NULL ); // rewrite the data to our real client if ( strftime(timebuf, MAX_LINE, RFC1123, gmtime(&m_time)) ) { byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, timebuf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0' ; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) { errcode = E_SOCK_WRITE; goto error_quit; } } /* strftime return 0 */ else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* handle location field, usually for HTTP return 302 or 301 */ else if ( !strcasecmp(field_name, "location") ) { if ( hd->wrap_301 ) { if ( !parse_location(hd, field_value) ) { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, hd->location); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } /* parse_location return error */ /* probable due to unknown location format */ else { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } } /* check this becuase I have to protect the client */ else if ( (hd->sc)->srv_hd_chk && (strlen(field_value) >= MAX_LINE) ) { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } /* OK, I don't need to check this header */ else { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } } } /* connection: keep-alive or close */ /* because I only support connection: close */ /* therefore, I would strip out connection header in here */ /* and return connection close at last */ else if ( !strcasecmp(field_name, "connection") ) { ; /* do nothing */ } /* all other header */ else { if ( ((hd->sc)->srv_hd_chk) && (strlen(field_value) >= MAX_LINE) ) { syslog(LOG_DEBUG, "Header Dropped=> %s: %s", field_name, field_value); } else { // rewrite the data to our real client byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", field_name, field_value); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } // save header in header cache if ( caching ) { if (lz_write(cache_fd, buf, byte, &cache_lzb)) caching = 0; } } } } } /* too many header fields */ else { errcode = BAD_GATEWAY; goto error_quit; } } while ( !http_eof ); /* now, send connection close to the client */ byte = snprintf(buf, sizeof(buf), "%s: %s\r\n", "Connection", "Close"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if (lz_write(hd->fd, buf, byte, &hd->fd_lzb)) { errcode = E_SOCK_WRITE; goto error_quit; } if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) { errcode = E_SOCK_WRITE; goto error_quit; } else { if ( lz_flush(hd->fd, &hd->fd_lzb) ) { errcode = E_SOCK_WRITE; goto error_quit; } } // save header in header cache if ( caching ) { if ( lz_write(cache_fd, "\r\n", 2, &cache_lzb) ) caching = 0; else { if ( lz_flush(cache_fd, &cache_lzb) ) caching = 0; } } // close cache header file // note we test cache_fd instead of caching if (cache_fd >= 0) { close(cache_fd); // if hd->no_cache ==> means pragma: no cache is found // if caching != 1 ==> means we have some error in cache_fd if ( hd->no_cache || !caching ) unlink(hd->hash); } /* well if remote server return NOT_MODIFIED, that's all */ if ( NOT_MODIFIED == hd->return_code ) { return 0; } /* if it is a moved response and I have to protect the server */ /* I have to send my own html */ if ( hd->wrap_301 ) { byte = snprintf(buf, sizeof(buf), "\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "\n\n%s\n\n", "Object Moved"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; /* location is a array would not be null, and its size is MAX_LINE */ byte = snprintf(buf, sizeof(buf), "\nThe Requested Object has been moved to here\n\n", hd->location); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; if ( lz_flush(hd->fd, &hd->fd_lzb) ) return -1; else return 0; } // here is the web data // we need to do file cache only if hd->return_code=200 // content-encode is not handle in here *(hd->hashptr) = '\0'; if ( (OK == hd->return_code) && (hd->sc)->enable_cache && caching ) { cache_fd = open(hd->hash, O_CREAT | O_TRUNC | O_WRONLY, 0600); } else { cache_fd = -1; } errcode = relay_data(hd->proxy, &hd->proxy_rab, hd->fd, cache_fd, content_length ); if ( 0 == errcode ) { hd->byte_count = content_length; return 0; } /* error goes to here and error_quit */ hd->byte_count = -1; error_quit: if ( cache_fd >= 0 ) { close( cache_fd ); // remove cache header *(hd->hashptr) = '.'; unlink(hd->hash); // remove cache data file *(hd->hashptr) = '\0'; unlink(hd->hash); } return errcode; } /* form a URL from header for logging and send request to web server */ /* return number of bytes generated */ /* return -1 if error */ /* buffer is null terminated if OK */ int gen_abs_url(http_header *hd) { int byte; /* abs_url may be already been setup for send_request use */ /* therefore, abs_url HAVE TO BE initialized before use */ if ( strcmp(hd->abs_url, "") ) { return strlen(hd->abs_url); } /* absolute URL */ if ( strcmp(hd->host, "") ) { if ( 80 != hd->port ) { byte = snprintf(hd->abs_url, sizeof(hd->abs_url), "http://%s:%d", hd->host, hd->port); } else { byte = snprintf(hd->abs_url, sizeof(hd->abs_url), "http://%s", hd->host); } } /* relative URL */ else { byte = snprintf(hd->abs_url, sizeof(hd->abs_url), ""); } if ( sizeof(hd->abs_url) == byte ) { *(hd->abs_url+byte-1) = '\0'; return -1; } else { return byte; } } /* praser the location field */ /* return 0 if OK, -1 if error */ /* hd->location will be setup properly if OK */ int parse_location (http_header *hd, char *line) { char host[MAX_LINE]; char path[MAX_LINE]; char *ptr; int port, byte; struct in_addr host_addr; struct hostent *loc_host_ent; char loc_host[MAX_LINE]; int loc_port; /* make sure buffers are null terminated */ host[sizeof(host)-1] = '\0'; path[sizeof(path)-1] = '\0'; /* check buffer */ if ( MAX_LINE <= strlen(line) ) return -1; /* absolute URL "http://somehting" */ if ( !strncasecmp(line, "http://", 7) ) { /* find next '/' after "http://" */ ptr = strchr(line+7, '/'); if ( NULL == ptr ) { /* if not found, assume the whole thing is a hostname */ strcpy(path, "/"); } else { strcpy(path, ptr); *ptr = '\0'; } /* now search for ':' */ ptr = strchr(line+7, ':'); if ( NULL == ptr ) { /* not found, default port = 80 */ port = 80; } else { /* point ptr after the ':' */ if ( sscanf(ptr+1, "%5d", &port) == 1 ) { *ptr = '\0'; } else { return -1; } } /* finally, get the hostname */ strcpy(host, line+7); /* ####################################################### *\ now we have "host" and "port" in hand if "host" = IP address I am connected to take away the IP address, the location field would then be "/path/to/here.html" instead of "http://www.web.com/path/to/here.html" otherwise, don't need to do anything \* ####################################################### */ if ( str2addr(host, &host_addr) ) /* str2addr return 0 means not a valid IP address string */ { syslog(LOG_DEBUG, "Yes IP: %s:%d ? %s:%d\n", inet_ntoa(host_addr), port, inet_ntoa(hd->forward->sin_addr), ntohs(hd->forward->sin_port)); if ( !memcmp (&(host_addr.s_addr), &(hd->forward->sin_addr.s_addr), 4) && port == ntohs(hd->forward->sin_port) ) { syslog(LOG_DEBUG, "IP Matched\n"); /* take away the IP, location field change to relative path */ strcpy(hd->location, path); return 0; } syslog(LOG_DEBUG, "IP NOT match\n"); } /* may be host is IP address but not the IP of remote destination server */ /* copy everything back to location, but be remind "line" is already corrupted */ if ( 80 == port ) { byte = snprintf(hd->location, sizeof(hd->location), "http://%s%s", host, path); } else { byte = snprintf(hd->location, sizeof(hd->location), "http://%s:%d%s", host, port, path); } /* well, I don't think there would be overflow here, but just give it a check */ if ( sizeof(hd->location) == byte ) { return -1; } else { return 0; } } /* relative URL "/path/file.html" */ else if ( !strncasecmp(line, "/", 1) ) { /* assume hd->location is MAX_LINE */ strcpy(hd->location, line); return 0; } /* no match type */ else { /* not RFC-compliant, but Location:'s without "/" exists, so ... */ strcpy(hd->location, line); return 0; } } int parse_request (http_header *hd, char *line) { char *ptr, *url; // GET http://server.com/page.html HTTP/1.0 hd->method = line; if ( NULL == (url=strchr(line, ' ')) ) return BAD_REQUEST; *url = '\0'; url++; // note strrchr not strchr, because url can contain space if ( NULL == (hd->version=strrchr(url, ' ')) ) return BAD_REQUEST; *hd->version = '\0'; hd->version++; // note hd->method and hd->version are case sensitive if ( strcmp(hd->method, "GET") && strcmp(hd->method, "POST") && strcmp(hd->method, "CONNECT") ) return NOT_IMPLEMENTED; if ( strcmp(hd->version, "HTTP/0.9") && strcmp(hd->version, "HTTP/1.0") && strcmp(hd->version, "HTTP/1.1") ) return NOT_IMPLEMENTED; // check url length if ( strlen(url) >= MAX_LINE ) { return BAD_REQUEST; } /* parse the url */ /* CONNECT has it own special format */ if ( !strcmp(hd->method, "CONNECT") ) { if ( 0 == (hd->sc)->enable_https ) return FORBIDDEN; /* host:port format, only valid for CONNECT */ if ( sscanf( url, "%[^:/]:%5d", hd->host, &hd->port ) == 2 ) { if ( hd->port != HTTPS_PORT ) return FORBIDDEN; /* this is not use, just key-in something */ strcpy(hd->opath, "/"); } else return BAD_REQUEST; } /* the followings are only for GET, POST */ /* is it "http://somehting" */ else if ( !strncasecmp(url, "http://", 7) ) { /* find next '/' after "http://" */ ptr = strchr(url+7, '/'); if ( NULL == ptr ) { /* if not found, assume the whole thing is a hostname */ strcpy(hd->opath, "/"); } else { strcpy(hd->opath, ptr); *ptr = '\0'; } /* now search for ':' */ ptr = strchr(url+7, ':'); if ( NULL == ptr ) { /* not found, default port = 80 */ hd->port = 80; } else { /* point ptr after the ':' */ if ( sscanf(ptr+1, "%5d", &hd->port) == 1 ) { *ptr = '\0'; } else { return BAD_REQUEST; } } /* finally, get the hostname */ strcpy(hd->host, url+7); } /* is it "/path/file.html" */ else if ( '/' == *url ) { strncpy(hd->opath, url, sizeof(hd->opath)); hd->opath[sizeof(hd->opath)-1] = '\0'; strcpy(hd->host, ""); hd->port = 80; } /* no match type */ else { return BAD_REQUEST; } /* get the query string from hd->opath */ if ( NULL != ( ptr = strchr(hd->opath, '?') ) ) { /* query string found */ *ptr = '\0'; ptr++; if ( strlen(ptr) >= MAX_QUERY ) return BAD_REQUEST; hd->query = ptr; } /* get the sessionid from hd->opath */ /* Java Servlet may include sessionid in URL instead of cookie field */ /* it looks like http://www.host.com/path/file.html;sessionid=0123456789 */ /* more info, refer to SunMicro */ if ( NULL != ( ptr = strchr(hd->opath, ';') ) ) { /* sessionid found */ *ptr = '\0'; ptr++; if ( strlen(ptr) >= MAX_SESSIONID ) return BAD_REQUEST; hd->sessionid = ptr; } /* ignore Anchor part */ /* anyway, the file is the same no matter what anchor it is */ /* this is not allowed here, anything after the anchor part is removed */ /* therefore, there should not have any '#' in the URL after decode */ if ( NULL != ( ptr = strchr(hd->opath, '#') ) ) { *ptr = '\0'; } /* check string length */ if ( strlen(hd->host) >= MAX_HOST ) return BAD_REQUEST; /* so MAX_URL does not include the QUERY string */ if ( strlen(hd->opath) >= MAX_URL ) return BAD_REQUEST; // copy url into hd->path // hd->path may be changed later, but hd->opath will not // note will only copy first MAX_LINE - 1 elements strcpy(hd->path, hd->opath); // append a '/' is there is not // point ptr to last element // ptr = hd->opath + strlen(hd->opath) - 1 ; // if ( '/' != *ptr ) // { // strcat(hd->opath, "/"); // already check buffer size, safe for strcat // } // disable caching for HTTP POST or GET with query string if ( !strcmp(hd->method, "POST") || (NULL != hd->query) ) hd->no_cache = 1; return 0; } int parse_path(http_header *hd) { char url[MAX_LINE]; int local, errcode; /* make sure url is null terminated */ url[sizeof(url)-1] = '\0'; // de_hexcode no matter if local or remote de_hexcode(hd->path); // find extension in here, so only check input url extension // local server with links to very strange file extension will be allowed hd->ext = find_ext(hd->path); // local web server, not working as proxy server if ( hd->sc->local ) { // local server will check malicious url by default if ( non_ascii(hd->path) ) return BAD_REQUEST; if ( invalid_ext(hd->ext) ) return BAD_REQUEST; if ( is_hidden(hd->path) ) return FORBIDDEN; if ( is_dotdotdot(hd->path) ) return BAD_REQUEST; if ( has_meta(hd->path, PATH_META) ) return BAD_REQUEST; // insert current dir path in the input url strcpy(url, hd->path); if ( NULL != getcwd(hd->path, sizeof(hd->path)-1) ) { // total size: current_path + hd->path + "\0" if ( strlen(url) + strlen(hd->path) > MAX_LINE-1 ) return BAD_REQUEST; strcat(hd->path, url); // buffer size checked, strcat safe // expand symbolic link and de_dotdot errcode = expand_link(hd->path, MAX_LINK); if ( errcode ) return errcode; // is the path a directory? if ( is_dir(hd->path) ) { // is index file available? if ( search_index(hd) ) { hd->dir = 0; errcode = expand_link(hd->path, MAX_LINK); if ( errcode ) return errcode; } else { // dir broswing hd->dir = 1; } } else { hd->dir = 0; } // is the path a cgi? if ( is_cgi_path(hd->path) ) { if ( hd->dir ) return FORBIDDEN; // deny cgi dir broswing hd->cgi = 1; } else { hd->cgi = 0; } // local server has to check is_hidden twice // original input and decoded output should not be hidden file or dir if ( is_hidden(hd->path) ) return FORBIDDEN; } // getcwd error else { syslog(LOG_ERR, "getcwd(): %s\n", strerror(errno)); return BAD_REQUEST; } } // not local else { hd->local = 0; hd->dir = 0; hd->cgi = 0; // only check malicious path when enabled if ( hd->sc->safe_url ) { // hd->path already de_hexcode, so de_dotdot is safe de_dotdot(hd->path); if ( non_ascii(hd->path) ) return BAD_REQUEST; if ( invalid_ext(hd->ext) ) return BAD_REQUEST; if ( is_hidden(hd->path) ) return FORBIDDEN; if ( is_dotdotdot(hd->path) ) return BAD_REQUEST; if ( has_meta(hd->path, PATH_META) ) return BAD_REQUEST; } } return 0; } int send_file (http_header *hd, int cache) { char buf[MAX_LINE], *ptr, *fname; int file, byte, result; struct stat fs; int errcode; /* make sure buf is null terminated */ buf[sizeof(buf)-1] = '\0'; /* set hash to be cache data file */ *(hd->hashptr) = '\0'; if ( cache ) fname = hd->hash; else fname = hd->path; // check if the input filename is under current working dir if ( !cache ) { if ( NULL == getcwd(buf, sizeof(buf)-1) ) { syslog(LOG_ERR, "getcwd(): %s\n", strerror(errno)); return INTERNAL_SERVER_ERROR; } else { ptr = strstr(fname, buf); if ( ptr != fname ) return FORBIDDEN; } } /* avoid race condition, open the file and use fstat to check file status */ if ( (file=open(fname, O_RDONLY)) < 0 ) { /* if this is a cache operation, but the file is not found */ /* probably this is an orphan file, better also delete the cache header */ if ( cache ) { /* set hash to be cache header */ *(hd->hashptr) = '.'; unlink(hd->hash); } return NOT_FOUND; } if ( 0 != fstat(file, &fs) ) return NOT_FOUND; // check if return full file or NOT_MODIFIED if ( hd->if_mod_since != (time_t)-1 ) { // for cache operation // [A] [B] [C] [D] // ------------------------------------------------> time // | | | // cache_file cache_header timeout // is_cache already sure it would be [D] // If If-Modified-Since is at: // [A] => send cache_file // [B] or [C] => send 304 NOT_MODIFIED // // for non-cache operation // the logic is the same, send whole data file only if // If-Modified-Since is elder than file last-modified (i.e. in zone [A]) if ( hd->if_mod_since >= fs.st_mtime ) return NOT_MODIFIED; } /* checking permission */ /* for caching, file has to be readable by owner ONLY */ /* for not caching, file has to be world readable */ if ( cache ) { /* in fact, file is already open */ /* therefore, if the file is NOT group and world readable */ /* then the file is only readable by owner and I am, in fact, the owner */ if ( (fs.st_mode & S_IRGRP) || (fs.st_mode & S_IROTH) ) return FORBIDDEN; } else { if ( !(fs.st_mode & S_IROTH) ) return FORBIDDEN; } if ( cache ) { errcode = send_header(hd, WITH_CACHE, fs.st_size, NULL, -1); if ( errcode != 0 ) { close(file); return errcode; } } else { errcode = send_header(hd, NO_CACHE, fs.st_size, check_mime_type(hd->path), fs.st_mtime); if ( errcode != 0 ) { close (file); return errcode; } } result = relay_data(file, NULL, hd->fd, -1, fs.st_size); close(file); /* setup total data send */ if ( result ) hd->byte_count = -1; else hd->byte_count = max( fs.st_size, 0 ); return result; } int send_dir (http_header *hd) { /* directory handle */ DIR *dir; struct dirent *dir_entry; struct stat fs; char mode_string[5]; char buf[MAX_LINE], file[MAX_LINE]; char *ptr; int byte, errcode; buf[sizeof(buf)-1] = '\0'; file[sizeof(file)-1] = '\0'; if ( NULL == getcwd(buf, sizeof(buf)-1) ) { syslog(LOG_ERR, "getcwd(): %s\n", strerror(errno)); return INTERNAL_SERVER_ERROR; } else { ptr = strstr(hd->path, buf); if ( ptr != hd->path ) return FORBIDDEN; } if ( 0 != stat(hd->path, &fs) ) return NOT_FOUND; /* is directory world executable? */ if ( fs.st_mode & S_IXOTH ) { /* we are sure hd->path is a path name */ if ( NULL == (dir = opendir(hd->path)) ) return NOT_FOUND; errcode = send_header(hd, NO_CACHE, fs.st_size, "text/html", fs.st_mtime); if ( errcode != 0 ) return errcode; byte = snprintf(buf, sizeof(buf), "Index of %s\n", hd->opath); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; byte = snprintf(buf, sizeof(buf), "

Index of %s

\n", hd->opath); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; byte = snprintf(buf, sizeof(buf), "
%-10s     %6s     %-32s
\n
", "Attribute", "Size", "File Name");
        if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; }
        if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE;
        
        while ( NULL != (dir_entry = readdir(dir)) ) 
        {
            // total size: dir_path + "/" + file_name + '\0'
            if ( strlen(hd->path) + 1 + strlen(dir_entry->d_name) < MAX_LINE ) 
            {
                strcpy(file, hd->path);
                strcat(file, "/");
                strcat(file, dir_entry->d_name);
                /* sure the file exist and is readable*/
                stat(file, &fs);
                if ( (fs.st_mode & S_IROTH) && !is_hidden(file) ) 
                {
                    byte = snprintf(buf, sizeof(buf), "

%s %6dk %s

\n", mode2str(fs.st_mode, mode_string, sizeof(mode_string)), max((int)((int)fs.st_size/1024), 1), hd->opath, dir_entry->d_name, dir_entry->d_name); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } } } byte = snprintf(buf, sizeof(buf), "
\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; if ( lz_flush(hd->fd, &hd->fd_lzb) ) return E_SOCK_WRITE; return 0; } else { return FORBIDDEN; } } int send_request(http_header *hd) { char buf[MAX_HEADER], timebuf[MAX_LINE]; char *url; int result, byte; // make sure buf is null terminated // note buf size is MAX_LINE + 1 buf[sizeof(buf)-1] = '\0'; timebuf[sizeof(timebuf)-1] = '\0'; /* For compatibility reason If I send the request to a proxy, I have to send something like GET http://host.com/path/file.ext HTTP/1.0 But if I send the file to a destination web server, I should sue GET /path/file.ext HTTP/1.0 if *hd->forward_proxy is NULL, I will point hd->proxy to sc->forward_proxy if *hd->forward_proxy is 0, I will use web server format if *hd->forward_proxy is 1, I will use proxy server format */ if ( NULL == hd->forward_proxy ) { hd->forward_proxy = &((hd->sc)->forward_proxy); } if ( 0 != *(hd->forward_proxy) ) /* proxy mode */ { // send GET command if ( NULL != hd->query ) { if ( NULL != hd->sessionid ) { byte = snprintf(buf, sizeof(buf), "%s %s%s;%s?%s HTTP/1.0\r\n", hd->method, hd->abs_url, hd->path, hd->sessionid, hd->query); // syslog(LOG_ERR, "%s\n", buf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } else { byte = snprintf(buf, sizeof(buf), "%s %s%s?%s HTTP/1.0\r\n", hd->method, hd->abs_url, hd->path, hd->query); // syslog(LOG_ERR, "%s\n", buf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } else { if ( NULL != hd->sessionid ) { byte = snprintf(buf, sizeof(buf), "%s %s%s;%s HTTP/1.0\r\n", hd->method, hd->abs_url, hd->path, hd->sessionid); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } else { byte = snprintf(buf, sizeof(buf), "%s %s%s HTTP/1.0\r\n", hd->method, hd->abs_url, hd->path); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } } else /* web server mode */ { // send GET command if ( NULL != hd->query ) { if ( NULL != hd->sessionid ) { byte = snprintf(buf, sizeof(buf), "%s %s;%s?%s HTTP/1.0\r\n", hd->method, hd->path, hd->sessionid, hd->query); // syslog(LOG_ERR, "%s\n", buf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } else { byte = snprintf(buf, sizeof(buf), "%s %s?%s HTTP/1.0\r\n", hd->method, hd->path, hd->query); // syslog(LOG_ERR, "%s\n", buf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } else { if ( NULL != hd->sessionid ) { byte = snprintf(buf, sizeof(buf), "%s %s;%s HTTP/1.0\r\n", hd->method, hd->path, hd->sessionid); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } else { byte = snprintf(buf, sizeof(buf), "%s %s HTTP/1.0\r\n", hd->method, hd->path); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } } // send HOST: if ( strlen(hd->host) > 0 ) { if ( 80 != hd->port ) { byte = snprintf(buf, sizeof(buf), "Host: %s:%d\r\n", hd->host, hd->port); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } else { byte = snprintf(buf, sizeof(buf), "Host: %s\r\n", hd->host); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } // cookie if ( NULL != hd->cookie ) { byte = snprintf(buf, sizeof(buf), "Cookie: %s\r\n", hd->cookie); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // referer if ( NULL != hd->referer ) { byte = snprintf(buf, sizeof(buf), "Referer: %s\r\n", hd->referer); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // user-agent if ( NULL != hd->user_agent ) { byte = snprintf(buf, sizeof(buf), "User-Agent: %s\r\n", hd->user_agent); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // content-type if ( NULL != hd->content_type ) { byte = snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", hd->content_type); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // authorization if ( NULL != hd->auth ) { byte = snprintf(buf, sizeof(buf), "Authorization: %s\r\n", hd->auth); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // accept if ( NULL != hd->accept ) { byte = snprintf(buf, sizeof(buf), "Accept: %s\r\n", hd->accept); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // accept-charset if ( NULL != hd->accept_charset ) { byte = snprintf(buf, sizeof(buf), "Accept-Charset: %s\r\n", hd->accept_charset); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // accept-encoding if ( NULL != hd->accept_encoding ) { byte = snprintf(buf, sizeof(buf), "Accept-Encoding: %s\r\n", hd->accept_encoding); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // accept-language if ( NULL != hd->accept_language ) { byte = snprintf(buf, sizeof(buf), "Accept-Language: %s\r\n", hd->accept_language); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // range if ( NULL != hd->range ) { byte = snprintf(buf, sizeof(buf), "Range: %s\r\n", hd->range); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } // if-modified-since if ( (time_t)-1 != hd->if_mod_since ) { if ( strftime(timebuf, sizeof(timebuf)-1, RFC1123, gmtime(&hd->if_mod_since)) ) { byte = snprintf(buf, sizeof(buf), "If-Modified-Since: %s\r\n", timebuf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; } } /* connection: close */ /* byte = snprintf(buf, sizeof(buf), "Connection: close\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; */ if ( !strcmp(hd->method, "POST") && (hd->post_len > 0) ) { // HTTP POST byte = snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", hd->post_len); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->proxy, buf, byte, &hd->proxy_lzb) ) return BAD_GATEWAY; if ( lz_write(hd->proxy, "\r\n", 2, &hd->proxy_lzb) ) return BAD_GATEWAY; if ( lz_flush(hd->proxy, &hd->proxy_lzb) ) return BAD_GATEWAY; result = relay_data(hd->fd, &hd->fd_rab, hd->proxy, -1, hd->post_len); return result; } else { // HTTP GET if ( lz_write(hd->proxy, "\r\n", 2, &hd->proxy_lzb) ) return BAD_GATEWAY; if ( lz_flush(hd->proxy, &hd->proxy_lzb) ) return BAD_GATEWAY; else return 0; } } int send_header (http_header *hd, int cache, int length, char *mime, time_t m_time) { time_t now; char timebuf[MAX_LINE]; char buf[MAX_LINE]; char *line; int byte, len, eof, cache_hd_fd; rabuf cache_rab; struct stat fs; // make sure buf is null terminated // note buf size is MAX_LINE + 1 buf[sizeof(buf)-1] = '\0'; timebuf[sizeof(timebuf)-1] = '\0'; byte = snprintf(buf, sizeof(buf), "%s %d %s\r\n", hd->version, OK, "OK"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; if ( NULL != ((hd->sc)->srv_ver) ) { if ( strlen((hd->sc)->srv_ver) > 0 ) { byte = snprintf(buf, sizeof(buf), "Server: %s\r\n", (hd->sc)->srv_ver); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } } now = time( (time_t *)NULL ); if ( strftime(timebuf, sizeof(timebuf)-1, RFC1123, gmtime(&now)) ) { byte = snprintf(buf, sizeof(buf), "Date: %s\r\n", timebuf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } if ( '\0' != mime || NULL != mime ) { byte = snprintf(buf, sizeof(buf), "Content-type: %s\r\n", mime); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } if ( length > 0 ) { byte = snprintf(buf, sizeof(buf), "Content-length: %d\r\n", length); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } if ( m_time != (time_t) -1 ) { if ( strftime(timebuf, sizeof(timebuf)-1, RFC1123, gmtime(&m_time)) ) { byte = snprintf(buf, sizeof(buf), "Last-modified: %s\r\n", timebuf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } } byte = snprintf(buf, sizeof(buf), "Connection: close\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; if ( !cache ) { // noted the double newline if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) return E_SOCK_WRITE; } else { // replay any other cached header *(hd->hashptr) = '.'; cache_hd_fd = open(hd->hash, O_RDONLY); /* check permission */ /* cache header file can ONLY be readable by ME */ if ( cache_hd_fd >= 0 ) { if ( 0 == fstat(cache_hd_fd, &fs) ) { if ( (fs.st_mode & S_IRGRP) || (fs.st_mode & S_IROTH) ) { /* group or world readable, close the cache_hd_fd */ close(cache_hd_fd); cache_hd_fd = -1; if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) return E_SOCK_WRITE; } } else { /* fstat return error, better close the file */ close(cache_hd_fd); cache_hd_fd = -1; if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) return E_SOCK_WRITE; } } eof = 0; init_rab(&cache_rab); while ( (cache_hd_fd >= 0) && !eof ) { if ( NULL != (line=read_line(cache_hd_fd, &cache_rab)) ) { len = strlen(line) ; if ( 0 == len ) eof = 1; // header dump // syslog(LOG_DEBUG, "cache_hd_fd >> %s\n", line); // filter out some header if necessary if ( ( !strncasecmp(line, "server", 6) && (NULL == (hd->sc)->srv_ver) ) || !strncasecmp(line, "date", 4) || !strncasecmp(line, "content-length", 14) || !strncasecmp(line, "set-cookie", 10) || !strncasecmp(line, "connection", 10) ) { ; } // all other header else { // rewrite the data to our real client if ( lz_write(hd->fd, line, len, &hd->fd_lzb) ) return E_SOCK_WRITE; if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) return E_SOCK_WRITE; } } else { if ( lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb) ) return E_SOCK_WRITE; break; } } // close cache header file if ( cache_hd_fd >= 0 ) close(cache_hd_fd); } if ( lz_flush(hd->fd, &hd->fd_lzb) ) return E_SOCK_WRITE; else return 0; } int send_error (http_header *hd, int error_no) { char *status, *error_h1, *error_h2; char timebuf[MAX_LINE], buf[MAX_ERROR]; time_t now; int byte; buf[sizeof(buf)-1] = '\0'; timebuf[sizeof(timebuf)-1] = '\0'; switch(error_no){ case MOVED: status = "MOVE"; break; case MOVED_TEMPORARILY: status = "MOVE TEMPORARILY"; break; case NOT_MODIFIED: status = "NOT_MODIFIED"; // error_h1 = "Not Modified"; // error_h2 = "The requested page has not been modified"; break; case BAD_REQUEST: status = "BAD_REQUEST"; error_h1 = "Invalid request"; error_h2 = "The requested page can not be resolved."; break; /* proxy_auth and unauth share the same code */ case PROXY_AUTHENTICATION_REQUIRED: case UNAUTHORIZED: status = "UNAUTHORIZED"; error_h1 = "Unauthorized Access"; error_h2 = "The username and login password is not correct."; break; case FORBIDDEN: status = "FORBIDDEN" ; error_h1 = "Forbidden"; error_h2 = "The requested page is not avaiable."; break; case NOT_FOUND: status = "NOT_FOUND" ; error_h1 = "Page not found"; error_h2 = "The requested page is not found. This may due to mis-spelled url or broken link."; break; case INTERNAL_SERVER_ERROR: status = "INTERNAL_SERVER_ERROR" ; error_h1 = "Internal Server Error"; error_h2 = "The server encountered unrecoverable error. Please try again later."; break; case NOT_IMPLEMENTED: status = "NOT_IMPLEMENTED" ; error_h1 = "Method not implemented"; error_h2 = "The requested method is not implemented by this server. Please contact your System Administrator for any details"; break; case BAD_GATEWAY: status = "BAD_GATEWAY" ; error_h1 = "Bad Gateway"; error_h2 = "The requested host is not resolved. This may due to server down or mis-spelled url."; break; case GATEWAY_TIMEOUT: status = "GATEWAY_TIMEOUT" ; error_h1 = "Gateway Timeout"; error_h2 = "Cannot connect to remote web server. Probably due to network traffic error or server is down."; break; case SERVICE_UNAVAILABLE: status = "SERVICE_UNAVAILABLE"; error_h1 = "Service Unavilable"; error_h2 = "The requested services is not avaiable."; break; default: status = "UNKNOWN"; error_h1 = "Unknow Error"; error_h2 = "The server encounter unknow error, Please contact your system administrator for further information"; break; } /* noted STDOUT is the hd->fd, ref to main() */ byte = snprintf(buf, sizeof(buf), "%s %d %s\r\n", "HTTP/1.0", error_no, status); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; if ( NULL != (hd->sc)->srv_ver ) { if ( strlen((hd->sc)->srv_ver) > 0 ) { byte = snprintf(buf, sizeof(buf), "Server: %s\r\n", (hd->sc)->srv_ver); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return E_SOCK_WRITE; } } if ( NOT_IMPLEMENTED == error_no ) { byte = snprintf(buf, sizeof(buf), "Allow: GET, POST\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; } else if ( UNAUTHORIZED == error_no ) { byte = snprintf(buf, sizeof(buf), "WWW-Authenticate: Basic\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; } else if ( PROXY_AUTHENTICATION_REQUIRED == error_no ) { byte = snprintf(buf, sizeof(buf), "Proxy-Authenticate: Basic\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; } else if ( MOVED == error_no || MOVED_TEMPORARILY == error_no ) { byte = snprintf(buf, sizeof(buf), "Location: %s\r\n", hd->location); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; } now = time( (time_t *)NULL ); if ( strftime(timebuf, sizeof(timebuf)-1, RFC1123, gmtime(&now)) ) { byte = snprintf(buf, sizeof(buf), "Date: %s\r\n", timebuf); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; } byte = snprintf(buf, sizeof(buf), "Content-type: %s\r\n", "text/html"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "Connection: close\r\n\r\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; /* belows are HTTP body */ if ( NOT_MODIFIED == error_no ) { if ( lz_flush(hd->fd, &hd->fd_lzb) ) return -1; return 0; } else if ( MOVED == error_no || MOVED_TEMPORARILY == error_no ) { byte = snprintf(buf, sizeof(buf), "\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "\n\n%s\n\n", "Object Moved"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; /* location is a array would not be null, and its size is MAX_LINE */ byte = snprintf(buf, sizeof(buf), "\nThe Requested Object has been moved to here\n\n", hd->location); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; if ( lz_flush(hd->fd, &hd->fd_lzb) ) return -1; else return 0; } else { byte = snprintf(buf, sizeof(buf), "\n"); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "\n\n%s\n\n", status); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "\n

%s

\n\n", error_h1); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; byte = snprintf(buf, sizeof(buf), "

%s

\n\n\n", error_h2); if ( sizeof(buf) == byte ) { buf[sizeof(buf)-1] = '\0'; } if ( lz_write(hd->fd, buf, byte, &hd->fd_lzb) ) return -1; if ( lz_flush(hd->fd, &hd->fd_lzb) ) return -1; else return 0; } } ////////////////////////////////////////////////////////////////////// // Socket manipulation ////////////////////////////////////////////////////////////////////// /* read until I found a complete http header 1. data will be stored in rab 4. if rab buffer full, return NULL */ char *read_block(int fd, rabuf *rab) { int line_len=0, byte=0; char *end, *ptr; int use_cat=0; struct timeval tv; fd_set rset; int s, val, maxfd; // note last byte in rad->a is reserved for '\0' // rab->n == MAX_RAB means buf full // note byte = 0 is allowed byte = MAX_RAB - (rab->p - rab->a) - rab->n - 1; if ( byte < 0 ) { syslog(LOG_ERR, "read_line() buffer full, max buf = %d", MAX_RAB); return NULL; } // check if read ahead buffer already has want we want if ( rab->n > 0 ) { // rab->p is not empty and newline found if ( NULL != (end = strstr(rab->p, "\r\n\r\n")) ) { rab->n -= (end + 4 - rab->p); *end = '\0' ; // advance rab->p ptr = rab->p; rab->p = end + 4; return ptr; } else if ( NULL != (end = strstr(rab->p, "\n\n")) ) { rab->n -= (end + 2 - rab->p); *end = '\0' ; // advance rab->p ptr = rab->p; rab->p = end + 2; return ptr; } } // don't have what we want?? but we are running out of space if ( byte <= 0 ) return NULL; // there are 2 conditions to reach here // 1) rab->p is empty // 2) rab->p is not empty, but does not have '\n', // read data from fd store in buf // init variable that we will use in the while loop FD_ZERO(&rset); FD_SET(fd, &rset); ptr = rab->p; while ( 1 ) { byte = MAX_RAB - (rab->p - rab->a) - rab->n - 1; if ( byte <= 0 ) return NULL; // init tv everytime, becuase Linux will modify this on return tv.tv_sec = SOCKET_TIMEOUT; tv.tv_usec = 0; while ( 0 >= (s=select(fd+1, &rset, NULL, NULL, &tv)) ) { if ( 0 == s ) return NULL; // time out else if ( EINTR == errno ) continue; // interrupted else return NULL; // other error } // here, select return > 0 // read data from socket, append to end of rab if ( FD_ISSET(fd, &rset) ) byte = read(fd, ptr, byte); else return NULL; // impossible, fd is the only thing in rset if ( byte > 0 ) { // note if the input data is not ascii // rab->n and rab->p may not be consistant // but this is not a problem as we use rab->n only for http message part rab->n += byte; ptr += byte; if ( NULL != (end = strstr(rab->p, "\r\n\r\n")) ) { rab->n -= (end + 4 - rab->p); *end = '\0' ; // advance rab->p ptr = rab->p; rab->p = end + 4; return ptr; } else if ( NULL != (end = strstr(rab->p, "\n\n")) ) { rab->n -= (end + 2 - rab->p); *end = '\0' ; // advance rab->p ptr = rab->p; rab->p = end + 2; return ptr; } continue; } else if ( byte < 0 && EINTR == errno ) { continue; } // this is, in fact, signal of EOF else if ( 0 == byte ) { return NULL; } // other read error else { return NULL; } } // you should not reach this return NULL; } /* read data from file descriptor 1. data will be stored in rab 2. return a pointer to a line found within rab 3. return pointer contains data up to '\r' 4. if rab buffer full, return NULL */ char *read_line(int fd, rabuf *rab) { int line_len=0, byte=0; char *end, *ptr; int use_cat=0; struct timeval tv; fd_set rset; int s, val, maxfd; // note last byte in rad->a is reserved for '\0' // rab->n == MAX_RAB means buf full // note byte = 0 is allowed byte = MAX_RAB - (rab->p - rab->a) - rab->n - 1; if ( byte < 0 ) { syslog(LOG_ERR, "read_line() buffer full, max buf = %d", MAX_RAB); return NULL; } // check if read ahead buffer already has want we want if ( rab->n > 0 ) { // rab->p is not empty and newline found if ( NULL != (end = strchr(rab->p, '\n')) ) { rab->n -= (end + 1 - rab->p); *end = '\0' ; // search if there is any other '\r', if found set it to '\0' // therefore only string to the first '\r' are copied to newline if ( NULL != (ptr = strchr(rab->p, '\r')) ) *ptr = '\0'; // advance rab->p ptr = rab->p; rab->p = end + 1; return ptr; } } // don't have what we want?? but we are running out of space if ( byte <= 0 ) return NULL; // there are 2 conditions to reach here // 1) rab->p is empty // 2) rab->p is not empty, but does not have '\n', // read data from fd store in buf // init variable that we will use in the while loop FD_ZERO(&rset); FD_SET(fd, &rset); ptr = rab->p; while ( 1 ) { byte = MAX_RAB - (rab->p - rab->a) - rab->n - 1; if ( byte <= 0 ) return NULL; // init tv everytime, becuase Linux will modify this on return tv.tv_sec = SOCKET_TIMEOUT; tv.tv_usec = 0; while ( 0 >= (s=select(fd+1, &rset, NULL, NULL, &tv)) ) { if ( 0 == s ) return NULL; // time out else if ( EINTR == errno ) continue; // interrupted else return NULL; // other error } // here, select return > 0 // read data from socket, append to end of rab if ( FD_ISSET(fd, &rset) ) byte = read(fd, ptr, byte); else return NULL; // impossible, fd is the only thing in rset if ( byte > 0 ) { // note if the input data is not ascii // rab->n and rab->p may not be consistant // but this is not a problem as we use rab->n only for http message part rab->n += byte; ptr += byte; if ( NULL != (end = strchr(rab->p, '\n')) ) { rab->n -= (end + 1 - rab->p); *end = '\0' ; // search if there is any other '\r', if found set it to '\0' // therefore only string to the first '\r' are copied to newline if ( NULL != (ptr = strchr(rab->p, '\r')) ) *ptr = '\0'; // advance rab->p ptr = rab->p; rab->p = end + 1; return ptr; } continue; } else if ( byte < 0 && EINTR == errno ) { continue; } // this is, in fact, signal of EOF else if ( 0 == byte ) { return NULL; } // other read error else { return NULL; } } // you should not reach this return NULL; } // lazy write to socket fd with lazy buffer lzb // if buffer is not full, would only store data in lzb // if buffer is full, would issue lzflush automatically // length can be > MAX_LINE // return 0 if success // return -1 if error int lz_write(int fd, char *line, int length, lzbuf *lzb) { int space, byte; char *end, *src; if ( 0 >= length ) return 0; space = MAX_LZB - lzb->n; if ( 0 >= space ) return -1; src = line; do { byte = min(space, length); end = lzb->a + lzb->n; memcpy(end, src, byte); lzb->n += byte; length -= byte; src += byte; if ( MAX_LZB <= lzb->n ) { if ( lz_flush(fd, lzb) ) { return -1; } } } while ( length > 0 ); return 0; } // send data from lazy buffer to socket in non-blocking mode // time out is impletemented by select() // return 0 if OK // return -1 if error int lz_flush(int write_fd, lzbuf *lzb) { char *p; int s, byte; p = lzb->a; while ( 1 ) { byte = write(write_fd, p, lzb->n); if ( byte == lzb->n ) { lzb->n = 0; return 0; } else if ( byte > 0 && byte != lzb->n ) { p += byte; lzb->n -= byte; continue; } else if ( byte <= 0 && EINTR == errno ) { continue; } else if ( byte <= 0 && EWOULDBLOCK == errno ) { if ( wait_writable(write_fd) ) return -1; continue; } else { return -1; } } } // wait until the socket is writable // return 0 if OK // return -1 if timeout or any other erros int wait_writable(int fd) { fd_set wset; struct timeval tv; int s; int error, n; FD_ZERO(&wset); FD_SET(fd, &wset); tv.tv_sec = SOCKET_TIMEOUT; tv.tv_usec = 0; while ( 0 >= (s=select(fd+1, NULL, &wset, NULL, &tv)) ) { if ( 0 == s ) return -1; // time out else if ( EINTR == errno ) continue; // interrupted else return -1; // other errors } // here, select return > 0 if ( !FD_ISSET(fd, &wset) ) return -1; // almost impossible else { n = sizeof(int); if ( getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &n) ) { return -1; // getsockopt error } else { if ( error ) return -1; // probably received an reset else return 0; // writable } } } // relay data from read file descriptor to write data descriptor // will also save data to cache file descriptor is defined // return 0 if no error // return READ_ERROR, WRITE_ERROR if error // n is number of bytes to be relay int relay_data(int read_fd, rabuf *rab, int write_fd, int cache_fd, int n) { int s, total, byte, byte2, w, ntmp, ntmp2; char buf[MAX_LINE], *ptr; fd_set rset; struct timeval tv; if ( 0 > read_fd ) return E_SOCK_READ; if ( 0 > write_fd ) return E_SOCK_WRITE; // write read ahead buffered data to write fd if there is any if ( NULL != rab ) { if ( rab->n > 0) { if ( n > 0) { ntmp = min(rab->n, n);} else { ntmp = rab->n; } ntmp2 = ntmp; while ( ntmp != (w = write(write_fd, rab->p, ntmp)) ) { if ( (w > 0) && (w < ntmp) ) { rab->p += w ; ntmp -= w ; continue ; } else if ( w <= 0 && EINTR == errno ) continue ; else if ( w <= 0 && EWOULDBLOCK == errno ) { if ( wait_writable(write_fd) ) return E_SOCK_WRITE; continue ; } else return E_SOCK_WRITE ; } // will try to write data to cache if cache_fd_ptr is not NULL // noted we don't wait_write, because file handle is always ready // if anything fail, set cache_fd to -1 if ( 0 <= cache_fd ) { while ( ntmp2 != (w = write(cache_fd, rab->p, ntmp2)) ) { if ( (w > 0) && (w < ntmp2) ) { rab->p += w ; ntmp2 -= w ; continue ; } else if ( w <= 0 && EINTR == errno ) { continue ; } else { cache_fd = -1 ; break ; } } } rab->n -= ntmp; if ( rab->n <= 0 ) init_rab(rab); if ( n > 0 ) { n -= ntmp; if ( n <= 0 ) return 0; } } } // if n < 0, loop until EOF for ( total = 0 ; total < n || n < 0 ; total += byte ) { FD_ZERO(&rset); FD_SET(read_fd, &rset); // init tv everytime, because Linux will modify tv on return tv.tv_sec = SOCKET_TIMEOUT; tv.tv_usec = 0; while ( 0 >= (s=select(read_fd+1, &rset, NULL, NULL, &tv)) ) { if ( 0 == s ) return -1; // time out else if ( EINTR == errno ) continue; // interrupted else return -1; // other errors } // here, select return > 0 if ( FD_ISSET(read_fd, &rset) ) byte = read( read_fd, buf, (n < 0) ? (MAX_LINE-1) : min(MAX_LINE-1, n-total) ); else return -1; // impossible, fd is the only thing in rset // error if ( byte < 0 ) { // if error due to interrupt if ( EINTR == errno ) { byte = 0; continue; } else return E_SOCK_READ ; } // no more data else if ( 0 == byte ) { if ( n < 0 ) return 0; // this case should not be possible else return E_SOCK_READ; // I am sure I still have some data to read } // normal case, send data to write_fd, and then continue to loop else { ptr = buf; byte2 = byte; while ( byte2 != (w = write(write_fd, ptr, byte2)) ) { if ( (w > 0) && (w < byte2) ) { ptr += w ; byte2 -= w ; continue ; } else if ( (w <= 0) && (EINTR == errno) ) continue ; else if ( (w <= 0) && (EWOULDBLOCK == errno) ) { if ( wait_writable(write_fd) ) return E_SOCK_WRITE; continue ; } else return E_SOCK_WRITE ; } // will try to write data to cache if cache_fd_ptr is not NULL // if anything fail, set cache_fd to -1 if ( 0 <= cache_fd ) { ptr = buf; byte2 = byte; while ( byte2 != (w = write(cache_fd, ptr, byte2)) ) { if ( w > 0 && w < byte2 ) { ptr += w ; byte2 -= w ; continue ; } else if ( w <= 0 && EINTR == errno ) { continue ; } else { cache_fd = -1 ; break ; } } } } } return 0; } // relay data from both direction // only use in CONNECT // return 0 if no error // return READ_ERROR, WRITE_ERROR if error int relay_2way(int fd1, int fd2) { int s, total, byte, byte2, w; int rfd, wfd; char buf[MAX_LINE], *ptr; fd_set prset, rset; struct timeval tv; if ( 0 > fd1 ) return -1; if ( 0 > fd2 ) return -1; // init variable that will be used in for() loop FD_ZERO(&prset); FD_SET(fd1, &prset); FD_SET(fd2, &prset); while (1) { memcpy(&rset, &prset, sizeof(fd_set)); tv.tv_sec = SOCKET_TIMEOUT; tv.tv_usec = 0; while ( 0 >= (s=select(max(fd1, fd2)+1, &rset, NULL, NULL, &tv)) ) { if ( 0 == s ) return -1; // time out else if ( EINTR == errno ) continue; // interrupted else return -1; // other errors } /* select return > 0, check which fd */ if ( FD_ISSET(fd1, &rset) ) { rfd = fd1; wfd = fd2; } else if ( FD_ISSET(fd2, &rset) ) { rfd = fd2; wfd = fd1; } else return -1; /* read in data */ byte = read(rfd, buf, MAX_LINE-1); // error ?? if ( byte < 0 ) { // if error due to interrupt if ( EINTR == errno ) { byte = 0; continue; } else { syslog(LOG_ERR, "read(): %s\n", strerror(errno)); return E_SOCK_READ ; } } // no more data else if ( 0 == byte ) { return 0; } // normal case, send data to wfd, and then continue to loop else { ptr = buf; byte2 = byte; while ( byte2 != (w = write(wfd, ptr, byte2)) ) { if ( (w > 0) && (w < byte2) ) { ptr += w ; byte2 -= w ; continue ; } else if ( w <= 0 && EINTR == errno ) continue ; else if ( w <= 0 && EWOULDBLOCK == errno ) { if ( wait_writable(wfd) ) return E_SOCK_WRITE; continue ; } else return E_SOCK_WRITE ; } } } return 0; } // set input file descriptor to be non-blocking // return 0 if OK, -1 if failed int set_nonblock(int fd) { int flag; if (0 > fd) return -1; // get file flag while( -1 == (flag = fcntl(fd, F_GETFL, 0)) ) { if ( EINTR == errno ) continue; else return -1; } // set file flag while ( -1 == fcntl(fd, F_SETFL, flag | O_NONBLOCK) ) { if ( EINTR == errno ) continue; else return -1; } return 0; } // set input file descriptor to be blocking // return 0 if OK, -1 if failed int set_block(int fd) { int flag; if (0 > fd) return -1; // get file flag while( -1 == (flag = fcntl(fd, F_GETFL, 0)) ) { if ( EINTR == errno ) continue; else return -1; } // set file flag flag &= ~O_NONBLOCK; while ( -1 == fcntl(fd, F_SETFL, flag) ) { if ( EINTR == errno ) continue; else return -1; } return 0; } // non-blocking connect // the socket has to be non-blocking before call // return 0 if OK // return -1 if error int nonb_connect(int fd, struct sockaddr_in *addr, int addr_len) { fd_set rset, wset; int n, s, error; struct timeval tv; n = connect(fd, (struct sockaddr *)addr, addr_len); if ( 0 == n ) { return 0; } else { if (EINPROGRESS != errno ) return -1; } tv.tv_sec = CONNECT_TIMEOUT; tv.tv_usec = 0; FD_ZERO(&rset); FD_SET(fd, &rset); wset = rset; while ( 0 >= (s=select(fd+1, &rset, &wset, NULL, &tv)) ) { if ( 0 == s ) return -1; // time out else if ( EINTR == errno ) continue; // interrupted else return -1; // other errors } // here, select return > 0 if ( FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset) ) { // FD_ISSET don't really mean connect is successful // we have to check for getsockopt SO_ERROR, 0 means OK, others are error code n = sizeof(int); if ( getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &n) ) { errno=error; return -1; } else { if ( error ) { errno=error; return -1; } else { return 0; } } } else { return -1; } return -1; // impossbile } /* try to close the socket politely */ int slow_close(int fd) { char buf[MAX_LINE]; time_t start, now ; fd_set rset; struct timeval tv; int s, byte; /* if shutdown return error, just close the socket */ if ( shutdown(fd, SHUT_WR) ) { return close(fd); } /* shutdown OK */ /* still have to read until the remote end send a FIN back to me */ /* the time allowed for the remote end to send data */ /* after I send FIN is SHUTDOWN_DELAY */ /* after that time, will simply close the socket */ else { while(-1) { tv.tv_sec = SHUTDOWN_DELAY; tv.tv_usec = 0; FD_ZERO(&rset); FD_SET(fd, &rset); start = time( (time_t *)NULL ); while ( 0 >= (s=select(fd+1, &rset, NULL, NULL, &tv)) ) { /* interrupted */ if ( 0 != s && EINTR == errno ) continue; /* time out or any other errors */ else return close(fd); } /* here, select return > 0 */ if ( FD_ISSET(fd, &rset) ) { byte = read(fd, buf, sizeof(buf)); if ( 0 < byte ) { /* time out ?? */ now = time( (time_t *)NULL ); if ( SHUTDOWN_DELAY < (now - start) ) return close(fd); /* if not, then read again */ continue; } /* probably byte = 0, safe to close */ return close(fd); } /* unknow select condition */ else { return close(fd); } } } /* you should not reach this */ return close(fd); } // create, bind and listen to a socket // input desired listen sockaddr // output listen fd // exit(-1) if error int Listen_srv(struct sockaddr_in *srv_addr) { int fd; int sockopt=1; struct linger linger_val; linger_val.l_onoff = 1; linger_val.l_linger = LINGER_TIMEOUT; /* get a socket and fill in the address form */ if ( 0 > (fd = socket(PF_INET, SOCK_STREAM, 0 )) ) { fprintf(stderr, "socket(): %s\n", strerror(errno)); exit(-1); } if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&sockopt, sizeof(int)) ) { fprintf(stderr, "setsockopt(SO_REUSERADDR): %s\n", strerror(errno)); exit(-1); } /* enable linger close */ if ( setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&linger_val, sizeof(struct linger)) ) { fprintf(stderr, "setsockopt(SO_LINGER): %s\n", strerror(errno)); exit(-1); } if ( 0 > (bind(fd, (struct sockaddr *)srv_addr, sizeof(struct sockaddr_in))) ) { fprintf(stderr, "bind(): %s\n", strerror(errno)); exit(-1); } if ( 0 != set_nonblock(fd) ) { fprintf(stderr, "set_nonblock(): %s\n", strerror(errno)); exit(-1); } // backlog is 5 if ( 0 > listen(fd, 5) ) { fprintf(stderr, "listen(): %s\n", strerror(errno)); exit(-1); } return fd; } ////////////////////////////////////////////////////////////////////// // String/PATH manipulation ////////////////////////////////////////////////////////////////////// /* this code is modified from libhttpd.c from Jef Poskanzer */ int hex2int (char c) { if ( c >= '0' && c <= '9' ) return (c - '0'); else if ( c >= 'a' && c <= 'z' ) return (c - 'a' + 10); else if ( c >= 'A' && c <= 'Z' ) return (c - 'A' + 10); else return 0; // error } /* this code is modified from libhttpd.c from Jef Poskanzer */ void de_hexcode (char *str) { char *src, *dst; for ( src = str, dst = str ; *src != '\0' ; ++src, ++dst ) { if ( *src=='%' && isxdigit(*(src+1)) && isxdigit(*(src+2)) ) { *dst = hex2int(*(src+1))*16 + hex2int(*(src+2)); src += 2; } else { *dst = *src; } } *dst = '\0'; } /* encode the URL in hexcode format */ /* return 0 if OK, -1 if error */ int hex_encode(char *str, int max_buf) { char hex_str[]="0123456789ABCDEF"; char buf[MAX_LINE]; char *safe_ptr; unsigned int h1, h2; int i, len; if ( sizeof(buf) < max_buf ) return -1; /* copy data to buffer first */ strncpy(buf, str, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; /* save safe_ptr */ /* max_buf - 1 -3 becuase %hh\0 */ safe_ptr = str + max_buf - 1 - 3; len = strlen(buf); for ( i=0; i<=len && safe_ptr>str ; i++ ) { if ( buf[i] == '\0' ) { *str = '\0'; return 0; } else if ( (buf[i] > 32) && (buf[i] < 127) ) { if ( 2 > max_buf ) return -1; else max_buf -= 1; *str = buf[i]; str++; } else { if ( 4 > max_buf ) return -1; else max_buf -= 3; h1 = (unsigned int)buf[i] / 16; h2 = (unsigned int)buf[i] % 16; *str = '%'; str++; *str = hex_str[h1] ; str++; *str = hex_str[h2] ; str++; } } return -1; } /* this code is modified from libhttpd.c from Jef Poskanzer */ void de_dotdot (char *str) { char *dot_ptr, *ptr; int len; /* extract any xxx/../ sequences */ while ( NULL != (dot_ptr = strstr(str, "/../")) ) { for ( ptr = dot_ptr - 1; ptr >= str && *ptr != '/'; --ptr ) continue; /* ptr < str or *ptr = '/' */ if ( ptr < str ) break; // finished strcpy(ptr, dot_ptr + 3); } /* extract any xxx/.. at the end */ if ( len = strlen(str) >= 3 ) { ptr = str + len - 3; if ( *ptr == '/' && *(ptr+1) == '.' && *(ptr+2) == '.' ) *ptr = '\0'; } } int expand_link (char *path, int link) { char buf[MAX_LINE]; char new_path[MAX_LINE]; char temp[MAX_LINE]; char *ptr, *ptr2; int path_len, errcode; struct stat fs; buf[sizeof(buf)-1] = '\0'; new_path[sizeof(new_path)-1] = '\0'; temp[sizeof(temp)-1] = '\0'; /* it is sured path would not be larger than buf */ strncpy(buf, path, sizeof(buf)); buf[sizeof(buf)-1]='\0'; // do this for safety ptr = buf; while ( NULL != (ptr=strchr(ptr+1, '/')) ) { /* 1. ensure we are not point to the the absolute root "/" 2. [/] [a] [b] [c] [/] [e] [f] [g] [/] [i] [.] [h] [t] [m] 10 11 12 13 14 15 16 17 18 19..... buf ptr */ *ptr = '\0'; if ( 0 != lstat(buf, &fs) ) return NOT_FOUND; /* yes it is a link */ if ( S_ISLNK(fs.st_mode) ) goto found_link; /* no it is a normal directory */ else *ptr='/'; } /* is the whole file a link? */ if ( 0 != lstat(buf, &fs) ) return NOT_FOUND; /* yes it is a link */ if ( S_ISLNK(fs.st_mode) ) goto found_link; /* no file or directory link */ return 0; found_link: /* too many links? */ if ( link == 0 ) return BAD_REQUEST; /* expand the link */ path_len = readlink(buf, new_path, sizeof(buf)); if ( path_len >= sizeof(buf) || path_len <= 0 ) return BAD_REQUEST; /* readlink does not provide '\0' */ new_path[path_len]='\0'; /* no need to replace if ptr=NULL, ie. the whole file is link */ if ( NULL != ptr ) { ptr++; /* replace absolute link */ if ( '/' == new_path[0] ) { if( strlen(new_path) + 1 + strlen(ptr+1) >= sizeof(temp) ) return BAD_REQUEST; strcpy(temp, new_path); strcat(temp, "/"); strcat(temp, ptr); temp[sizeof(temp)-1]='\0'; // for safety } /* replace if relative link */ else { if( strlen(buf) + 1 + strlen(new_path) + strlen(ptr) >= sizeof(temp) ) return BAD_REQUEST; strcpy(temp, buf); strcat(temp, "/"); strcat(temp, new_path); strcat(temp, ptr); temp[sizeof(temp)-1]='\0'; // for safety de_dotdot(temp); } } /* whole file is a link file */ /* only need to do if it is a relative link */ else { if ( '/' == new_path[0] ) { strcpy(temp, new_path); } else { ptr = strrchr(buf, '/'); *ptr = '\0'; if( strlen(buf) + 1 + strlen(new_path) >= sizeof(temp) ) return BAD_REQUEST; strcpy(temp, buf); strcat(temp, "/"); strcat(temp, new_path); temp[sizeof(temp)-1]='\0'; // for safety de_dotdot(temp); } } errcode = expand_link(temp, --link) ; // recursive call if ( errcode ) return errcode; strcpy(path, temp); // return updated path if (debug) (LOG_DEBUG, "Expanded-path: %s\n", path); return 0; } /* search the direct to see if there is an index file */ /* I am not handling any error here, is there is any error, simply quit */ /* return 1 if found any index file */ int search_index (http_header *hd) { /* directory handle */ DIR *dir; struct dirent *dir_entry; struct stat fs; if ( 0 != stat(hd->path, &fs) ) return 0; /* is directory world readable? */ if ( !(fs.st_mode & S_IROTH) ) return 0; /* we are sure hd->path is a path name */ if ( NULL == (dir = opendir(hd->path)) ) return 0; while ( NULL != (dir_entry = readdir(dir)) ) { if ( !strncmp(dir_entry->d_name, "index.html", 10) || !strncmp(dir_entry->d_name, "index.htm", 9) || !strncmp(dir_entry->d_name, "default.html", 10) || !strncmp(dir_entry->d_name, "default.htm", 9) ) { /* protect from buffer overflow */ if ( strlen(hd->path) + strlen(dir_entry->d_name) + 1 < MAX_LINE -1 ) { /* apend "/" at the back, extra "/" does not hurt */ strcat( hd->path, "/" ); strcat( hd->path, dir_entry->d_name ); return 1; } } } return 0; } /* decode authorization line */ /* note assuem scheme, username, passwd are all MAX_AUTH */ /* return -1 if deocde failed, otherwise, return 0 */ /* will force all three arrays to plain english if hd_chk enabled */ /* note scheme and username are array while passwd is pointer */ int htpasswd_decode(char *line, char *username, char *passwd, int hd_chk) { char coded[MAX_AUTH]; char decoded[MAX_AUTH]; char scheme[MAX_AUTH]; char *ptr; /* all buffers are MAX_AUTH long */ if ( strlen(line) >= MAX_AUTH ) return -1; /* Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== */ if ( sscanf(line, "%s %s", scheme, coded) != 2 ) return -1; /* sorry, I only support basic scheme */ if ( strcasecmp(scheme, "basic") ) return -1; if ( b64_decode(coded, username, sizeof(coded)) < 0 ) return -1; /* make sure username is null terminated */ /* assume username is MAX_AUTH */ ptr = memchr(username, '\0', MAX_AUTH); if ( NULL == ptr ) return -1; /* extract name and passwd */ /* it should be in the form of name:passwd */ passwd = strchr(username, ':'); if ( NULL == passwd ) return -1; *passwd = '\0'; passwd++; if ( hd_chk ) { if ( !non_ascii(username) && !non_ascii(passwd) ) return 0; else return -1; } else { return 0; } } char *check_mime_type (char *path) { char *ext; ext = strrchr(path, '.'); if (NULL == ext) return "text/plain"; if ( strlen(ext) <= 5 ) { if ( !strcasecmp(ext, ".html") ) return "text/html"; if ( !strcasecmp(ext, ".htm") ) return "text/html"; if ( !strcasecmp(ext, ".css") ) return "text/css"; if ( !strcasecmp(ext, ".txt") ) return "text/plain"; if ( !strcasecmp(ext, ".jpeg") ) return "image/jpeg"; if ( !strcasecmp(ext, ".jpg") ) return "image/jpeg"; if ( !strcasecmp(ext, ".gif") ) return "image/gif"; } /* default mime type */ return "application/octet-stream"; } char *mode2str(mode_t mode, char *string, int i) { char attr[5]="----"; if ( 5 > i ) strcpy(string, ""); if ( S_ISDIR(mode) ) attr[0] = 'd'; if ( S_ISLNK(mode) ) attr[0] = 'l'; if (mode & S_IROTH) attr[1] = 'r'; if (mode & S_IWOTH) attr[2] = 'w'; if (mode & S_IXOTH) attr[3] = 'x'; return strcpy(string, attr); } int is_dir(char *path) { struct stat fs; if ( 0 != stat(path, &fs) ) return 0; return ( S_ISDIR(fs.st_mode) ) ? 1 : 0; } int is_cgi_path(char *path) { return 0; } // resolv the remote host IP // return 0 if OK, http errcode if error int resolv_remote(http_header *hd) { int i, rport; struct hostent *host_addr; char **ptr; /* if hd->forward is NULL, then the destionation address is defined in server config */ /* otherwise, hd->forward has already set by policy route thru eval_dt() */ if ( NULL == hd->forward ) { hd->forward = &(hd->sc->forward); } /* if address is 0.0.0.0, then it is auto forward mode */ /* otherwise, it is fix forward mode */ if ( 0 != hd->forward->sin_addr.s_addr ) { memcpy(&(hd->remote_addr), hd->forward, sizeof(struct sockaddr_in)); return 0; } // auto forward, get remote IP by gethostbyname // also have to make sure the requested IP will not loopback to me else { // if host is empty or too long, return error if ( !strncasecmp(hd->host, "", 1) || strlen(hd->host) > MAX_HOST ) return BAD_GATEWAY; // gethostbyname can be domain name or IPv4 hd->host_ent = gethostbyname(hd->host); host_addr = hd->host_ent; if ( NULL == host_addr ) { syslog(LOG_ERR, "gethostbyname(%s): %s\n", hd->host, strerror(h_errno)); return BAD_GATEWAY; } // dummy check to make sure we are dealing with IPv4 if ( AF_INET != host_addr->h_addrtype || 4 != host_addr->h_length ) { syslog(LOG_ERR, "gethostbyname(%s): return not IPv4\n", hd->host); return BAD_GATEWAY; } // here host_addr is valid, // but we still need to check if it will loopback to me rport = htons(hd->port); ptr = host_addr->h_addr_list; while ( '\0' != *ptr ) { for ( i=0; iip.sin_port ) { if ( !memcmp( *ptr, &((cfg.sclist[i])->ip.sin_addr), 4 ) ) { return BAD_GATEWAY; } } } ptr++; } // OK, everything seems to be clean return 0; } } // find the extension part of input path // the string after the last "/" and "." // the extension part is returned // return a pointer pointing to the last element of input string // if no ext is found char *find_ext(char *fname) { char *ptr; int len; len = strlen(fname); // found the LAST occurence of "/" // in fact, there should be at least one "/" if ( NULL == (ptr = strrchr(fname, '/')) ) return (fname+len); // found the FIRST occurence of "." starting from ptr1 // i.e. the base filename could only have one "." // no "." found mean no extension or dir browsing if ( NULL == (ptr = strchr(ptr, '.')) ) return (fname+len); // not include the dot return ptr+1; } // check if the extension is only [0-9a-zA-Z] int invalid_ext(char *ext) { char *ptr; // always valid if ext is NULL if ( NULL == ext ) return 0; ptr = ext; while ( '\0' != *ptr ) { if ( !isalnum(*ptr) ) return 1; ptr++; } return 0; } // check if the string is in ASCII char only // range int non_ascii(char *fname) { char *ptr; ptr = fname; while ( '\0' != *ptr ) { // allowed char set is // between 0d32 (space) to 0d126 (~) if ( *ptr < ' ' || *ptr > '~' ) return 1; ptr++; } return 0; } int is_hidden(char *fname) { if ( !strncasecmp(fname, ".", 1) || (strstr(fname, "/.") != NULL) ) return 1; else return 0; } // return true if "/..." is found in the string // "./" -> current dir // "../" -> parent dir // ".../" -> parent of parent dir (only for Windowz machine) // "..../" -> parent of parent of parent dir (only for Windowz machine) int is_dotdotdot(char *fname) { if ( strstr(fname, "...") != NULL ) return 1; else return 0; } // check if the input string contains metachars int has_meta(char *string, char *meta) { // check input string if ( NULL == string || NULL == meta ) return 0; if ( NULL == strpbrk(string, meta) ) return 0; else return 1; } int is_valid (char *text, char *pattern) { regex_t regex; int comp_reg, mismatch; comp_reg=regcomp (& regex, pattern, REG_EXTENDED|REG_NOSUB|REG_ICASE); mismatch= regexec (& regex, text, 1, NULL, 0); regfree (& regex); return ! mismatch; } // hash the "host:port/path?query" to file cache file path // this routine use md5 hashing function int hash(http_header *hd) { char temp[3]; char number[6]; int i; MD5_CTX md5; // total size, path_to_cache_dir + '/' + hash_data + ".header" + '\0' // hash data is 16 bytes if ( (strlen(cfg.cache_dir) + 1 + 16 + 7 ) >= (sizeof(hd->hash) - 1) ) return -1; strcpy(hd->hash, cfg.cache_dir); strcat(hd->hash, "/"); // append "/" for safety, double "/" doesn't hurt MD5Init( &md5 ); MD5Update( &md5, hd->host, strlen(hd->host) ); i = snprintf(number, 5, "%5d", hd->port); MD5Update( &md5, number, i ); MD5Update( &md5, hd->path, strlen(hd->path) ); // query is optional, test it before hash if ( NULL != hd->query ) MD5Update(&md5, hd->query, strlen(hd->query)); MD5Final( &md5 ); for (i = 0; i < 16; i++) { sprintf(temp, "%02x", md5.digest[i]); strcat(hd->hash, temp); // buffer size checked, strcat safe } i = strlen(hd->hash); hd->hashptr = &hd->hash[i]; strcat(hd->hash, ".header"); return 0; } time_t decode_time(char *line) { char wkday[16], month[16]; int date, year, hour, minute, second; int leap, day; // RFC 1123 or 822 // Sun, 06 Nov 1994 08:49:37 GMT if ( sscanf( line, "%3[^ ,], %2d %3s %4d %2d:%2d:%2d GMT", wkday, &date, month, &year, &hour, &minute, &second ) == 7 ) { ; } // RFC 1036 or 850 // Sunday, 06-Nov-94 08:49:37 GMT // note longest wkday is "Wednesday" = 9 char else if ( sscanf( line, "%9[^ ,], %2d-%3[^ -]-%2d %2d:%2d:%2d GMT", wkday, &date, month, &year, &hour, &minute, &second ) == 7 ) { if ( year > 70 ) year += 1900; else year += 2000; } // ANSI C asctime() format // Sun Nov 6 08:49:37 1994 else if ( sscanf( line, "%3s %3s %d %2d:%2d:%2d %4d", wkday, month, &date, &hour, &minute, &second, &year ) == 7 ) { ; } // uknown, return error else { return ((time_t)-1); } // well basically, I don't check wkday // check mont, date, hour, minute, second, and year if ( second < 0 || second >= 60 ) return ((time_t)-1); if ( minute < 0 || minute >= 60 ) return ((time_t)-1); if ( hour < 0 || hour >= 24 ) return ((time_t)-1); if ( date < 1 || date > 31 ) return ((time_t)-1); // is this reasonable?? if ( year < 1980 || year > 2020 ) return ((time_t)-1); if ( !strncasecmp(month, "jan", 3) ) day = 0; else if ( !strncasecmp(month, "feb", 3) ) day = 31; else if ( !strncasecmp(month, "mar", 3) ) day = 59; else if ( !strncasecmp(month, "apr", 3) ) day = 90; else if ( !strncasecmp(month, "may", 3) ) day = 120; else if ( !strncasecmp(month, "jun", 3) ) day = 151; else if ( !strncasecmp(month, "jul", 3) ) day = 181; else if ( !strncasecmp(month, "aug", 3) ) day = 212; else if ( !strncasecmp(month, "sep", 3) ) day = 243; else if ( !strncasecmp(month, "oct", 3) ) day = 273; else if ( !strncasecmp(month, "nov", 3) ) day = 304; else if ( !strncasecmp(month, "dec", 3) ) day = 334; else return ((time_t)-1); // check for leap year, note 1968 is a leap year leap = (year - 1968) >> 2 ; if ( ((year - 1968) % 4) == 0 ) { // this year is a leap year if ( day < 59 ) day += leap - 1; else day += leap; } else { // this year is not a leap year day += leap; } // ok, return time_t sine 1970 Jan 1 00:00:00 return ( (time_t) ( (year-1970)*365*24*60*60 + (day+date-1)*24*60*60 + hour*60*60 + minute*60 + second ) ); } // check if the file is in the cache int is_cached(http_header *hd) { struct stat fs; // noted cache.header file is check instead of cache file // cache header file will be newer if remote http server return NOT_MODIFIED *(hd->hashptr) = '.'; if ( 0 != stat(hd->hash, &fs) ) { return 0; } else { if ( (fs.st_mode & S_IFREG) && (fs.st_mode & S_IRUSR) ) { if ( hd->if_mod_since != (time_t)-1 ) { // the client issue if-modified-since, probably due to reload // cache_fast_tout is default 5 min if ( fs.st_mtime + cfg.cache_fast_tout >= hd->if_mod_since ) return 1; else return 0; } else { // no if-modified-since, cache_timeout is default 24hr if ( fs.st_mtime + cfg.cache_tout >= hd->if_mod_since ) return 1; else return 0; } } else return 0; } } http_srv2(http_header *hd) { int byte; char *line; set_nonblock(hd->fd); syslog(LOG_ERR, "Welcome to Testing Server\n"); while ( NULL != (line=read_line(hd->fd, &hd->fd_rab)) ) { syslog(LOG_ERR, "Receive something\n"); byte = strlen(line); lz_write(hd->fd, line, byte, &hd->fd_lzb); lz_write(hd->fd, "\r\n", 2, &hd->fd_lzb); lz_flush(hd->fd, &hd->fd_lzb); } syslog(LOG_ERR, "Exiting Testing Server\n"); exit(0); } ////////////////////////////////////////////////////////////////////// // Main Loop ////////////////////////////////////////////////////////////////////// void show_usage(char *name) { fprintf(stderr, "\n"); fprintf(stderr, "%s %s by Sam NG \n", THIS_NAME, THIS_VERSION); fprintf(stderr, "A secure HTTP proxy \n"); fprintf(stderr, "Usage: %s [options] [file] \n", name); fprintf(stderr, " \n"); fprintf(stderr, " -c cfg_file Read config file from [cfg_file]\n"); fprintf(stderr, " Default is /etc/twhttpd.cfg \n"); fprintf(stderr, " -V Show the version of this program\n"); fprintf(stderr, " -h This help messages \n"); fprintf(stderr, "\n"); exit(-1); } void show_version(char *name) { fprintf(stdout, "\n"); fprintf(stdout, "Secure HTTP Proxy: %s version %s\n", THIS_NAME, THIS_VERSION); exit(0); } int main(int argc, char *argv[]) { pid_t rc; int opt, i, s, len, srv_fd, maxfd, fork_fail; char *config_file; fd_set srv_set, perm_set; struct sigaction sigact; http_header hd; // getopt; debug = 0; config_file = DEFAULT_CONFIG_FILE; while( -1 != (opt=getopt(argc, argv, "c:Vh")) ) { switch(opt) { case 'c': config_file = strdup(optarg); break; case 'h': show_usage(argv[0]); case 'V': show_version(argv[0]); case ':': fprintf(stderr, "option need a value\n"); show_usage(argv[0]); case '?': fprintf(stderr, "unkown option\n"); show_usage(argv[0]); } } yyin = fopen(config_file, "r"); if ( NULL == yyin ) { fprintf(stderr, "fopen(%s): %s\n", config_file, strerror(errno)); exit(-1); } init_gen_cfg(&cfg); init_srv_cfg(&tmp_sc); yyparse(); fclose(yyin); /* open syslog , log with PID and log to console if syslog not available */ openlog(THIS_NAME, LOG_PID|LOG_CONS, LOG_DAEMON); syslog(LOG_ERR, "%s started\n", THIS_NAME); /* Catch defunct children. */ sigact.sa_handler = handle_sigchld; if ( sigemptyset(&sigact.sa_mask) ) { fprintf(stderr, "sigemptyset(): %s\n", strerror(errno)); exit(-1); } sigact.sa_flags = 0; if ( sigaction(SIGCHLD, &sigact, NULL) ) { fprintf(stderr, "sigaction(): %s\n", strerror(errno)); exit(-1); } /* Catch SIGPIPE */ sigact.sa_handler = handle_sigpipe; if ( sigaction(SIGPIPE, &sigact, NULL) ) { fprintf(stderr, "sigaction(): %s\n", strerror(errno)); exit(-1); } // create and bind all server ip FD_ZERO(&perm_set); maxfd = -1; for ( i = 0; i < MAX_SC ; i++ ) { if ( NULL == cfg.sclist[i] ) break; // note Listen_srv will exit(-1) if error srv_fd = Listen_srv(&(cfg.sclist[i]->ip)); cfg.sclist[i]->fd = srv_fd; maxfd = max(maxfd, srv_fd); FD_SET(srv_fd, &perm_set); } // from now on, we don't display any more message on stderror daemonize(); while (1) { // init http header init_http_header(&hd); len = sizeof(struct sockaddr_in); // fd_set will be cleared very time after select // therefore, it have to be backup memcpy(&srv_set, &perm_set, sizeof(fd_set)); // select without timeout while ( 0 >= (s=select(maxfd+1, &srv_set, NULL, NULL, NULL)) ) { // s=0 means timeout, errno=EINTR means interrupted if ( (0 == s) || (EINTR == errno) ) { continue; } // other error, don't know what is it, and don't know how to handle else { syslog(LOG_ERR, "select(): %s\n", strerror(errno)); exit(-1); } } // here, select return > 0 for ( i = 0; i < MAX_SC; i++ ) { if ( NULL == cfg.sclist[i] ) break; if ( FD_ISSET((cfg.sclist[i])->fd, &srv_set) ) { // active fd found hd.fd = accept((cfg.sclist[i])->fd, (struct sockaddr *)&hd.clt_addr, &len); // note the socket is non-blocking // if the client RST to my listen socket // accept would return -1 and errno = EWOULDBLOCK if ( hd.fd < 0 ) break; // set server config to http_header hd.sc = cfg.sclist[i]; if ( ( rc = fork() ) < 0 ) { /* fork failed */ syslog(LOG_ERR, "fork(): %s\n", strerror(errno)); exit(-1); } // child process else if ( rc ==0 ) { close(srv_fd); http_srv(&hd); exit(0); } // parent process else { fork_fail=0; close(hd.fd); } break; } /* end of FD_ISSET */ } } /* end of while() */ // well you should not reach this exit(-1); }