/* * httpd -- Simple httpd-server * Copyright (c) 1995 Tero Kivinen * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * TERO KIVINEN ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. TERO KIVINEN DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * */ /* * Program: simple httpd-server * $Source: /iki/src/simple-httpd/RCS/httpd.c,v $ * $Author: kivinen $ * * (C) Tero Kivinen 1995 * * Creation : 23:47 Mar 23 1995 kivinen * Last Modification : 21:28 Mar 5 2012 kivinen * Last check in : $Date: 2019/01/23 17:27:31 $ * Revision number : $Revision: 1.37 $ * State : $State: Exp $ * Version : 1.922 * Edit time : 555 min * * Description : Simple http-server * * * $Log: httpd.c,v $ * Revision 1.37 2019/01/23 17:27:31 kivinen * Added __linux__ ifdefs around sin6_len, and added time.h. * * Revision 1.36 2014/04/18 20:29:35 kivinen * Added .ico -> text/plain to mime mapping. * * Revision 1.35 2012/03/05 19:28:59 kivinen * Added one more IPv6 / IPv4 logging. * * Revision 1.34 2012/03/05 19:17:33 kivinen * Added logging whether the server is IPv4 or IPv6 server. * * Revision 1.33 2011/12/09 13:50:20 kivinen * Fixed edit time to be correct again. * * Revision 1.32 2011/12/09 13:33:13 kivinen * Added check that final Location url cannot overflow the buffer * reserved for it. * * Revision 1.31 2011/12/08 13:55:56 kivinen * Added checks for too deep tree hierarchies. Removed some * commented out code. Added check for too long local_root_url * string. * * Revision 1.30 2011/12/07 13:52:25 kivinen * Removed public_html code. * * Revision 1.29 2011/04/06 11:01:50 kivinen * Added ipv6 support and -6 option. * * Revision 1.28 2010/09/08 05:40:53 kivinen * Added special case for /iki/faq/ to support two levels of * forwardings. (i.e. instead of always checking for * /iki/foobar.html, we also check /iki/faq/foobar.html style * forwardings). * * Revision 1.27 2010/02/17 13:42:19 kivinen * Updated address. * * Revision 1.26 2009/12/16 19:50:56 kivinen * Changed STAT_TIME from 5 minutes to minute. * * Revision 1.25 2006/11/24 12:45:36 kivinen * Fixed opendir + telldir + closedir + opendir + seekdir to so * it willnot reopen the directory, and do not do extra seeks * etc. The seekdir cannot be used after directory has been * closed. Changed most of strings to unsigned char's to remove * warnings. * * Revision 1.24 2006/11/02 18:57:04 kivinen * Added css to known mime types. * * Revision 1.23 2006/10/05 13:32:27 kivinen * Added charset parameter to text/plain and text/html. * * Revision 1.22 2002/11/12 18:16:57 kivinen * Added code that will exit if connections do not finish in 30 * seconds after quit. * * Revision 1.21 2002/10/06 13:32:04 kivinen * Added code that will raise the rlimit_nofile to max. * * Revision 1.20 2002/10/06 13:06:47 kivinen * Added some memory allocation checks. Changed version number to * 1.2. Optimized the code to use only one write instead of two * when sending reply. * * Revision 1.19 1998/12/03 20:13:55 kivinen * Added ignoring of sigpipe. * * Revision 1.18 1997/12/06 12:06:22 kivinen * Fixed 2 bugs. * * Revision 1.17 1997/10/09 03:14:19 kivinen * Fixed bug reported by Jon Wickstrom about weekday being off by * one. * * Revision 1.16 1997/05/13 16:08:57 kivinen * Added changes from liw for solaris. * * Revision 1.15 1996/11/19 21:05:13 kivinen * Added post command to be processed just like get. * * Revision 1.14 1996/09/13 20:18:08 kivinen * Added status command. * * Revision 1.13 1996/08/21 14:09:46 kivinen * getservbyname returns port number in network byte order, * removed htons from using servent->s_port. * * Revision 1.12 1996/07/15 16:14:51 kivinen * Added printing of errno in case of errors. * If read fails set write_state to STATE_ERROR too, so it don't * try to write to socket. * * Revision 1.11 1996/07/15 15:59:45 kivinen * Changed to use LOG_LOCAL0 instead of LOG_DAEMON. * * Revision 1.10 1996/07/11 03:06:08 kivinen * Fixed bug in last_info_hour. * * Revision 1.9 1996/07/11 02:08:52 kivinen * Modified all times to use MINUTES and HOUR defines. * Added statistic output. Changed LOG_NOTICE to LOG_INFO and * added all statistics to be on level LOG_NOTICE. * * Revision 1.8 1996/03/01 14:36:08 kivinen * Added png. * * Revision 1.7 1995/12/14 19:28:32 kivinen * Fixed %-n.ns to %.ns. * * Revision 1.6 1995/11/29 06:48:32 kivinen * Increased command length to 2048, so now the headers can also * fit there. * Added READ_TIME_OUT that will tell when the request reads time * out when reading headers. * Added read_state, write_state and headers to connection_t. * Changed all %s to %-n.ns in syslogs. * Changed main loop so it will close socket only after all of * the request have been read from the socket. * * Revision 1.5 1995/11/16 18:00:43 kivinen * Added timeout code. * * Revision 1.4 1995/07/26 12:14:49 kivinen * Fixed bug in temporary redirection page generation. * * Revision 1.3 1995/07/20 04:14:51 kivinen * Raised BUF_LENGTH from 2048 to 3000, because it now must be * large enough for 2 URL's. * Moved setsockopt to correct place before bind. * Changed rfc850date to rfc1123date. * Added URI-field, renamed Date: to X-Date (Date must be current * date, and our date-field was the date when the page was * created in the memory). * Added url-decoding. * Changed protocol version check to check only HTTP/1. * * Revision 1.2 1995/07/16 16:30:32 kivinen * Fixed quit code. * * Revision 1.1 1995/07/16 11:45:07 kivinen * Created. * * * * * * */ /* * If you have any useful modifications or extensions please send them to * Tero.Kivinen@iki.fi */ /* Make sure this is big enough. */ #define FD_SETSIZE 1024 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __sgi__ #include #endif #include #include #ifdef __sun__ #include #define getdtablesize() 1024 #endif #undef DEBUG #undef NIKSULAROOT #undef SHADOWSROOT #define HTTPD_GID 80 #define HTTPD_UID 80 #define VERSION "SimpleHTTP/1.2" /* Local http-root */ #ifdef NIKSULAROOT #define LOCAL_ROOT_URL "http://nukkekoti.cs.hut.fi" #else #ifdef SHADOWSROOT #define LOCAL_ROOT_URL "http://shadows.cs.hut.fi" #else #define LOCAL_ROOT_URL "http://www.iki.fi" #endif #endif #define MINUTE (60) #define HOUR (MINUTE * 60) /* How long wait for the http connections to finish before exiting. */ #define QUIT_TIME 30 /* How long to keep the temporal pages in memory */ #define KEEP_TIME (10 * MINUTE) /* How often to check if we have temporal pages in memory we can throw away */ #define CHECK_TIME (MINUTE) /* This tells how often we try to stat files, to see if they have changed */ #define STAT_TIME (MINUTE) /* This tells long we wait for command */ #define KICK_TIME (2 * MINUTE) /* This tells long we wait for headers */ #define READ_TIME_OUT (2 * MINUTE) /* Max line length. Used to read data from redirections file. */ #define LINE_LENGTH 1024 /* Max command length. Maximum of this much is read from socket to find url */ #define COMMAND_LENGTH 2048 /* Maximum length of url. Must be larger or same as COMMAND_LENGTH, * LINE_LENGTH, and PATH_MAX. */ #define URL_LENGTH 2048 /* Misc buffer length. Must be at least URL_LENGTH * 2 + TIME_LENGTH + * 300 (misc headers) */ #define BUF_LENGTH 5000 /* Length of time buffers "Weekday, dd-Mon-95 hh:mm:ss GMT" = 34 chars */ #define TIME_LENGTH 64 /* Length of ipaddress buffer "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255" = 45 chars */ #define ADDR_LENGTH 50 /* Listen backlog value */ #define LISTEN_BACKLOG 10 /* Debug output */ #ifdef DEBUG #define DPRINT(x) dprint x #else #define DPRINT(x) #endif char *program; int f_inetd = 0; int f_daemon = 0; int f_ipv6 = 0; gid_t f_gid = -1; pid_t f_uid = -1; char *f_port = "http"; int port = -1; typedef enum { STATE_NONE, STATE_DOING, STATE_COMMAND_READ, /* Only for read */ STATE_ERROR, STATE_TIMED_OUT, /* Only for read */ STATE_DONE, } state_t; typedef enum { COMMAND_HEAD, COMMAND_BODY, COMMAND_BOTH } command_t; typedef struct page_s { unsigned char *url; /* Url of the page */ unsigned char *reply_head; /* Headers of the reply */ long reply_head_len; /* Length of headers */ unsigned char *reply_body; /* Body of the reply, this follows directly the headers, i.e it is in same buffer. */ long reply_body_len; /* Length of the body */ unsigned char *filename; /* Filename of page or NULL if none */ time_t last_modification; /* Last modification time of the page */ time_t last_stat; /* Last stat for the file */ time_t ref_count; /* Reference count. Initially set to 1 for all * permanent pages (files, permanent * redirections). When it gets to 0 the * page is freed (permanent pages are never * freed) */ time_t last_ref; /* Last time the page was referenced */ int permanent; /* Permanent page */ } *page_t; typedef struct redirection_s { unsigned char *from; /* The path component of url to redirect */ unsigned char *to; /* The initial url where to redirect */ } *redirection_t; typedef struct connection_s { int socket; char addr[ADDR_LENGTH]; time_t last_time; state_t read_state, write_state; long total_bytes; unsigned char *out_data; long out_data_len; unsigned char *out_data_ptr; unsigned char *in_data; long in_data_len; unsigned char *in_data_ptr; unsigned char *url; unsigned char *status; unsigned char *command; unsigned char *headers; page_t page; } *connection_t; page_t *pages, *temp_pages; redirection_t *redirections; connection_t *connections; int max_pages, num_pages, max_redirections, num_redirections; int max_temp_pages; int master_server = -1; time_t now, last_info_hour, quit_time = 0; unsigned char local_root_url[URL_LENGTH]; int total_bytes_in_cache = 0; fd_set fdrdset, fdwrset; int max_connections, number_of_sockets; int total_connections, total_max_connections, total_pages, total_errors, total_errors_no_page, total_temp_cache_hit, total_temp_make, total_perm_page, total_user_home, total_stat, total_read, total_bytes_sent, total_bytes; /* * Debug printf */ void dprint(const unsigned char *fmt, ...) { va_list args; char buffer[BUF_LENGTH]; va_start(args, fmt); vsprintf(buffer, (char *) fmt, args); va_end(args); fprintf(stderr, "%s\n", buffer); } /* * Open server socket */ int open_service(const char *serv) { struct sockaddr_in sin; struct sockaddr_in6 sin6; int err, socks; struct servent *servent; int one; DPRINT(("opening service %s", serv)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; #ifndef __linux__ sin6.sin6_len = sizeof(sin); #endif /* __linux__ */ sin6.sin6_addr = in6addr_any; servent = getservbyname(serv, "tcp"); if (servent == NULL) { errno = 0; port = atoi(serv); if (port == 0) { syslog(LOG_CRIT, "Error unknown service %.200s, exiting", serv); exit(1); } sin.sin_port = htons((unsigned short) port); sin6.sin6_port = htons((unsigned short) port); } else { sin.sin_port = servent->s_port; sin6.sin6_port = servent->s_port; port = servent->s_port; } DPRINT(("found port %d", ntohs(sin.sin_port))); if (f_ipv6) socks = socket(AF_INET6, SOCK_STREAM,0); else socks = socket(AF_INET, SOCK_STREAM,0); if (socks < 0) { syslog(LOG_CRIT, "Error in socket at open_service opening service %.200s, exiting", serv); exit(1); } one = 1; if (setsockopt(socks, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) == -1) { syslog(LOG_ERR, "Error in setsockopt REUSEADDR"); } if (f_ipv6) err = bind(socks, (struct sockaddr *) &sin6, sizeof(sin6)); else err = bind(socks, (struct sockaddr *) &sin, sizeof(sin)); if (err) { close(socks); syslog(LOG_CRIT, "Error in bind at open_service opening service %.200s, exiting", serv); exit(1); } err = listen(socks, LISTEN_BACKLOG); if (err) { close(socks); syslog(LOG_CRIT, "Error in listen at open_service opening service %.200s, exiting", serv); exit(1); } DPRINT(("service opened")); return socks; } char *wkday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; char *month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /* * Convert unix date integer to rfc1123 date */ unsigned char *rfc1123date(unsigned char *strbuf, time_t t) { struct tm *tm; tm = localtime(&t); sprintf((char *) strbuf, "%s, %02d %s %04d %02d:%02d:%02d GMT", wkday[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], tm->tm_year+1900, tm->tm_hour, tm->tm_min, tm->tm_sec); DPRINT(("rfc1123date: %s", strbuf)); return strbuf; } /* * Skip all whitespace characters. Returns the pointer to first non-whitespace * character. */ unsigned char *skip_white(unsigned char *p) { if (p == NULL) return NULL; while(isspace(*p)) p++; return p; } /* * Skip all non whitespace characters. Returns the pointer to first whitespace * character. */ unsigned char *skip_non_white(unsigned char *p) { if (p == NULL) return NULL; while(*p && !isspace(*p)) p++; return p; } /* * Compare page entries using url field. */ int compr_url(const void *a, const void *b) { const page_t ap = *(page_t *) a; const page_t bp = *(page_t *) b; return strcasecmp((char *) ap->url, (char *) bp->url); } /* * Compare page entries using url field. */ int compr_redirection(const void *a, const void *b) { const redirection_t ar = *(redirection_t *) a; const redirection_t br = *(redirection_t *) b; return strcasecmp((char *) ar->from, (char *) br->from); } /* * Set head and body data. If the buffer is NULL then it is not copied, * if the size is 0 then strlen is used. */ void set_head_and_body(page_t page, unsigned char *header, long header_len, unsigned char *body, long body_len) { long length; unsigned char *data; if (header_len == 0 && header != NULL) header_len = strlen((char *) header); if (body_len == 0 && body != NULL) body_len = strlen((char *) body); length = header_len + body_len; data = malloc(length); if (data == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } page->reply_head = data; page->reply_head_len = header_len; page->reply_body = data + header_len; page->reply_body_len = body_len; if (header != NULL) memcpy(page->reply_head, header, header_len); if (body != NULL) memcpy(page->reply_body, body, body_len); } /* * Add redirection page */ void add_redir_page(unsigned char *url, unsigned char *to_url) { long length; unsigned char buffer[BUF_LENGTH], hbuffer[BUF_LENGTH], timebuf[TIME_LENGTH]; DPRINT(("Adding redir page: %s -> %s", url, to_url)); pages[num_pages] = calloc(1, sizeof(struct page_s)); if (pages[num_pages] == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } pages[num_pages]->url = (unsigned char *) strdup((char *) url); if (pages[num_pages]->url == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } sprintf((char *) buffer, "Redirection\n

Query redirected to another address

\nThis is only a redirection service, the document can be found here.

", to_url); length = strlen((char *) buffer); sprintf((char *) hbuffer, "HTTP/1.0 302 Found\r\nX-Date: %s\r\nServer: %s\r\nMIME-version: 1.0\r\nLocation: %s\r\nURI: <%s>\r\nContent-type: text/html\r\nContent-Length: %ld\r\n\r\n", rfc1123date(timebuf, now), VERSION, to_url, to_url, length); set_head_and_body(pages[num_pages], hbuffer, 0L, buffer, 0L); pages[num_pages]->filename = NULL; pages[num_pages]->last_modification = 0; pages[num_pages]->last_stat = 0; pages[num_pages]->ref_count = 0; pages[num_pages]->permanent = 1; num_pages++; if (num_pages >= max_pages) { pages = realloc(pages, sizeof(page_t) * 2 * max_pages); if (pages == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } memset(pages + max_pages, 0, sizeof(page_t) * max_pages); max_pages *= 2; } } void add_forwarding_page(unsigned char *url, unsigned char *to_url) { DPRINT(("Adding forwarding page: %s -> %s", url, to_url)); redirections[num_redirections] = calloc(1, sizeof(struct redirection_s)); if (redirections[num_redirections] == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } redirections[num_redirections]->from = (unsigned char *) strdup((char *) url); redirections[num_redirections]->to = (unsigned char *) strdup((char *) to_url); if (redirections[num_redirections]->from == NULL || redirections[num_redirections]->to == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } num_redirections++; if (num_redirections >= max_redirections) { redirections = realloc(redirections, sizeof(redirection_t) * 2 * max_redirections); if (redirections == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } memset(redirections + max_redirections, 0, sizeof(redirection_t) * max_redirections); max_redirections *= 2; } } /* * Read redirections. */ void read_redirections(unsigned char *file_name) { FILE *file; unsigned char line[LINE_LENGTH]; long length; unsigned char *from, *to; file = fopen((char *) file_name, "r"); if (file == NULL) { syslog(LOG_CRIT, "Cannot open redirections file, exiting"); exit(1); } while(fgets((char *) line, LINE_LENGTH, file) != NULL) { length = strlen((char *) line); if (length == 0) continue; while (length > 0 && isspace(line[length - 1])) { length--; } line[length] = '\0'; from = skip_white(line); to = skip_non_white(from); *to++ = '\0'; to = skip_white(to); if (strlen((char *) from) == 0 || strlen((char *) to) == 0) continue; add_redir_page(from, to); if (from[strlen((char *) from) - 1] == '/') { add_forwarding_page(from, to); } } fclose(file); } /* * Convert filename to type */ char *match_type(char *name) { char *dot; dot = strrchr(name, '.'); if (dot == NULL) return "text/plain; charset=ISO-8859-1"; else if (strcasecmp(dot, ".html") == 0) return "text/html; charset=ISO-8859-1"; else if (strcasecmp(dot, ".css") == 0) return "text/css"; else if (strcasecmp(dot, ".htm") == 0) return "text/html; charset=ISO-8859-1"; else if (strcasecmp(dot, ".txt") == 0) return "text/plain; charset=ISO-8859-1"; else if (strcasecmp(dot, ".aiff") == 0) return "audio/x-aiff"; else if (strcasecmp(dot, ".au") == 0) return "audio/x-au"; else if (strcasecmp(dot, ".gif") == 0) return "image/gif"; else if (strcasecmp(dot, ".png") == 0) return "image/png"; else if (strcasecmp(dot, ".bmp") == 0) return "image/bmp"; else if (strcasecmp(dot, ".jpeg") == 0) return "image/jpeg"; else if (strcasecmp(dot, ".jpg") == 0) return "image/jpeg"; else if (strcasecmp(dot, ".tiff") == 0) return "image/tiff"; else if (strcasecmp(dot, ".tif") == 0) return "image/tiff"; else if (strcasecmp(dot, ".pnm") == 0) return "image/x-portable-anymap"; else if (strcasecmp(dot, ".pbm") == 0) return "image/x-portable-bitmap"; else if (strcasecmp(dot, ".pgm") == 0) return "image/x-portable-graymap"; else if (strcasecmp(dot, ".ppm") == 0) return "image/x-portable-pixmap"; else if (strcasecmp(dot, ".rgb") == 0) return "image/rgb"; else if (strcasecmp(dot, ".xbm") == 0) return "image/x-bitmap"; else if (strcasecmp(dot, ".xpm") == 0) return "image/x-pixmap"; else if (strcasecmp(dot, ".mpeg") == 0) return "video/mpeg"; else if (strcasecmp(dot, ".mpg") == 0) return "video/mpeg"; else if (strcasecmp(dot, ".ps") == 0) return "application/ps"; else if (strcasecmp(dot, ".eps") == 0) return "application/ps"; else if (strcasecmp(dot, ".dvi") == 0) return "application/x-dvi"; else if (strcasecmp(dot, ".ico") == 0) return "image/x-icon"; return "text/plain"; } /* * Read one www-page, fill in the data to page-struct given. Return 1 if * success, 0 otherwise. */ int read_page(page_t *page, unsigned char *file, struct stat *st, unsigned char *url) { int fd; unsigned char buffer[BUF_LENGTH], timebuf1[TIME_LENGTH], timebuf2[TIME_LENGTH]; DPRINT(("Reading file = %s, url = %s", file, url)); fd = open((char *) file, O_RDONLY, 0666); if (fd < 0) { syslog(LOG_ERR, "Error cannot open file %.200s", file); return 0; } if (*page == NULL) { *page = calloc(1, sizeof(struct page_s)); if (*page == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } } (*page)->url = (unsigned char *) strdup((char *) url); if ((*page)->url == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } sprintf((char *) buffer, "HTTP/1.0 200 OK\r\nX-Date: %s\r\nServer: %s\r\nMIME-version: 1.0\r\nLast-Modified: %s\r\nContent-type: %s\r\nContent-Length: %ld\r\n\r\n", rfc1123date(timebuf1, now), VERSION, rfc1123date(timebuf2, st->st_mtime), match_type((char *) file), (unsigned long) st->st_size); set_head_and_body(*page, buffer, 0L, NULL, (long) st->st_size); (*page)->filename = (unsigned char *) strdup((char *) file); if ((*page)->filename == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } (*page)->last_modification = st->st_mtime; (*page)->last_stat = now; (*page)->ref_count = 0; if (read(fd, (*page)->reply_body, st->st_size) != st->st_size) { close(fd); syslog(LOG_ERR, "Error reading file %.200s", file); free((*page)->reply_head); free((*page)->url); free((*page)); (*page) = NULL; return 0; } close(fd); return 1; } /* * Read htdocs. */ void read_htdocs(unsigned char *htdocs) { DIR *dir; struct dirent *de; struct stat st; unsigned char url[URL_LENGTH], to_url[URL_LENGTH], file[PATH_MAX]; DPRINT(("Reading htdocs = %s", htdocs)); if (htdocs[0] == '.' && htdocs[1] == '\0') { sprintf((char *) to_url, "%s/index.html", local_root_url); add_redir_page((unsigned char *) "/", to_url); } else { if (htdocs[0] == '.' && htdocs[1] == '/') { sprintf((char *) url, "/%s", htdocs + 2); } else { sprintf((char *) url, "/%s", htdocs); } sprintf((char *) to_url, "%s%s/index.html", local_root_url, url); add_redir_page(url, to_url); strcat((char *) url, "/"); add_redir_page(url, to_url); } sprintf((char *) file, "%s/.", htdocs); dir = opendir((char *) file); if (dir == NULL) { syslog(LOG_ERR, "Error opendir(%.200s) failed", htdocs); return; } while((de = readdir(dir)) != NULL) { if (strlen((char *) htdocs) + strlen(de->d_name) + 20 > PATH_MAX) { syslog(LOG_ERR, "Filename too long %.200s", htdocs); continue; } sprintf((char *) file, "%s/%s", htdocs, de->d_name); if (htdocs[0] == '.' && htdocs[1] == '\0') { sprintf((char *) url, "/%s", de->d_name); } else if (htdocs[0] == '.' && htdocs[1] == '/') { sprintf((char *) url, "/%s/%s", htdocs + 2, de->d_name); } else { sprintf((char *) url, "/%s/%s", htdocs, de->d_name); } if (stat((char *) file, &st) < 0) { syslog(LOG_ERR, "Error stat to %.200s failed", de->d_name); continue; } if (st.st_mode & S_IFDIR) { if (strcmp((char *) de->d_name, ".") != 0 && strcmp((char *) de->d_name, "..") != 0) { read_htdocs(file); } } else { if (read_page(&(pages[num_pages]), file, &st, url)) { pages[num_pages]->permanent = 1; num_pages++; } if (num_pages >= max_pages) { pages = realloc(pages, sizeof(page_t) * 2 * max_pages); if (pages == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } memset(pages + max_pages, 0, sizeof(page_t) * max_pages); max_pages *= 2; } } } closedir(dir); } /* * Read www-pages. First read redirections file, which contain from-http, * to-http pairs one at a line separated by spaces. * * Then read all files from htdocs directory. */ void read_pages(unsigned char *file, unsigned char *htdocs) { max_pages = 64; num_pages = 0; pages = calloc(max_pages, sizeof(page_t)); max_redirections = 64; num_redirections = 0; redirections = calloc(max_redirections, sizeof(redirection_t)); max_temp_pages = 64; temp_pages = calloc(max_temp_pages, sizeof(page_t)); if (pages == NULL || redirections == NULL || temp_pages == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } read_redirections(file); chdir((char *) htdocs); read_htdocs((unsigned char *) "."); qsort(pages, num_pages, sizeof(page_t), compr_url); qsort(redirections, num_redirections, sizeof(redirection_t), compr_redirection); } /* * Do writing of data. */ void do_write(int i) { int ret; DPRINT(("Writing data to connection %d", i)); if (connections[i]->write_state == STATE_ERROR || connections[i]->write_state == STATE_DONE) return; connections[i]->write_state = STATE_DOING; while (1) { if (connections[i]->out_data_len > 0) { ret = write(connections[i]->socket, connections[i]->out_data_ptr, connections[i]->out_data_len); if (ret < 0) { if (errno == EWOULDBLOCK) return; syslog(LOG_ERR, "Write failed for %.200s : %d", connections[i]->addr, errno); connections[i]->write_state = STATE_ERROR; return; } connections[i]->out_data_ptr += ret; connections[i]->out_data_len -= ret; } else { connections[i]->write_state = STATE_DONE; FD_CLR(connections[i]->socket, &fdwrset); return; } } } /* * Close connection */ void close_connection(int i) { DPRINT(("Closing connection %d", i)); syslog(LOG_INFO, "%.40s %.200s (%ld/%ld bytes) from %.30s %.100s.", connections[i]->command, connections[i]->url, connections[i]->total_bytes - connections[i]->out_data_len, connections[i]->total_bytes, connections[i]->addr, connections[i]->status); total_pages++; total_bytes += connections[i]->total_bytes; total_bytes_sent += connections[i]->total_bytes - connections[i]->out_data_len; FD_CLR(connections[i]->socket, &fdwrset); FD_CLR(connections[i]->socket, &fdrdset); number_of_sockets--; close(connections[i]->socket); if (connections[i]->page != NULL) { connections[i]->page->ref_count--; if (connections[i]->page->ref_count == 0) { DPRINT(("Marking page %s last reference time to %d", connections[i]->page->url, now)); connections[i]->page->last_ref = now; } } connections[i]->socket = -1; connections[i]->total_bytes = 0; connections[i]->last_time = now; connections[i]->out_data = NULL; connections[i]->out_data_len = 0L; connections[i]->out_data_ptr = NULL; connections[i]->in_data_len = 0L; } unsigned char bad_request[] = "400 Bad Request\n

400 Bad Request

\nYour client sent a query that this server could not understand.

\n\n"; unsigned char not_found[] = "HTTP/1.0 404 Not found\r\nServer: " VERSION "\r\nMIME-version: 1.0\r\nContent-type: text/html\r\n\r\n404 Not Found\n

404 Not Found

\nThe requested URL was not found on this server.

\n\n"; unsigned char done[] = "HTTP/1.0 200 OK\r\n\r\nDone\n

Done

\n\n"; /* Do not change header unless you change the status_head_len also. */ unsigned char status[1024] = "HTTP/1.0 200 OK\r\n\r\n"; long status_head_len = 19L; /* * Return error message for connection i. Start writing of error */ void return_error(int i) { DPRINT(("Returning bad request error to connection %d", i)); connections[i]->out_data = bad_request; connections[i]->out_data_len = sizeof(bad_request) - 1; connections[i]->out_data_ptr = bad_request; connections[i]->total_bytes = sizeof(bad_request) - 1; connections[i]->status = (unsigned char *) "bad request error"; connections[i]->page = NULL; total_errors++; do_write(i); } /* * Set out data from the from the string and total length and optional * header length. If the header length is not given then the first \r\n\r\n * is searched and that is used as end of header. */ void set_out_data(connection_t connection, unsigned char *str, size_t tlen, size_t hlen, command_t cmd) { if (cmd == COMMAND_BOTH) { connection->out_data = str; connection->out_data_len = tlen; connection->out_data_ptr = str; } else { if (hlen == 0) { unsigned char *p; p = str; while (memcmp(p, "\r\n\r\n", 4) != 0) { p = memchr(p + 1, '\r', tlen - (p - str - 1)); if (p == NULL) { syslog(LOG_CRIT, "Internal error, no end of header found"); exit(1); } } hlen = (p - str) + 4; } if (cmd == COMMAND_BODY) { connection->out_data = str + hlen; connection->out_data_len = tlen - hlen; connection->out_data_ptr = str + hlen; } else { connection->out_data = str; connection->out_data_len = hlen; connection->out_data_ptr = str; } } } /* * Find free temporary page */ page_t *find_free_temp_page() { int i; for(i = 0; i < max_temp_pages; i++) { if (temp_pages[i] == NULL || temp_pages[i]->url == NULL) break; } if (i >= max_temp_pages) { temp_pages = realloc(temp_pages, sizeof(page_t) * 2 * max_temp_pages); if (temp_pages == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } memset(temp_pages + max_temp_pages, 0, sizeof(page_t) * max_temp_pages); max_temp_pages *= 2; } return &(temp_pages[i]); } /* * Make redirection page */ page_t *make_redirection_page(redirection_t redirection, unsigned char *rest_of_url) { long length; unsigned char buffer[BUF_LENGTH], hbuffer[BUF_LENGTH], timebuf[TIME_LENGTH], url[URL_LENGTH]; page_t *page; if (strlen((char *)redirection->to) + strlen((char *)rest_of_url) + 30 > URL_LENGTH) { syslog(LOG_ERR, "Too long redirection"); return NULL; } page = find_free_temp_page(); if (*page == NULL) { *page = calloc(1, sizeof(struct page_s)); if (*page == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } } sprintf((char *) url, "%s%s", redirection->from, rest_of_url); (*page)->url = (unsigned char *) strdup((char *) url); if ((*page)->url == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } sprintf((char *) url, "%s%s", redirection->to, rest_of_url); DPRINT(("Making redirection page from %s -> %s", (*page)->url, url)); sprintf((char *) buffer, "Redirection\n

Query redirected to another address

\nThis is only a redirection service, the document can be found here.

", url); length = strlen((char *) buffer); total_bytes_in_cache += length; sprintf((char *) hbuffer, "HTTP/1.0 302 Found\r\nX-Date: %s\r\nServer: %s\r\nMIME-version: 1.0\r\nLocation: %s\r\nURI: <%s>\r\nContent-type: text/html\r\nContent-Length: %ld\r\n\r\n", rfc1123date(timebuf, now), VERSION, url, url, length); set_head_and_body(*page, hbuffer, 0L, buffer, 0L); (*page)->filename = NULL; (*page)->last_modification = now; (*page)->last_stat = now; (*page)->ref_count = 0; (*page)->permanent = 0; return page; } /* * Decode url */ void decode_url_in_place(unsigned char *url) { unsigned char *where, *to; for(where = url, to = url; *where; ) { if (*where == '%' && isxdigit(where[1]) && isxdigit(where[2])) { *to++ = (isdigit(where[1]) ? (where[1] - '0') : (tolower(where[1]) - 'a' + 10)) * 16 + (isdigit(where[2]) ? (where[2] - '0') : (tolower(where[2]) - 'a' + 10)); where += 3; } else { *to++ = *where++; } } *to = '\0'; } /* * Set the www-page for transfer */ void do_get(int i, unsigned char *url, command_t cmd) { page_t *page, key_page_ptr; struct page_s key_page; unsigned char decoded_url[URL_LENGTH]; strcpy((char *) decoded_url, (char *) url); decode_url_in_place(decoded_url); key_page.url = decoded_url; key_page_ptr = &key_page; connections[i]->url = url; DPRINT(("Finding url %s for connection %d", url, i)); page = bsearch(&key_page_ptr, pages, num_pages, sizeof(page_t), compr_url); if (page == NULL) { int j; DPRINT(("No permanent page found, finding temp")); for(j = 0; j < max_temp_pages; j++) { if (temp_pages[j] != NULL && temp_pages[j]->url != NULL && strcmp((char *) temp_pages[j]->url, (char *) decoded_url) == 0) { DPRINT(("Temp page found, created at %d", temp_pages[j]->last_stat)); page = &(temp_pages[j]); total_temp_cache_hit++; break; } } } else { total_perm_page++; } if (page == NULL) { redirection_t *redirection, key_redirection_ptr; struct redirection_s key_redirection; unsigned char *first_slash, *rest_of_url, url_buffer[URL_LENGTH]; DPRINT(("No page found, checking for forwards")); if (strncmp((char *) url, "/iki/faq/", 9) == 0) { DPRINT(("Special /iki/faq/ case, making redirection page")); rest_of_url = url + 9; strcpy((char *) url_buffer, (char *) decoded_url); url_buffer[9] = '\0'; } else { first_slash = (unsigned char *) strchr((char *) url + 1, '/'); if (first_slash != 0) { rest_of_url = ++first_slash; } else { rest_of_url = (unsigned char *) ""; } strcpy((char *) url_buffer, (char *) decoded_url); first_slash = (unsigned char *) strchr((char *) url_buffer + 1, '/'); if (first_slash != 0) { *++first_slash = '\0'; } } key_redirection.from = url_buffer; key_redirection_ptr = &key_redirection; redirection = bsearch(&key_redirection_ptr, redirections, num_redirections, sizeof(redirection_t), compr_redirection); if (redirection == NULL) { page = NULL; } else { page = make_redirection_page(*redirection, rest_of_url); if (page != NULL) { total_temp_make++; } } } if (page == NULL) { DPRINT(("No page found, returning error")); set_out_data(connections[i], not_found, sizeof(not_found) - 1, 0, cmd); connections[i]->status = (unsigned char *) "page not found"; connections[i]->page = NULL; total_errors_no_page++; } else { DPRINT(("Page found, checking age")); if ((*page)->filename != NULL && (*page)->ref_count == 0 && (*page)->last_stat + STAT_TIME < now) { struct stat st; DPRINT(("Statting")); total_stat++; if (stat((char *) ((*page)->filename), &st) < 0) { syslog(LOG_ERR, "Error stat to %.200s failed", (*page)->filename); } else { (*page)->last_stat = now; DPRINT(("Last modification is %d (was %d)", st.st_mtime, (*page)->last_modification)); if (st.st_mtime != (*page)->last_modification) { page_t new_page = NULL; total_read++; if (read_page(&new_page, (*page)->filename, &st, (*page)->url)) { if ((*page)->permanent) { new_page->permanent = 1; } else { total_bytes_in_cache -= (*page)->reply_body_len; total_bytes_in_cache += new_page->reply_body_len; } DPRINT(("Freeing old page and using new")); free((*page)->url); free((*page)->filename); free((*page)->reply_head); free(*page); *page = new_page; } } } } set_out_data(connections[i], (*page)->reply_head, (*page)->reply_head_len + (*page)->reply_body_len, (*page)->reply_head_len, cmd); connections[i]->status = (unsigned char *) "page found"; connections[i]->page = *page; (*page)->ref_count++; } } /* * Remove expired pages */ void cleanup_temp_cache() { int i; for(i = 0; i < max_temp_pages; i++) { if (temp_pages[i] != NULL && temp_pages[i]->url != NULL && temp_pages[i]->ref_count == 0 && temp_pages[i]->last_ref + KEEP_TIME < now) { DPRINT(("Clening page %s, %d bytes (last ref = %d)", temp_pages[i]->url, temp_pages[i]->reply_body_len, temp_pages[i]->last_ref)); total_bytes_in_cache -= temp_pages[i]->reply_body_len; free(temp_pages[i]->url); free(temp_pages[i]->reply_head); if (temp_pages[i]->filename != NULL) free(temp_pages[i]->filename); temp_pages[i]->url = NULL; temp_pages[i]->reply_head = NULL; temp_pages[i]->reply_body = NULL; temp_pages[i]->filename = NULL; temp_pages[i]->reply_head_len = 0L; temp_pages[i]->reply_body_len = 0L; temp_pages[i]->last_modification = 0; temp_pages[i]->last_stat = 0; temp_pages[i]->last_ref = 0; } } DPRINT(("Cleaned cache, total %d bytes remaining", total_bytes_in_cache)); } /* * New connection */ void new_connection() { struct sockaddr_in rsin; struct sockaddr_in6 rsin6; socklen_t rsinlen; socklen_t rsin6len; int i; rsinlen = sizeof(rsin); rsin6len = sizeof(rsin6); total_connections++; for(i = 0; i < max_connections; i++) { if (connections[i] == NULL || connections[i]->socket == -1) break; } if (i > total_max_connections) total_max_connections = i; if (i == max_connections) { int client; if (f_ipv6) client = accept(master_server, (struct sockaddr *) &rsin6, &rsin6len); else client = accept(master_server, (struct sockaddr *) &rsin, &rsinlen); syslog(LOG_ERR, "Too many connections, dropping connection"); close(client); } else { if (connections[i] == NULL) { connections[i] = calloc(1, sizeof(struct connection_s)); if (connections[i] == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } connections[i]->socket = -1; connections[i]->in_data = malloc(COMMAND_LENGTH); if (connections[i]->in_data == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } } connections[i]->last_time = now; connections[i]->write_state = STATE_NONE; connections[i]->read_state = STATE_NONE; connections[i]->in_data_ptr = connections[i]->in_data; connections[i]->command = (unsigned char *) "(no command)"; connections[i]->headers = (unsigned char *) "(no headers)"; connections[i]->url = (unsigned char *) "(no url)"; connections[i]->in_data_len = 0L; if (f_ipv6) { connections[i]->socket = accept(master_server, (struct sockaddr *) &rsin6, &rsin6len); inet_ntop(AF_INET6, (const void *) &rsin6.sin6_addr, connections[i]->addr, ADDR_LENGTH); } else { connections[i]->socket = accept(master_server, (struct sockaddr *) &rsin, &rsinlen); inet_ntop(AF_INET, (const void *) &rsin.sin_addr, connections[i]->addr, ADDR_LENGTH); } if (connections[i]->socket < 0) { syslog(LOG_ERR, "Accept failed from %.200s", connections[i]->addr); connections[i]->socket = -1; } else { if (fcntl(connections[i]->socket, F_SETFL, fcntl(connections[i]->socket, F_GETFL, 0) | FNDELAY) < 0) { syslog(LOG_ERR, "fcntl failed for %.200s", connections[i]->addr); close(connections[i]->socket); connections[i]->socket = -1; } else { int one; one = 1; if (setsockopt(connections[i]->socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) == -1) { syslog(LOG_ERR, "Setsockopt REUSEADDR fails"); } connections[i]->status = (unsigned char *) "connected"; FD_SET(connections[i]->socket, &fdrdset); number_of_sockets++; DPRINT(("New connection %d from %s", i, connections[i]->addr)); } } } } /* * Data read from socket */ void read_data(int i) { int data_read; data_read = read(connections[i]->socket, connections[i]->in_data_ptr, COMMAND_LENGTH - connections[i]->in_data_len); if (connections[i]->read_state == STATE_ERROR || connections[i]->read_state == STATE_TIMED_OUT || connections[i]->read_state == STATE_DONE) return; if (data_read <= 0) { if (data_read < 0) { if (errno == EWOULDBLOCK) return; syslog(LOG_WARNING, "Read error from host %.200s : %d", connections[i]->addr, errno); connections[i]->status = (unsigned char *) "read error"; connections[i]->read_state = STATE_ERROR; connections[i]->write_state = STATE_ERROR; } connections[i]->status = (unsigned char *) "other end of socket closed"; connections[i]->read_state = STATE_DONE; FD_CLR(connections[i]->socket, &fdrdset); } else { DPRINT(("Read %d bytes", data_read)); connections[i]->in_data_ptr += data_read; connections[i]->in_data_len += data_read; } } /* * parse command */ void parse_command(int i) { unsigned char *eol1, *eol2; unsigned char *command, *uri, *protocol, *tmp; eol1 = memchr(connections[i]->in_data, '\n', connections[i]->in_data_len); eol2 = memchr(connections[i]->in_data, '\r', connections[i]->in_data_len); if (eol1 == NULL && eol2 == NULL) { if (connections[i]->in_data_len >= COMMAND_LENGTH) { syslog(LOG_WARNING, "No url in buffer range from %.200s", connections[i]->addr); connections[i]->status = (unsigned char *) "read buffer overflow"; connections[i]->read_state = STATE_ERROR; connections[i]->write_state = STATE_ERROR; } if (connections[i]->read_state == STATE_DONE) { return_error(i); } return; } connections[i]->read_state = STATE_COMMAND_READ; FD_SET(connections[i]->socket, &fdwrset); if (eol1 == NULL) eol1 = eol2; if (eol2 == NULL) eol2 = eol1; if (eol1 > eol2) eol1 = eol2; *eol1 = '\0'; connections[i]->headers = ++eol1; command = connections[i]->in_data; command = skip_white(command); connections[i]->command = command; uri = skip_non_white(command); if (*uri != '\0') { *uri++ = '\0'; uri = skip_white(uri); protocol = skip_non_white(uri); if (*protocol != '\0') { *protocol++ = '\0'; protocol = skip_white(protocol); tmp = skip_non_white(protocol); *tmp++ = '\0'; } } else { protocol = uri; *connections[i]->headers = '\0'; } connections[i]->url = uri; DPRINT(("Parsed command = %s, uri = %s, protocol = %s", command, uri, protocol)); if (strcasecmp((char *) command, "get") == 0 || strcasecmp((char *) command, "head") == 0 || strcasecmp((char *) command, "post") == 0) { command_t cmd; cmd = COMMAND_BOTH; if (!*protocol) cmd = COMMAND_BODY; else if (strcasecmp((char *) command, "head") == 0) cmd = COMMAND_HEAD; if (*uri == '\0') { return_error(i); return; } do_get(i, uri, cmd); if (cmd != COMMAND_BODY && strncasecmp((char *) protocol, "http/1.", 6) != 0) { return_error(i); return; } connections[i]->total_bytes = connections[i]->out_data_len; do_write(i); return; } else if (strcasecmp((char *) command, "quit") == 0) { quit_time = now + QUIT_TIME; close(master_server); FD_CLR(master_server, &fdrdset); number_of_sockets--; set_out_data(connections[i], done, sizeof(done) - 1, 0, COMMAND_BOTH); connections[i]->total_bytes = connections[i]->out_data_len; connections[i]->status = (unsigned char *) "quit done"; do_write(i); return; } else if (strcasecmp((char *) command, "status") == 0) { sprintf((char *) status + status_head_len, "Status\n

Status

\nStatistics time = %ld
\n%d connections (%d max)
\n%d pages (%d no page errors/%d errors)
\n%d perm, %d temp cache, %d temp make, %d user home
\n%d stated, %d reread
\n%d/%d bytes sent
\nCache: %d pages, %d redirections, %d bytes in temp cache
\n%s
\n\n", now - last_info_hour, total_connections, total_max_connections, total_pages, total_errors_no_page, total_errors, total_perm_page, total_temp_cache_hit, total_temp_make, total_user_home, total_stat, total_read, total_bytes_sent, total_bytes, num_pages, num_redirections, total_bytes_in_cache, f_ipv6 ? "IPv6" : "IPv4"); set_out_data(connections[i], status, strlen((char *) status), status_head_len, COMMAND_BOTH); connections[i]->total_bytes = connections[i]->out_data_len; connections[i]->status = (unsigned char *) "status info done"; do_write(i); return; } return_error(i); } /* * parse command */ void parse_headers(int i) { unsigned char *p; long len; len = connections[i]->in_data_len - (connections[i]->headers - connections[i]->in_data); for(p = connections[i]->headers; len > 0 ; len--, p++) { if ((p[0] == '\n' && ((len >= 4 && p[1] == '\r' && p[2] == '\n' && p[3] == '\r') || (len >= 2 && p[1] == '\n'))) || (p[0] == '\r' && ((len >= 4 && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') || (len >= 2 && p[1] == '\r')))) { p[1] = '\0'; connections[i]->read_state = STATE_DONE; return; } } } /* * master server */ void http_server() { fd_set tmprdset, tmpwrset; int nfound, i; time_t last_cleanup_time; struct timeval tm; struct tm *t; max_connections = getdtablesize(); connections = calloc(max_connections, sizeof(connection_t)); if (connections == NULL) { syslog(LOG_CRIT, "Out of memory, exiting"); exit(1); } FD_ZERO(&fdrdset); FD_ZERO(&fdwrset); FD_SET(master_server, &fdrdset); number_of_sockets = 1; now = time(NULL); last_cleanup_time = now; last_info_hour = now; /* Round to exact hour */ t = localtime(&last_info_hour); last_info_hour -= t->tm_sec + t->tm_min * 60; while (number_of_sockets > 0) { now = time(NULL); if (quit_time != 0 && now > quit_time) { syslog(LOG_NOTICE, "Server didn't die after %d seconds, exiting", QUIT_TIME); break; } if (last_cleanup_time + CHECK_TIME <= now) { cleanup_temp_cache(); last_cleanup_time = now; } if (last_info_hour + HOUR <= now) { syslog(LOG_NOTICE, "%d connections (%d max), %d pages (%d no page errors/%d errors), %d perm, %d temp cache, %d temp make, %d user home, %d stated, %d reread, %d/%d bytes sent. Cache: %d pages, %d redirections, %d bytes in temp cache, %s", total_connections, total_max_connections, total_pages, total_errors_no_page, total_errors, total_perm_page, total_temp_cache_hit, total_temp_make, total_user_home, total_stat, total_read, total_bytes_sent, total_bytes, num_pages, num_redirections, total_bytes_in_cache, f_ipv6 ? "IPv6" : "IPv4"); total_connections = 0; total_max_connections = 0; total_pages = 0; total_errors_no_page = 0; total_errors = 0; total_perm_page = 0; total_temp_cache_hit = 0; total_temp_make = 0; total_user_home = 0; total_stat = 0; total_read = 0; total_bytes_sent = 0; total_bytes = 0; last_info_hour += HOUR; } tmprdset = fdrdset; tmpwrset = fdwrset; tm.tv_sec = last_cleanup_time + CHECK_TIME - now; tm.tv_usec = 0; nfound = select(FD_SETSIZE, &tmprdset, &tmpwrset, NULL, &tm); if(nfound < 0 && errno != EINTR) { syslog(LOG_CRIT, "Select failed in main_loop, exiting"); exit(1); } if (nfound < 0 && errno == EINTR) continue; if (FD_ISSET(master_server, &tmprdset)) new_connection(); for(i = 0; i < max_connections; i++) { if (connections[i] == NULL) break; if (connections[i]->socket == -1) continue; if (FD_ISSET(connections[i]->socket, &tmprdset)) { connections[i]->last_time = now; if (connections[i]->read_state == STATE_NONE) { connections[i]->read_state = STATE_DOING; } read_data(i); if (connections[i]->read_state == STATE_DOING) { parse_command(i); } if (connections[i]->read_state == STATE_COMMAND_READ) { parse_headers(i); } } if (connections[i]->read_state == STATE_COMMAND_READ && now - connections[i]->last_time > READ_TIME_OUT) { connections[i]->read_state = STATE_TIMED_OUT; } if (FD_ISSET(connections[i]->socket, &tmpwrset)) { connections[i]->last_time = now; do_write(i); } if ((connections[i]->read_state == STATE_ERROR || connections[i]->read_state == STATE_TIMED_OUT || connections[i]->read_state == STATE_DONE) && (connections[i]->write_state == STATE_ERROR || connections[i]->write_state == STATE_DONE)) close_connection(i); if (now - connections[i]->last_time > KICK_TIME) { close_connection(i); } } } syslog(LOG_NOTICE, "%d connections (%d max), %d pages (%d no page errors/%d errors), %d perm, %d temp cache, %d temp make, %d user home, %d stated, %d reread, %d/%d bytes sent. Cache: %d pages, %d redirections, %d bytes in temp cache, %s", total_connections, total_max_connections, total_pages, total_errors_no_page, total_errors, total_perm_page, total_temp_cache_hit, total_temp_make, total_user_home, total_stat, total_read, total_bytes_sent, total_bytes, num_pages, num_redirections, total_bytes_in_cache, f_ipv6 ? "IPv6" : "IPv4"); } int main(int argc, char **argv) { extern char *optarg; extern int optind; int c, errflg = 0; signal(SIGPIPE, SIG_IGN); now = time(NULL); total_connections = 0; total_max_connections = 0; total_pages = 0; total_errors_no_page = 0; total_errors = 0; total_perm_page = 0; total_temp_cache_hit = 0; total_temp_make = 0; total_user_home = 0; total_stat = 0; total_read = 0; total_bytes_sent = 0; total_bytes = 0; program = strrchr(argv[0], '/'); if (program == NULL) program = argv[0]; else program++; #ifdef RLIMIT_NOFILE { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); } } #endif /* RLIMIT_NOFILE */ openlog(program, LOG_PID, LOG_LOCAL0); while ((c = getopt(argc, argv, "di6g:u:p:")) != EOF) { switch (c) { case 'd': f_daemon++; break; case 'i': f_inetd++; break; case '6': f_ipv6++; break; case 'g': f_gid = atoi(optarg); break; case 'u': f_uid = atoi(optarg); break; case 'p': f_port = optarg; break; case '?': errflg++; break; } } if (errflg || argc - optind < 2) { fprintf(stderr, "Usage: %s [-di6] [-g gid] [-u uid] [-p service] redir-file htdocs-dir\n", program); exit(1); } if (f_daemon) { int pid, i; pid = fork(); if (pid) { if (pid == -1) { syslog(LOG_CRIT, "Can't fork a child, exiting!"); exit(1); } printf("%d\n", pid); fflush(stdout); exit(0); } closelog(); for(i = getdtablesize() - 1; i >= 0; i--) if (!f_inetd || i != 0) close(i); setsid(); openlog(program, LOG_PID, LOG_DAEMON); } if (f_inetd) master_server = 0; else master_server = open_service(f_port); if (getuid() == 0) { if (f_gid != -1) setgid(f_gid); if (f_uid != -1) setuid(f_uid); } if (port == 80 || port == -1) { sprintf((char *) local_root_url, "%s", LOCAL_ROOT_URL); } else { sprintf((char *) local_root_url, "%s:%d", LOCAL_ROOT_URL, port); } if (strlen((char *) local_root_url) > URL_LENGTH - 80) { syslog(LOG_CRIT, "Local root url is too long"); exit(1); } read_pages((unsigned char *) argv[optind], (unsigned char *) argv[optind + 1]); http_server(); return 0; }