/** * @PROJECT Kagera uHTTP Daemon * @COPYRIGHT See COPYING in the top level directory * @FILE kuhttpd.c * @PURPOSE HTTP Server * @DEVELOPERS Eric Bishop * Rafal Kupiec * Jef Poskanzer */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CROSS_BUILD #include #endif #include "kuhttpd.h" #include "dateparse.h" #include "match.h" #include "mime_enc.h" #include "mime_typ.h" static void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod) { time_t now; char timebuf[100]; char buf[10000]; int buflen; const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; status = s; bytes = b; make_log_entry(); start_response(); buflen = snprintf(buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title); add_to_response(buf, buflen); buflen = snprintf(buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE); add_to_response(buf, buflen); now = time((time_t*) 0); (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now)); buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf); add_to_response(buf, buflen); buflen = snprintf(buf, sizeof(buf), "Expires: %s\015\012", timebuf); add_to_response(buf, buflen); if(mod != (time_t) -1) { (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&mod)); buflen = snprintf(buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf); add_to_response(buf, buflen); } if(extra_header != (char*) 0 && extra_header[0] != '\0') { buflen = snprintf(buf, sizeof(buf), "%s\015\012", extra_header); add_to_response(buf, buflen); } if(me != (char*) 0 && me[0] != '\0') { buflen = snprintf(buf, sizeof(buf), "Content-Encoding: %s\015\012", me); add_to_response(buf, buflen); } if(mt != (char*) 0 && mt[0] != '\0') { buflen = snprintf(buf, sizeof(buf), "Content-Type: %s\015\012", mt); add_to_response(buf, buflen); } if(bytes >= 0) { buflen = snprintf(buf, sizeof(buf), "Content-Length: %lld\015\012", (long long int) bytes); add_to_response(buf, buflen); } if(p3p != (char*) 0 && p3p[0] != '\0') { buflen = snprintf(buf, sizeof(buf), "P3P: %s\015\012", p3p); add_to_response(buf, buflen); } buflen = snprintf(buf, sizeof(buf), "\015\012"); add_to_response(buf, buflen); } static void add_to_buf(char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len) { if(*bufsizeP == 0) { *bufsizeP = len + 500; *buflenP = 0; *bufP = (char*) e_malloc(*bufsizeP); } else if(*buflenP + len >= *bufsizeP) { *bufsizeP = *buflenP + len + 500; *bufP = (char*) e_realloc((void*) *bufP, *bufsizeP); } (void) memmove(&((*bufP)[*buflenP]), str, len); *buflenP += len; (*bufP)[*buflenP] = '\0'; } static void add_to_request(char* str, size_t len) { add_to_buf(&request, &request_size, &request_len, str, len); } static void add_to_response(char* str, size_t len) { add_to_buf(&response, &response_size, &response_len, str, len); } static void auth_check(char* dirname, int is_ssl) { char authpath[10000]; char realmName[500]; struct stat sb; char authinfo[500]; char* authpass; char* colon; static char line[10000]; int l; FILE* fp; char* cryp; if(dirname[strlen(dirname) - 1] == '/') { (void) snprintf(authpath, sizeof(authpath), "%s%s", dirname, AUTH_FILE); } else { (void) snprintf(authpath, sizeof(authpath), "%s/%s", dirname, AUTH_FILE); } if(stat(authpath, &sb) < 0) { if(defaultRealmName == NULL || defaultRealmPasswordFile == NULL) { return; } else { snprintf(authpath, sizeof(authpath), "%s", defaultRealmPasswordFile); } } if(strcmp(authpath, defaultRealmPasswordFile) == 0) { snprintf(realmName, sizeof(realmName), "%s", defaultRealmName); } else { snprintf(realmName, sizeof(realmName), "%s", dirname); } if(authorization == (char*) 0) { send_authenticate(realmName, is_ssl); } if(strncmp(authorization, "Basic ", 6) != 0) { send_authenticate(realmName, is_ssl); } l = b64_decode(&(authorization[6]), (unsigned char*) authinfo, sizeof(authinfo) - 1); authinfo[l] = '\0'; authpass = strchr(authinfo, ':'); if(authpass == (char*) 0) { send_authenticate(realmName, is_ssl); } *authpass++ = '\0'; colon = strchr(authpass, ':'); if(colon != (char*) 0) { *colon = '\0'; } fp = fopen(authpath, "r"); if(fp == (FILE*) 0) { syslog(LOG_ERR, "%.80s auth file %.80s could not be opened - %m", ntoa(&client_addr), authpath); send_error(403, "Forbidden", "", "File is protected.", is_ssl); } while(fgets(line, sizeof(line), fp) != (char*) 0) { l = strlen(line); if(line[l - 1] == '\n') { line[l - 1] = '\0'; } cryp = strchr(line, ':'); if(cryp == (char*) 0) { continue; } *cryp++ = '\0'; if(strcmp(line, authinfo) == 0) { (void) fclose(fp); if(strcmp(crypt(authpass, cryp), cryp) == 0) { remoteuser = line; return; } else { send_authenticate(realmName, is_ssl); } } } (void) fclose(fp); send_authenticate(realmName, is_ssl); } static int b64_decode(const char* str, unsigned char* space, int size) { const char* cp; int space_idx, phase; int d, prev_d = 0; unsigned char c; space_idx = 0; phase = 0; for(cp = str; *cp != '\0'; ++cp) { d = b64_decode_table[(int) *cp]; if(d != -1) { switch(phase) { case 0: ++phase; break; case 1: c = ((prev_d << 2) | ((d & 0x30) >> 4)); if(space_idx < size) { space[space_idx++] = c; } ++phase; break; case 2: c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2)); if(space_idx < size) { space[space_idx++] = c; } ++phase; break; case 3: c = (((prev_d & 0x03) << 6) | d); if(space_idx < size) { space[space_idx++] = c; } phase = 0; break; } prev_d = d; } } return space_idx; } static char* build_env(char* fmt, char* arg) { char* cp; int size; static char* buf; static int maxbuf = 0; size = strlen(fmt) + strlen(arg); if(size > maxbuf) { if(maxbuf == 0) { maxbuf = MAX(200, size + 100); buf = (char*) e_malloc(maxbuf); } else { maxbuf = MAX(maxbuf * 2, size * 5 / 4); buf = (char*) e_realloc((void*) buf, maxbuf); } } (void) snprintf(buf, maxbuf, fmt, arg); cp = e_strdup(buf); return cp; } static void cgi_interpose_input(int wfd, int is_ssl) { size_t c; ssize_t r, r2; char buf[1024]; c = request_len - request_idx; if(c > 0) { if(write(wfd, &(request[request_idx]), c) != c) { return; } } while(c < content_length) { r = my_read(buf, MIN( sizeof(buf), content_length - c), is_ssl); if(r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r <= 0) { return; } for(;;) { r2 = write(wfd, buf, r); if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r2 != r) { return; } break; } c += r; } post_post_garbage_hack(is_ssl); } static void cgi_interpose_output(int rfd, int parse_headers, int is_ssl) { ssize_t r, r2; char buf[1024]; if(!parse_headers) { char http_head[] = "HTTP/1.0 200 OK\015\012"; (void) my_write(http_head, sizeof(http_head), is_ssl); } else { size_t headers_size, headers_len; char* headers; char* br; int status; char* title; char* cp; headers_size = 0; add_to_buf(&headers, &headers_size, &headers_len, (char*) 0, 0); for(;;) { r = read(rfd, buf, sizeof(buf)); if(r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r <= 0) { br = &(headers[headers_len]); break; } add_to_buf(&headers, &headers_size, &headers_len, buf, r); if((br = strstr(headers, "\015\012\015\012")) != (char*) 0 || (br = strstr(headers, "\012\012")) != (char*) 0) { break; } } if(headers[0] == '\0') { return; } status = 200; if((cp = strstr(headers, "Status:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) { cp += 7; cp += strspn(cp, " \t"); status = atoi(cp); } if((cp = strstr(headers, "Location:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) { status = 302; } switch(status) { case 200: title = "OK"; break; case 302: title = "Found"; break; case 304: title = "Not Modified"; break; case 400: title = "Bad Request"; break; case 401: title = "Unauthorized"; break; case 403: title = "Forbidden"; break; case 404: title = "Not Found"; break; case 408: title = "Request Timeout"; break; case 500: title = "Internal Error"; break; case 501: title = "Not Implemented"; break; case 503: title = "Service Temporarily Overloaded"; break; default: title = "Something"; break; } (void) snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title); (void) my_write(buf, strlen(buf), is_ssl); if(strstr(headers, "Server:") == NULL) { char line[200]; sprintf(line, "Server: %s\015\012", SERVER_SOFTWARE); (void) my_write(line, strlen(line), is_ssl); } if(strstr(headers, "Date:") == NULL) { char line[200]; char timebuf[100]; time_t now = time((time_t*) 0); const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now)); sprintf(line, "Date: %s\015\012", timebuf); (void) my_write(line, strlen(line), is_ssl); if(strstr(headers, "Expires:") == NULL) { sprintf(line, "Expires: %s\015\012", timebuf); (void) my_write(line, strlen(line), is_ssl); } } else if(strstr(headers, "Expires:") == NULL) { char* line = "Expires: Thu, 01 Jan 1970 00:00:00 GMT\015\012"; (void) my_write(line, strlen(line), is_ssl); } (void) my_write(headers, headers_len, is_ssl); } for(;;) { r = read(rfd, buf, sizeof(buf)); if(r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r <= 0) { goto done; } for(;;) { r2 = my_write(buf, r, is_ssl); if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r2 != r) { goto done; } break; } } done: shutdown(conn_fd, SHUT_WR); } static void check_referer(int is_ssl) { char* cp; if(url_pattern == (char*) 0) { return; } if(really_check_referer()) { return; } cp = hostname; if(cp == (char*) 0) { cp = ""; } syslog(LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", ntoa(&client_addr), cp, path, referer); send_error(403, "Forbidden", "", "You must supply a local referer.", is_ssl); } static void clear_ndelay(int fd) { int flags, newflags; flags = fcntl(fd, F_GETFL, 0); if(flags != -1) { newflags = flags & ~ (int) O_NDELAY; if(newflags != flags) { (void) fcntl(fd, F_SETFL, newflags); } } } static void de_dotdot(char* file) { char* cp; char* cp2; int l; while((cp = strstr(file, "//")) != (char*) 0) { for(cp2 = cp + 2; *cp2 == '/'; ++cp2) { continue; } (void) strcpy(cp + 1, cp2); } while(strncmp(file, "./", 2) == 0) { (void) strcpy(file, file + 2); } while((cp = strstr( file, "/./")) != (char*) 0) { (void) strcpy(cp, cp + 2); } for(;;) { while(strncmp(file, "../", 3) == 0) { (void) strcpy( file, file + 3 ); } cp = strstr(file, "/../"); if(cp == (char*) 0) { break; } for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) { continue; } (void) strcpy(cp2 + 1, cp + 4); } while((l = strlen(file)) > 3 && strcmp((cp = file + l - 3), "/..") == 0) { for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) { continue; } if(cp2 < file) { break; } *cp2 = '\0'; } } static void do_cgi(int is_ssl, unsigned short conn_port) { char** argp; char** envp; int parse_headers; char* binary; char* directory; if(method != METHOD_GET && method != METHOD_POST) { send_error(501, "Not Implemented", "", "That method is not implemented for CGI.", is_ssl); } if(conn_fd == STDIN_FILENO || conn_fd == STDOUT_FILENO || conn_fd == STDERR_FILENO) { int newfd = dup2(conn_fd, STDERR_FILENO + 1); if(newfd >= 0) { conn_fd = newfd; } } envp = make_envp(is_ssl, conn_port); argp = make_argp(); if((method == METHOD_POST && request_len > request_idx) || is_ssl) { int p[2]; int r; if(pipe(p) < 0) { send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl); } r = fork(); if(r < 0) { send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl); } if(r == 0) { (void) close(p[0]); cgi_interpose_input(p[1], is_ssl); exit(0); } (void) close(p[1]); if(p[0] != STDIN_FILENO) { (void) dup2(p[0], STDIN_FILENO); (void) close(p[0]); } } else { if(conn_fd != STDIN_FILENO) { (void) dup2(conn_fd, STDIN_FILENO); } } if(strncmp(argp[0], "nph-", 4) == 0) { parse_headers = 0; } else { parse_headers = 1; } if(parse_headers || is_ssl) { int p[2]; int r; if(pipe(p) < 0) { send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl); } r = fork(); if(r < 0) { send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl); } if(r == 0) { (void) close(p[1]); cgi_interpose_output(p[0], parse_headers, is_ssl); exit(0); } (void) close(p[0]); if(p[1] != STDOUT_FILENO) { (void) dup2(p[1], STDOUT_FILENO); } if(p[1] != STDERR_FILENO) { (void) dup2(p[1], STDERR_FILENO); } if(p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO) { (void) close(p[1]); } } else { if(conn_fd != STDOUT_FILENO) { (void) dup2(conn_fd, STDOUT_FILENO); } if(conn_fd != STDERR_FILENO) { (void) dup2(conn_fd, STDERR_FILENO); } } if(logfp != (FILE*) 0) { (void) fclose(logfp); } closelog(); if(nice(CGI_NICE) < 0) { ; } directory = e_strdup(file); binary = strrchr(directory, '/'); if(binary == (char*) 0) { binary = file; } else { *binary++ = '\0'; if(chdir(directory) < 0) { ; } } (void) signal(SIGPIPE, SIG_DFL); (void) execve(binary, argp, envp); send_error(500, "Internal Error", "", "Something unexpected went wrong running a CGI program.", is_ssl); } static void do_dir(int is_ssl) { char buf[10000]; size_t buflen; char* contents; size_t contents_size, contents_len; int n, i; struct dirent **dl; char* name_info; if(pathinfo != (char*) 0) { send_error(404, "Not Found", "", "File not found.", is_ssl); } auth_check(file, is_ssl); check_referer(is_ssl); n = scandir(file, &dl, NULL, alphasort); if(n < 0) { syslog(LOG_INFO, "%.80s Directory \"%.80s\" is protected", ntoa(&client_addr), path); send_error(403, "Forbidden", "", "Directory is protected.", is_ssl); } contents_size = 0; buflen = snprintf(buf, sizeof(buf), "\nIndex of %s\n\n

Index of %s


\n", file, file);
	add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
	for(i = 0; i < n; ++i) {
		if(strcmp(dl[i]->d_name, ".") == 0) {
			continue;
		}
		name_info = file_details(file, dl[i]->d_name);
		add_to_buf(&contents, &contents_size, &contents_len, name_info, strlen(name_info));
	}
	buflen = snprintf(buf, sizeof(buf), "

\n%s\n\n\n", SERVER_SOFTWARE); add_to_buf(&contents, &contents_size, &contents_len, buf, buflen); add_headers(200, "Ok", "", "", "text/html; charset=%s", contents_len, sb.st_mtime); if(method != METHOD_HEAD) { add_to_response(contents, contents_len); } send_response(is_ssl); } static void do_file(int is_ssl, unsigned short conn_port) { char buf[10000]; char mime_encodings[500]; const char* mime_type; char fixed_mime_type[500]; char* cp; int fd; (void) strncpy(buf, file, sizeof(buf)); cp = strrchr(buf, '/'); if(cp == (char*) 0) { (void) strcpy(buf, "."); } else { *cp = '\0'; } auth_check(buf, is_ssl); if(strcmp(file, AUTH_FILE) == 0 || (strcmp(&(file[strlen(file) - sizeof(AUTH_FILE) + 1]), AUTH_FILE) == 0 && file[strlen(file) - sizeof(AUTH_FILE)] == '/')) { syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path); send_error(403, "Forbidden", "", "File is protected.", is_ssl); } if(defaultRealmName != NULL && defaultRealmPasswordFile != NULL) { if(strcmp(file, defaultRealmPasswordFile) == 0 || strcmp(&(file[strlen(file) - sizeof(defaultRealmPasswordFile) + 1]), defaultRealmPasswordFile) == 0 && file[strlen(file) - sizeof(defaultRealmPasswordFile)] == '/') { syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path); send_error(403, "Forbidden", "", "File is protected.", is_ssl); } } check_referer(is_ssl); if(cgi_pattern != (char*) 0 && match(cgi_pattern, file)) { do_cgi(is_ssl, conn_port); return; } else if(pathinfo != (char*) 0) { send_error(404, "Not Found", "", "File not found.", is_ssl); } fd = open(file, O_RDONLY); if(fd < 0) { syslog(LOG_INFO, "%.80s File \"%.80s\" is protected", ntoa(&client_addr), path); send_error(403, "Forbidden", "", "File is protected.", is_ssl); } mime_type = figure_mime(file, mime_encodings, sizeof(mime_encodings)); (void) snprintf(fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset); if(if_modified_since != (time_t) -1 && if_modified_since >= sb.st_mtime) { add_headers(304, "Not Modified", "", mime_encodings, fixed_mime_type, (off_t) -1, sb.st_mtime); send_response(is_ssl); return; } add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime); send_response(is_ssl); if(method == METHOD_HEAD) { return; } if(sb.st_size > 0) { send_via_write(fd, sb.st_size, is_ssl); } (void) close(fd); } static void* e_malloc(size_t size) { void* ptr; ptr = malloc(size); if(ptr == (void*) 0) { syslog(LOG_CRIT, "out of memory"); (void) fprintf(stderr, "%s: out of memory\n", argv0); exit(1); } return ptr; } static void* e_realloc(void* optr, size_t size) { void* ptr; ptr = realloc(optr, size); if(ptr == (void*) 0) { syslog(LOG_CRIT, "out of memory"); (void) fprintf(stderr, "%s: out of memory\n", argv0); exit(1); } return ptr; } static char* e_strdup(char* ostr) { char* str; str = strdup(ostr); if(str == (char*) 0) { syslog(LOG_CRIT, "out of memory copying a string"); (void) fprintf(stderr, "%s: out of memory copying a string\n", argv0); exit(1); } return str; } static int ext_compare(struct mime_entry* a, struct mime_entry* b) { return strcmp(a->ext, b->ext); } static const char* figure_mime(char* name, char* me, size_t me_size) { char* prev_dot; char* dot; char* ext; int me_indexes[100], n_me_indexes; size_t ext_len, me_len; int i, top, bot, mid; int r; const char* default_type = "application/octet-stream "; const char* type; n_me_indexes = 0; for(prev_dot = &name[strlen(name)]; ; prev_dot = dot) { for(dot = prev_dot - 1; dot >= name && *dot != '.'; --dot); if(dot < name) { type = default_type; goto done; } ext = dot + 1; ext_len = prev_dot - ext; for(i = 0; i < n_enc_tab; ++i) { if(ext_len == enc_tab[i].ext_len && strncasecmp(ext, enc_tab[i].ext, ext_len) == 0) { if(n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes)) { me_indexes[n_me_indexes] = i; ++n_me_indexes; } goto next; } } break; next: ; } top = n_typ_tab - 1; bot = 0; while(top >= bot) { mid = (top + bot) / 2; r = strncasecmp(ext, typ_tab[mid].ext, ext_len); if(r < 0) { top = mid - 1; } else if(r > 0) { bot = mid + 1; } else if(ext_len < typ_tab[mid].ext_len) { top = mid - 1; } else if(ext_len > typ_tab[mid].ext_len) { bot = mid + 1; } else { type = typ_tab[mid].val; goto done; } } type = default_type; done: me[0] = '\0'; me_len = 0; for(i = n_me_indexes - 1; i >= 0; --i) { if(me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size) { if(me[0] != '\0') { (void) strcpy(&me[me_len], ","); ++me_len; } (void) strcpy(&me[me_len], enc_tab[me_indexes[i]].val); me_len += enc_tab[me_indexes[i]].val_len; } } return type; } static char* file_details(const char* dir, const char* name) { struct stat sb; char f_time[20]; static char encname[1000]; static char buf[2000]; (void) snprintf(buf, sizeof(buf), "%s/%s", dir, name); if(lstat(buf, &sb) < 0) return "???"; (void) strftime(f_time, sizeof(f_time), "%d-%b-%Y %H:%M", localtime(&sb.st_mtime)); strencode(encname, sizeof(encname), name); (void) snprintf(buf, sizeof(buf), "%-50s %15s %14lld\n", encname, name, f_time, (long long int) sb.st_size); return buf; } static char* get_method_str(int m) { switch(m) { case METHOD_GET: return "GET"; case METHOD_HEAD: return "HEAD"; case METHOD_POST: return "POST"; default: return "UNKNOWN"; } } static int get_pathinfo(void) { int r; pathinfo = &file[strlen(file)]; for(;;) { do { --pathinfo; if(pathinfo <= file) { pathinfo = (char*) 0; return -1; } } while(*pathinfo != '/'); *pathinfo = '\0'; r = stat(file, &sb); if(r >= 0) { ++pathinfo; return r; } else { *pathinfo = '/'; } } } static char* get_request_line(void) { int i; char c; for(i = request_idx; request_idx < request_len; ++request_idx) { c = request[request_idx]; if(c == '\012' || c == '\015') { request[request_idx] = '\0'; ++request_idx; if(c == '\015' && request_idx < request_len && request[request_idx] == '\012') { request[request_idx] = '\0'; ++request_idx; } return &(request[i]); } } return (char*) 0; } static void handle_read_timeout(int sig, int is_ssl) { syslog(LOG_INFO, "%.80s connection timed out reading", ntoa(&client_addr)); send_error(408, "Request Timeout", "", "No request appeared within a reasonable time period.", is_ssl); } static void handle_read_timeout_sig(int sig) { handle_read_timeout(0,0); } static void handle_request(int is_ssl, unsigned short conn_port) { char* method_str; char* line; char* cp; int r, file_len, i; const char* index_names[] = {"default.html", "default.htm", "index.html", "index.htm", "index.cgi", "index.sh" }; (void) signal(SIGALRM, handle_read_timeout_sig); (void) alarm(READ_TIMEOUT); remoteuser = (char*) 0; method = METHOD_UNKNOWN; path = (char*) 0; file = (char*) 0; pathinfo = (char*) 0; query = ""; protocol = (char*) 0; status = 0; bytes = -1; authorization = (char*) 0; content_type = (char*) 0; content_length = -1; cookie = (char*) 0; host = (char*) 0; if_modified_since = (time_t) -1; referer = ""; useragent = ""; #ifdef TCP_NOPUSH r = 1; (void) setsockopt(conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &r, sizeof(r)); #endif if(is_ssl) { ssl = SSL_new(ssl_ctx); SSL_set_fd(ssl, conn_fd); int accept_ret = SSL_accept(ssl); if(accept_ret <= 0) { int e = SSL_get_error(ssl, accept_ret); if(e != VERSION_ERROR) { syslog(LOG_CRIT, "error: can't initialize ssl connection, error = %d\n", e); } exit(1); } } start_request(); for(;;) { char buf[10000]; int r = my_read( buf, sizeof(buf), is_ssl ); if(r < 0 && (errno == EINTR || errno == EAGAIN)) { continue; } if(r <= 0) { break; } (void) alarm(READ_TIMEOUT); add_to_request(buf, r); if(strstr(request, "\015\012\015\012") != (char*) 0 || strstr(request, "\012\012") != (char*) 0) { break; } } method_str = get_request_line(); if(method_str == (char*) 0) { send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); } path = strpbrk(method_str, " \t\012\015"); if(path == (char*) 0) { send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); } *path++ = '\0'; path += strspn(path, " \t\012\015"); protocol = strpbrk(path, " \t\012\015"); if(protocol == (char*) 0) { send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); } *protocol++ = '\0'; protocol += strspn(protocol, " \t\012\015"); query = strchr(path, '?'); if(query == (char*) 0) { query = ""; } else { *query++ = '\0'; } while((line = get_request_line()) != (char*) 0) { if(line[0] == '\0') { break; } else if(strncasecmp(line, "Authorization:", 14) == 0) { cp = &line[14]; cp += strspn(cp, " \t"); authorization = cp; } else if(strncasecmp(line, "Content-Length:", 15) == 0) { cp = &line[15]; cp += strspn(cp, " \t"); content_length = atol(cp); } else if(strncasecmp(line, "Content-Type:", 13) == 0) { cp = &line[13]; cp += strspn(cp, " \t"); content_type = cp; } else if(strncasecmp(line, "Cookie:", 7) == 0) { cp = &line[7]; cp += strspn(cp, " \t"); cookie = cp; } else if(strncasecmp(line, "Host:", 5) == 0) { cp = &line[5]; cp += strspn(cp, " \t"); host = cp; if(strchr(host, '/') != (char*) 0 || host[0] == '.') { send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); } } else if(strncasecmp(line, "If-Modified-Since:", 18) == 0) { cp = &line[18]; cp += strspn(cp, " \t"); if_modified_since = dateparse(cp); } else if(strncasecmp(line, "Referer:", 8) == 0) { cp = &line[8]; cp += strspn(cp, " \t"); referer = cp; } else if(strncasecmp(line, "User-Agent:", 11) == 0) { cp = &line[11]; cp += strspn(cp, " \t"); useragent = cp; } } if(strcasecmp(method_str, get_method_str(METHOD_GET)) == 0) { method = METHOD_GET; } else if(strcasecmp(method_str, get_method_str(METHOD_HEAD)) == 0) { method = METHOD_HEAD; } else if(strcasecmp(method_str, get_method_str(METHOD_POST)) == 0) { method = METHOD_POST; } else { send_error(501, "Not Implemented", "", "That method is not implemented.", is_ssl); } strdecode(path, path); if(path[0] != '/') { send_error(400, "Bad Request", "", "Bad filename.", is_ssl); } file = &(path[1]); de_dotdot(file); if(file[0] == '\0') { file = "./"; } if(file[0] == '/' || (file[0] == '.' && file[1] == '.' && (file[2] == '\0' || file[2] == '/'))) { send_error(400, "Bad Request", "", "Illegal filename.", is_ssl); } (void) signal(SIGALRM, handle_write_timeout); (void) alarm(WRITE_TIMEOUT); r = stat(file, &sb); if(r < 0) { r = get_pathinfo(); } if(r < 0) { if(pageNotFoundFile != NULL && host != NULL) { send_redirect("", host, pageNotFoundFile, is_ssl); } else { send_error(404, "Not Found", "", "File not found.", is_ssl); } } file_len = strlen( file ); if(!S_ISDIR(sb.st_mode)) { while(file[file_len - 1] == '/') { file[file_len - 1] = '\0'; --file_len; } do_file(is_ssl, conn_port); } else { char idx[10000]; unsigned char found_index = 0; if(file[file_len - 1] != '/' && pathinfo == (char*) 0) { char location[10000]; if(query[0] != '\0') { (void) snprintf(location, sizeof(location), "Location: %s/?%s", path, query); } else { (void) snprintf(location, sizeof(location), "Location: %s/", path); send_error(302, "Found", location, "Directories must end with a slash.", is_ssl); } } if(defaultPageFile != NULL) { (void) snprintf(idx, sizeof(idx), "%s%s", file, defaultPageFile); if(stat( idx, &sb ) >= 0) { file = idx; do_file(is_ssl, conn_port); found_index = 1; } } for(i = 0; i < (sizeof(index_names) / sizeof(char*)) && found_index == 0; ++i) { (void) snprintf(idx, sizeof(idx), "%s%s", file, index_names[i]); if(stat(idx, &sb) >= 0) { file = idx; do_file(is_ssl, conn_port); found_index = 1; } } if(found_index == 0) { if(allowDirectoryListing == 0) { if(pageNotFoundFile != NULL && host != NULL) { send_redirect("", host, pageNotFoundFile, is_ssl); } else { send_error(404, "Not Found", "", "File not found.", is_ssl); } } else { do_dir(is_ssl); } } } if(is_ssl) { SSL_free(ssl); } } static void handle_sigchld(int sig) { const int oerrno = errno; pid_t pid; int status; (void) signal(SIGCHLD, handle_sigchld); for(;;) { pid = waitpid((pid_t) -1, &status, WNOHANG); if((int) pid == 0) { break; } if((int) pid < 0) { if(errno == EINTR || errno == EAGAIN) { continue; } if(errno != ECHILD) { syslog(LOG_ERR, "child wait - %m"); perror("child wait"); } break; } } errno = oerrno; } static void handle_sighup(int sig) { const int oerrno = errno; (void) signal(SIGHUP, handle_sighup); got_hup = 1; errno = oerrno; } static void handle_sigterm(int sig) { syslog(LOG_NOTICE, "exiting due to signal %d", sig); (void) fprintf(stderr, "%s: exiting due to signal %d\n", argv0, sig); closelog(); exit(1); } static void handle_write_timeout(int sig) { syslog(LOG_INFO, "%.80s connection timed out writing", ntoa(&client_addr)); exit(1); } static int hexit(char c) { if(c >= '0' && c <= '9') { return c - '0'; } if(c >= 'a' && c <= 'f') { return c - 'a' + 10; } if(c >= 'A' && c <= 'F') { return c - 'A' + 10; } return 0; } static void init_mime(void) { int i; qsort(enc_tab, n_enc_tab, sizeof(*enc_tab), (int(*)(const void*, const void*)) ext_compare); qsort(typ_tab, n_typ_tab, sizeof(*typ_tab), (int(*)(const void*, const void*)) ext_compare); for(i = 0; i < n_enc_tab; ++i) { enc_tab[i].ext_len = strlen(enc_tab[i].ext); enc_tab[i].val_len = strlen(enc_tab[i].val); } for(i = 0; i < n_typ_tab; ++i) { typ_tab[i].ext_len = strlen(typ_tab[i].ext); typ_tab[i].val_len = strlen(typ_tab[i].val); } } static int initialize_listen_socket(usockaddr* usaP) { int listen_fd; int i; if(!sockaddr_check(usaP)) { syslog(LOG_ERR, "unknown sockaddr family on listen socket - %d", usaP->sa.sa_family); (void) fprintf(stderr, "%s: unknown sockaddr family on listen socket - %d\n", argv0, usaP->sa.sa_family); return -1; } listen_fd = socket(usaP->sa.sa_family, SOCK_STREAM, 0); if(listen_fd < 0) { if(usaP->sa.sa_family == AF_INET6 && (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT || EPFNOSUPPORT || EAFNOSUPPORT)) { /* IPv6 not compiled into kernel, no big deal, don't print errors */ } else { syslog(LOG_CRIT, "socket %.80s - %m", ntoa(usaP)); perror("socket"); } return -1; } (void) fcntl(listen_fd, F_SETFD, 1); i = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i)) < 0) { syslog(LOG_CRIT, "setsockopt SO_REUSEADDR - %m"); perror("setsockopt SO_REUSEADDR"); return -1; } if(bind(listen_fd, &usaP->sa, sockaddr_len(usaP)) < 0) { syslog(LOG_CRIT, "bind %.80s - %m", ntoa(usaP)); perror("bind"); return -1; } if(listen(listen_fd, 1024) < 0) { syslog(LOG_CRIT, "listen - %m"); perror("listen"); return -1; } return listen_fd; } static void lookup_hostname(usockaddr* usa4P, usockaddr* usa4sP, size_t sa4_len, int* gotv4P, int* gotv4sP, usockaddr* usa6P, usockaddr* usa6sP, size_t sa6_len, int* gotv6P, int* gotv6sP) { int port_index; unsigned short port_list[4]; port_list[0] = port; port_list[1] = sslPort; port_list[2] = 0; *gotv6P = 0; *gotv6sP = 0; *gotv4P = 0; *gotv4sP = 0; for(port_index=0; port_list[port_index] != 0; port_index++) { struct addrinfo hints; char portstr[10]; int gaierr; struct addrinfo* ai; struct addrinfo* ai2; struct addrinfo* aiv6; struct addrinfo* aiv4; (void) memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; (void) snprintf(portstr, sizeof(portstr), "%d", (int) port_list[port_index]); if((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0) { syslog(LOG_CRIT, "getaddrinfo %.80s - %s", hostname, gai_strerror(gaierr)); (void) fprintf(stderr, "%s: getaddrinfo %.80s - %s\n", argv0, hostname, gai_strerror(gaierr)); exit(1); } aiv6 = (struct addrinfo*) 0; aiv4 = (struct addrinfo*) 0; for(ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next) { switch(ai2->ai_family) { case AF_INET6: if(aiv6 == (struct addrinfo*) 0) { aiv6 = ai2; } break; case AF_INET: if(aiv4 == (struct addrinfo*) 0) { aiv4 = ai2; } break; } } if(aiv6 != (struct addrinfo*) 0) { if(sa6_len < aiv6->ai_addrlen) { syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen); (void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen); exit(1); } if(port_index==0) { *gotv6P = 1; (void) memset(usa6P, 0, sa6_len); (void) memmove(usa6P, aiv6->ai_addr, aiv6->ai_addrlen); } else { *gotv6sP = 1; (void) memset(usa6sP, 0, sa6_len); (void) memmove(usa6sP, aiv6->ai_addr, aiv6->ai_addrlen); } } if(aiv4 != (struct addrinfo*) 0) { if(sa4_len < aiv4->ai_addrlen) { syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen); (void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen); exit(1); } if(port_index == 0) { *gotv4P = 1; (void) memset(usa4P, 0, sa4_len); (void) memmove(usa4P, aiv4->ai_addr, aiv4->ai_addrlen); } else { *gotv4sP = 1; (void) memset(usa4sP, 0, sa4_len); (void) memmove(usa4sP, aiv4->ai_addr, aiv4->ai_addrlen); } *gotv4P = 1; } freeaddrinfo(ai); } } int main(int argc, char** argv) { int argn; struct passwd* pwd; uid_t uid = 32767; gid_t gid = 32767; usockaddr host_addr4; usockaddr host_addr6; int gotv4, gotv4s, gotv6, gotv6s; fd_set lfdset; int maxfd; usockaddr usa; int sz, r; char* cp; usockaddr host_addr4s; usockaddr host_addr6s; argv0 = argv[0]; debug = 0; port = 0; dir = (char*) 0; data_dir = (char*) 0; cgi_pattern = DEFAULT_CGI_PATTERN; url_pattern = (char*) 0; no_empty_referers = 0; local_pattern = (char*) 0; charset = DEFAULT_CHARSET; language = DEFAULT_LANGUAGE; p3p = (char*) 0; max_age = -1; user = DEFAULT_USER; hostname = (char*) 0; logfile = (char*) 0; pidfile = (char*) 0; logfp = (FILE*) 0; do_ssl = 0; certfile = DEFAULT_CERTFILE; cipher = (char*) 0; defaultRealmName = NULL; defaultRealmPasswordFile = NULL; defaultPageFile = NULL; pageNotFoundFile = NULL; allowDirectoryListing = 1; sslPort = 0; argn = 1; while(argn < argc && argv[argn][0] == '-') { if(strcmp( argv[argn], "-V" ) == 0) { (void) printf("%s\n", SERVER_SOFTWARE); exit(0); } else if(strcmp(argv[argn], "-C") == 0 && argn + 1 < argc) { ++argn; read_config(argv[argn]); } else if(strcmp(argv[argn], "-D") == 0) { debug = 1; } else if(strcmp(argv[argn], "-S") == 0) { do_ssl = 1; } else if(strcmp(argv[argn], "-E") == 0 && argn + 1 < argc) { ++argn; certfile = argv[argn]; } else if(strcmp(argv[argn], "-Y") == 0 && argn + 1 < argc) { ++argn; cipher = argv[argn]; } else if(strcmp(argv[argn], "-p") == 0 && argn + 1 < argc) { ++argn; port = (unsigned short) atoi(argv[argn]); } else if(strcmp(argv[argn], "-d") == 0 && argn + 1 < argc) { ++argn; dir = argv[argn]; } else if(strcmp(argv[argn], "-dd") == 0 && argn + 1 < argc) { ++argn; data_dir = argv[argn]; } else if(strcmp(argv[argn], "-c") == 0 && argn + 1 < argc) { ++argn; cgi_pattern = argv[argn]; } else if(strcmp(argv[argn], "-u") == 0 && argn + 1 < argc) { ++argn; user = argv[argn]; } else if(strcmp(argv[argn], "-h") == 0 && argn + 1 < argc) { ++argn; hostname = argv[argn]; } else if(strcmp(argv[argn], "-l") == 0 && argn + 1 < argc) { ++argn; logfile = argv[argn]; } else if(strcmp(argv[argn], "-i") == 0 && argn + 1 < argc) { ++argn; pidfile = argv[argn]; } else if(strcmp(argv[argn], "-T") == 0 && argn + 1 < argc) { ++argn; charset = argv[argn]; } else if(strcmp(argv[argn], "-L") == 0 && argn + 1 < argc) { ++argn; language = argv[argn]; } else if(strcmp(argv[argn], "-P") == 0 && argn + 1 < argc) { ++argn; p3p = argv[argn]; } else if(strcmp(argv[argn], "-M") == 0 && argn + 1 < argc) { ++argn; max_age = atoi(argv[argn]); } else if(strcmp(argv[argn], "-DRN") == 0 && argn + 1 < argc) { ++argn; defaultRealmName = argv[argn]; } else if(strcmp(argv[argn], "-DRP") == 0 && argn + 1 < argc) { ++argn; defaultRealmPasswordFile = argv[argn]; } else if(strcmp(argv[argn], "-DPF") == 0 && argn + 1 < argc) { ++argn; defaultPageFile = argv[argn]; } else if(strcmp(argv[argn], "-PNF") == 0 && argn + 1 < argc) { ++argn; pageNotFoundFile = argv[argn]; } else if(strcmp(argv[argn], "-ADL") == 0 && argn + 1 < argc) { ++argn; if(strcmp(argv[argn], "1") == 0 || strcmp(argv[argn], "true") == 0 || strcmp(argv[argn], "TRUE") == 0 || strcmp(argv[argn], "yes") == 0 || strcmp(argv[argn], "YES") == 0) { allowDirectoryListing = 1; } else { allowDirectoryListing = 0; } } else if(strcmp(argv[argn], "-SP") == 0 && argn + 1 < argc) { ++argn; sslPort = (unsigned short) atoi(argv[argn]); } else { usage(); } ++argn; } if(argn != argc) { usage(); } cp = strrchr(argv0, '/'); if(cp != (char*) 0) { ++cp; } else { cp = argv0; } openlog(cp, LOG_NDELAY|LOG_PID, LOG_DAEMON); if(port == 0) { if(sslPort != 0 && do_ssl) { port = sslPort; sslPort = 0; } if(do_ssl) { port = DEFAULT_HTTPS_PORT; } else { port = DEFAULT_HTTP_PORT; } } if(getuid() == 0) { pwd = getpwnam(user); if(pwd == (struct passwd*) 0) { syslog(LOG_CRIT, "unknown user - '%s'", user); (void) fprintf(stderr, "%s: unknown user - '%s'\n", argv0, user); exit(1); } uid = pwd->pw_uid; gid = pwd->pw_gid; } if(logfile != (char*) 0) { logfp = fopen(logfile, "a"); if(logfp == (FILE*) 0) { syslog(LOG_CRIT, "%s - %m", logfile); perror(logfile); exit(1); } if(logfile[0] != '/') { syslog(LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it"); (void) fprintf(stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0); } if(getuid() == 0) { if(fchown(fileno(logfp), uid, gid) < 0) { syslog(LOG_WARNING, "fchown logfile - %m"); perror("fchown logfile"); } } } lookup_hostname(&host_addr4, &host_addr4s, sizeof(struct sockaddr_in), &gotv4, &gotv4s, &host_addr6, &host_addr6s, sizeof(struct sockaddr_in6), &gotv6, &gotv6s); if(hostname == (char*) 0) { (void) gethostname(hostname_buf, sizeof(hostname_buf)); hostname = hostname_buf; } if(!( gotv4 || gotv4s || gotv6 || gotv6s)) { syslog(LOG_CRIT, "can't find any valid address"); (void) fprintf(stderr, "%s: can't find any valid address\n", argv0); exit(1); } listen6_fd = -1; listen6s_fd = -1; listen4_fd = -1; listen4s_fd = -1; if(gotv6) { listen6_fd = initialize_listen_socket(&host_addr6); } if(gotv6s) { listen6s_fd = initialize_listen_socket(&host_addr6s); } if(gotv4) { listen4_fd = initialize_listen_socket(&host_addr4); } if(gotv4s) { listen4s_fd = initialize_listen_socket(&host_addr4s); } if(listen4_fd == -1 && listen6_fd == -1 && listen4s_fd == -1 && listen6s_fd == -1) { syslog(LOG_CRIT, "can't bind to any address"); (void) fprintf(stderr, "%s: can't bind to any address\n", argv0); exit(1); } if(do_ssl) { ssl_ctx = SSL_CTX_new(TLSv1_server_method()); SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, 0); if(certfile[0] != '\0') { if(SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS || SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS) { syslog(LOG_CRIT, "can't load certificate and/or private key\n"); exit(1); } } if(cipher != (char*) 0) { if(SSL_CTX_set_cipher_list(ssl_ctx, cipher) <= 0) { syslog(LOG_CRIT, "can't load certificate and/or private key\n"); exit(1); } } } if(!debug) { if(daemon(1, 0) < 0) { syslog(LOG_CRIT, "daemon - %m"); perror("daemon"); exit(1); } } else { (void) setsid(); } if(pidfile != (char*) 0) { FILE* pidfp = fopen(pidfile, "w"); if(pidfp == (FILE*) 0) { syslog(LOG_CRIT, "%s - %m", pidfile); perror(pidfile); exit(1); } (void) fprintf(pidfp, "%d\n", (int) getpid()); (void) fclose(pidfp); } tzset(); if(getuid() == 0) { if(setgroups(0, (gid_t*) 0) < 0) { syslog(LOG_CRIT, "setgroups - %m"); perror("setgroups"); exit(1); } if(setgid(gid) < 0) { syslog(LOG_CRIT, "setgid - %m"); perror("setgid"); exit(1); } if(initgroups(user, gid ) < 0) { syslog(LOG_ERR, "initgroups - %m"); perror("initgroups"); } } if(dir != (char*) 0) { if(chdir(dir) < 0) { syslog(LOG_CRIT, "chdir - %m"); perror("chdir"); exit(1); } } if(getcwd(cwd, sizeof(cwd) - 1) == NULL) { ; } if(cwd[strlen(cwd) - 1] != '/') { (void) strcat( cwd, "/" ); } if(data_dir != (char*) 0) { if(chdir(data_dir) < 0) { syslog(LOG_CRIT, "data_dir chdir - %m"); perror("data_dir chdir"); exit(1); } } if(getuid() == 0) { if(setuid(uid) < 0) { syslog(LOG_CRIT, "setuid - %m"); perror("setuid"); exit(1); } } (void) signal(SIGTERM, handle_sigterm); (void) signal(SIGINT, handle_sigterm); (void) signal(SIGUSR1, handle_sigterm); (void) signal(SIGHUP, handle_sighup); (void) signal(SIGCHLD, handle_sigchld); (void) signal(SIGPIPE, SIG_IGN); got_hup = 0; init_mime(); if(hostname == (char*) 0) { syslog(LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, (int) port); } else { syslog(LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, hostname, (int) port); } for(;;) { int is_ssl; unsigned short conn_port; if(got_hup) { reopen_logfile(); got_hup = 0; } FD_ZERO(&lfdset); maxfd = -1; if(listen4_fd != -1) { FD_SET(listen4_fd, &lfdset); if(listen4_fd > maxfd) { maxfd = listen4_fd; } } if(listen4s_fd != -1) { FD_SET(listen4s_fd, &lfdset); if(listen4s_fd > maxfd) { maxfd = listen4s_fd; } } if(listen6_fd != -1) { FD_SET(listen6_fd, &lfdset); if(listen6_fd > maxfd) { maxfd = listen6_fd; } } if(listen6s_fd != -1) { FD_SET(listen6s_fd, &lfdset); if(listen6s_fd > maxfd) { maxfd = listen6s_fd; } } if(select(maxfd + 1, &lfdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*) 0) < 0) { if(errno == EINTR || errno == EAGAIN) { continue; } syslog(LOG_CRIT, "select - %m"); perror("select"); exit(1); } is_ssl= 0; conn_port = port; if(do_ssl) { if(sslPort == 0) { is_ssl= 1; } else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) { is_ssl = 1; conn_port = sslPort; } else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) { is_ssl = 1; conn_port = sslPort; } } sz = sizeof(usa); if(listen4_fd != -1 && FD_ISSET(listen4_fd, &lfdset)) { conn_fd = accept(listen4_fd, &usa.sa, &sz); } else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) { conn_fd = accept(listen4s_fd, &usa.sa, &sz); } else if(listen6_fd != -1 && FD_ISSET(listen6_fd, &lfdset)) { conn_fd = accept(listen6_fd, &usa.sa, &sz); } else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) { conn_fd = accept(listen6s_fd, &usa.sa, &sz); } else { syslog(LOG_CRIT, "select failure"); (void) fprintf(stderr, "%s: select failure\n", argv0); exit(1); } if(conn_fd < 0) { if(errno == EINTR || errno == EAGAIN) { continue; } #ifdef EPROTO if(errno == EPROTO) { continue; } #endif syslog(LOG_CRIT, "accept - %m"); perror("accept"); exit(1); } r = fork(); if(r < 0) { syslog(LOG_CRIT, "fork - %m"); perror("fork"); exit(1); } if(r == 0) { client_addr = usa; if(listen4_fd != -1) { (void) close(listen4_fd); } if(listen4s_fd != -1) { (void) close(listen4s_fd); } if(listen6_fd != -1) { (void) close(listen6_fd); } if(listen6s_fd != -1) { (void) close(listen6s_fd); } handle_request(is_ssl, conn_port); exit(0); } (void) close(conn_fd); } } static char** make_argp(void) { char** argp; int argn; char* cp1; char* cp2; argp = (char**) malloc((strlen(query) + 2) * sizeof(char*)); if(argp == (char**) 0) { return (char**) 0; } argp[0] = strrchr(file, '/'); if(argp[0] != (char*) 0) { ++argp[0]; } else { argp[0] = file; } argn = 1; if(strchr(query, '=') == (char*) 0) { for(cp1 = cp2 = query; *cp2 != '\0'; ++cp2) { if(*cp2 == '+') { *cp2 = '\0'; strdecode(cp1, cp1); argp[argn++] = cp1; cp1 = cp2 + 1; } } if(cp2 != cp1) { strdecode(cp1, cp1); argp[argn++] = cp1; } } argp[argn] = (char*) 0; return argp; } static char** make_envp(int is_ssl, unsigned short conn_port) { static char* envp[50]; int envn; char* cp; char buf[256]; envn = 0; envp[envn++] = build_env("PATH=%s", CGI_PATH); envp[envn++] = build_env("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH); envp[envn++] = build_env("SERVER_SOFTWARE=%s", SERVER_SOFTWARE); envp[envn++] = build_env("WEBUI_LANGUAGE=%s", language); cp = hostname; if(cp != (char*) 0) { envp[envn++] = build_env("SERVER_NAME=%s", cp); } envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1"; envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0"; (void) snprintf(buf, sizeof(buf), "%d", (int) conn_port); envp[envn++] = build_env("SERVER_PORT=%s", buf); envp[envn++] = build_env("REQUEST_METHOD=%s", get_method_str(method)); envp[envn++] = build_env("SCRIPT_NAME=%s", path); if(pathinfo != (char*) 0) { envp[envn++] = build_env("PATH_INFO=/%s", pathinfo); (void) snprintf(buf, sizeof(buf), "%s%s", cwd, pathinfo); envp[envn++] = build_env("PATH_TRANSLATED=%s", buf); } if(query[0] != '\0') { envp[envn++] = build_env("QUERY_STRING=%s", query); } envp[envn++] = build_env("REMOTE_ADDR=%s", ntoa(&client_addr)); if(referer[0] != '\0') { envp[envn++] = build_env("HTTP_REFERER=%s", referer); } if(useragent[0] != '\0') { envp[envn++] = build_env("HTTP_USER_AGENT=%s", useragent); } if(cookie != (char*) 0) { envp[envn++] = build_env("HTTP_COOKIE=%s", cookie); } if(host != (char*) 0) { envp[envn++] = build_env("HTTP_HOST=%s", host); } if(content_type != (char*) 0) { envp[envn++] = build_env("CONTENT_TYPE=%s", content_type); } if(content_length != -1) { (void) snprintf(buf, sizeof(buf), "%lu", (unsigned long) content_length); envp[envn++] = build_env("CONTENT_LENGTH=%s", buf); } if(remoteuser != (char*) 0) { envp[envn++] = build_env("REMOTE_USER=%s", remoteuser); } if(authorization != (char*) 0) { envp[envn++] = build_env("AUTH_TYPE=%s", "Basic"); } if(getenv("TZ") != (char*) 0) { envp[envn++] = build_env("TZ=%s", getenv("TZ")); } envp[envn] = (char*) 0; return envp; } static void make_log_entry(void) { char* ru; char url[500]; char bytes_str[40]; time_t now; struct tm* t; const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; char date_nozone[100]; int zone; char sign; char date[100]; if(logfp == (FILE*) 0) { return; } if(protocol == (char*) 0) { protocol = "UNKNOWN"; } if(path == (char*) 0) { path = ""; } if(remoteuser != (char*) 0) { ru = remoteuser; } else { ru = "-"; } now = time((time_t*) 0); (void) snprintf(url, sizeof(url), "%s", path); if(bytes >= 0) { (void) snprintf(bytes_str, sizeof(bytes_str), "%lld", (long long int) bytes ); } else { (void) strcpy(bytes_str, "-"); } t = localtime(&now); (void) strftime(date_nozone, sizeof(date_nozone), cernfmt_nozone, t); zone = t->tm_gmtoff / 60L; if(zone >= 0) { sign = '+'; } else { sign = '-'; zone = -zone; } zone = (zone / 60) * 100 + zone % 60; (void) snprintf(date, sizeof(date), "%s %c%04d", date_nozone, sign, zone); (void) fprintf(logfp, "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", ntoa(&client_addr), ru, date, get_method_str(method), url, protocol, status, bytes_str, referer, useragent); (void) fflush(logfp); } static ssize_t my_read(char* buf, size_t size, int is_ssl) { if(is_ssl) { return SSL_read(ssl, buf, size); } else { return read(conn_fd, buf, size); } } static ssize_t my_write(char* buf, size_t size, int is_ssl) { if(is_ssl) { return SSL_write(ssl, buf, size); } else { return write(conn_fd, buf, size); } } static void no_value_required(char* name, char* value) { if(value != (char*) 0) { (void) fprintf(stderr, "%s: no value required for %s option\n", argv0, name); exit(1); } } static char* ntoa(usockaddr* usaP) { static char str[200]; if(getnameinfo(&usaP->sa, sockaddr_len(usaP), str, sizeof(str), 0, 0, NI_NUMERICHOST) != 0) { str[0] = '?'; str[1] = '\0'; } else if(IN6_IS_ADDR_V4MAPPED(&usaP->sa_in6.sin6_addr) && strncmp(str, "::ffff:", 7) == 0) { (void) strcpy(str, &str[7]); } return str; } static void post_post_garbage_hack(int is_ssl) { char buf[2]; if(is_ssl) { return; } set_ndelay(conn_fd); if(read(conn_fd, buf, sizeof(buf)) < 0) { ; }; clear_ndelay(conn_fd); } static void read_config(char* filename) { FILE* fp; char line[10000]; char* cp; char* cp2; char* name; char* value; fp = fopen(filename, "r"); if(fp == (FILE*) 0) { syslog(LOG_CRIT, "%s - %m", filename); perror(filename); exit(1); } while(fgets(line, sizeof(line), fp) != (char*) 0) { if((cp = strchr(line, '#')) != (char*) 0) { *cp = '\0'; } cp = line; cp += strspn(cp, " \t\012\015"); while(*cp != '\0') { cp2 = cp + strcspn(cp, " \t\012\015"); while(*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\012' || *cp2 == '\015') { *cp2++ = '\0'; } name = cp; value = strchr(name, '='); if(value != (char*) 0) { *value++ = '\0'; } if(strcasecmp(name, "debug") == 0) { no_value_required(name, value); debug = 1; } else if(strcasecmp(name, "port") == 0) { value_required(name, value); port = (unsigned short) atoi(value); } else if(strcasecmp(name, "dir") == 0) { value_required(name, value); dir = e_strdup(value); } else if(strcasecmp(name, "data_dir") == 0) { value_required(name, value); data_dir = e_strdup(value); } else if(strcasecmp(name, "user") == 0) { value_required(name, value); user = e_strdup(value); } else if(strcasecmp(name, "cgipat") == 0) { value_required(name, value); cgi_pattern = e_strdup(value); } else if(strcasecmp(name, "urlpat") == 0) { value_required(name, value); url_pattern = e_strdup(value); } else if(strcasecmp(name, "noemptyreferers") == 0) { value_required(name, value); no_empty_referers = 1; } else if(strcasecmp(name, "localpat") == 0) { value_required(name, value); local_pattern = e_strdup(value); } else if(strcasecmp(name, "host") == 0) { value_required(name, value); hostname = e_strdup(value); } else if(strcasecmp(name, "logfile") == 0) { value_required(name, value); logfile = e_strdup(value); } else if(strcasecmp(name, "pidfile") == 0) { value_required(name, value); pidfile = e_strdup(value); } else if(strcasecmp(name, "charset") == 0) { value_required(name, value); charset = e_strdup(value); } else if(strcasecmp(name, "p3p") == 0) { value_required(name, value); p3p = e_strdup(value); } else if(strcasecmp(name, "max_age") == 0) { value_required(name, value); max_age = atoi(value); } else if(strcasecmp(name, "default_realm_name") == 0) { value_required(name, value); defaultRealmName = e_strdup(value); } else if(strcasecmp(name, "default_realm_password_file") == 0) { value_required(name, value); defaultRealmPasswordFile = e_strdup(value); } else if(strcasecmp(name, "default_page_file") == 0) { value_required(name, value); defaultPageFile = e_strdup(value); } else if(strcasecmp(name, "page_not_found_file") == 0) { value_required(name, value); pageNotFoundFile = e_strdup(value); } else if(strcasecmp(name, "allow_directory_listing") == 0) { value_required(name, value); if(strcmp(value, "1") == 0 || strcmp(value, "true") == 0 || strcmp(value, "TRUE") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "YES") == 0) { allowDirectoryListing = 1; } else { allowDirectoryListing = 0; } } else if(strcasecmp(name, "ssl") == 0) { no_value_required(name, value); do_ssl = 1; } else if(strcasecmp(name, "certfile") == 0) { value_required(name, value); certfile = e_strdup(value); } else if(strcasecmp(name, "cipher") == 0) { value_required(name, value); cipher = e_strdup(value); } else { (void) fprintf(stderr, "%s: unknown config option '%s'\n", argv0, name); exit(1); } cp = cp2; cp += strspn(cp, " \t\012\015"); } } (void) fclose(fp); } static int really_check_referer(void) { char* cp1; char* cp2; char* cp3; char* refhost; char *lp; if(referer == (char*) 0 || referer[0] == '\0' || (cp1 = strstr(referer, "//")) == (char*) 0) { if(no_empty_referers && match(url_pattern, path)) { return 0; } else { return 1; } } cp1 += 2; for(cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2) { continue; } refhost = (char*) e_malloc(cp2 - cp1 + 1); for(cp3 = refhost; cp1 < cp2; ++cp1, ++cp3) { if(isupper(*cp1)) { *cp3 = tolower(*cp1); } else { *cp3 = *cp1; } } *cp3 = '\0'; if(local_pattern != (char*) 0) { lp = local_pattern; } else { lp = hostname; if(lp == (char*) 0) { return 1; } } if(!match(lp, refhost) && match(url_pattern, path)) { return 0; } else { return 1; } } static void reopen_logfile(void) { if(logfp != (FILE*) 0) { (void) fclose(logfp); logfp = (FILE*) 0; } if(logfile != (char*) 0) { syslog(LOG_NOTICE, "re-opening logfile"); logfp = fopen(logfile, "a"); if(logfp == (FILE*) 0) { syslog(LOG_CRIT, "%s - %m", logfile); perror(logfile); exit(1); } } } static void send_authenticate(char* realm, int is_ssl) { char header[10000]; (void) snprintf(header, sizeof(header), "WWW-Authenticate: Basic realm=\"%s\"", realm); send_error(401, "Unauthorized", header, "Authorization required.", is_ssl); } static void send_error(int s, char* title, char* extra_header, char* text, int is_ssl) { add_headers(s, title, extra_header, "", "text/html; charset=%s", (off_t) -1, (time_t) -1); send_error_body(s, title, text); send_error_tail(); send_response(is_ssl); SSL_free(ssl); exit(1); } static void send_error_body(int s, char* title, char* text) { char filename[1000]; char buf[10000]; int buflen; (void) snprintf(filename, sizeof(filename), "%s/err%d.html", ERR_DIR, s); if(send_error_file(filename)) { return; } buflen = snprintf(buf, sizeof(buf), "\nError %d - %s\n\n

Error %d

\n", s, title, s); add_to_response(buf, buflen); buflen = snprintf(buf, sizeof(buf), "%s\n", text); add_to_response(buf, buflen); } static int send_error_file(char* filename) { FILE* fp; char buf[1000]; size_t r; fp = fopen(filename, "r"); if(fp == (FILE*) 0) { return 0; } for(;;) { r = fread(buf, 1, sizeof(buf), fp); if(r == 0) { break; } add_to_response(buf, r); } (void) fclose(fp); return 1; } static void send_error_tail(void) { char buf[500]; int buflen; if(match("**MSIE**", useragent)) { int n; buflen = snprintf(buf, sizeof(buf), "\n"); add_to_response(buf, buflen); } buflen = snprintf(buf, sizeof(buf), "
\n%s\n\n\n", SERVER_SOFTWARE); add_to_response(buf, buflen); } static void send_redirect(char* extra_header, char* hostname, char* new_location, int is_ssl) { int extra_length = 0; char extra_header_buf[5000]; const char *sep = new_location[0] == '/' ? "" : "/"; const char *proto = is_ssl == 1 ? "https://" : "http://"; extra_header = extra_header == NULL ? "" : extra_header; if(strcmp(extra_header, "") == 0) { sprintf(extra_header_buf, "Location: %s%s%s%s", proto, hostname, sep, new_location); } else { sprintf(extra_header_buf, "%s\r\nLocation: %s%s%s%s", extra_header, proto, hostname, sep, new_location); } add_headers(301, "Moved Permanently", extra_header_buf, "", "text/html; charset=%s", (off_t) -1, (time_t) -1); send_error_body(301, "Moved Permanently", "Moved Permanently"); send_error_tail(); send_response(is_ssl); SSL_free(ssl); exit(1); } static void send_response(is_ssl) { (void) my_write(response, response_len, is_ssl); } static void send_via_write(int fd, off_t size, int is_ssl) { if(size <= SIZE_T_MAX) { size_t size_size = (size_t) size; void* ptr = mmap(0, size_size, PROT_READ, MAP_PRIVATE, fd, 0); if(ptr != (void*) -1) { (void) my_write(ptr, size_size, is_ssl); (void) munmap(ptr, size_size); } #ifdef MADV_SEQUENTIAL (void) madvise(ptr, size_size, MADV_SEQUENTIAL); #endif } else { char buf[30000]; ssize_t r, r2; for(;;) { r = read(fd, buf, sizeof(buf)); if(r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r <= 0) { return; } for(;;) { r2 = my_write(buf, r, is_ssl); if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if(r2 != r) { return; } break; } } } } static void set_ndelay(int fd) { int flags, newflags; flags = fcntl(fd, F_GETFL, 0); if(flags != -1) { newflags = flags | (int) O_NDELAY; if(newflags != flags) { (void) fcntl(fd, F_SETFL, newflags); } } } static int sockaddr_check(usockaddr* usaP) { switch (usaP->sa.sa_family) { case AF_INET: return 1; case AF_INET6: return 1; default: return 0; } } static size_t sockaddr_len(usockaddr* usaP) { switch(usaP->sa.sa_family) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); default: return 0; } } static void start_request(void) { request_size = 0; request_idx = 0; } static void start_response(void) { response_size = 0; } static void strdecode(char* to, char* from) { for(; *from != '\0'; ++to, ++from) { if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { *to = hexit(from[1]) * 16 + hexit(from[2]); from += 2; } else { *to = *from; } } *to = '\0'; } static void strencode(char* to, size_t tosize, const char* from) { int tolen; for(tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) { if(isalnum(*from) || strchr("/_.-~", *from) != (char*) 0) { *to = *from; ++to; ++tolen; } else { (void) sprintf(to, "%%%02x", (int) *from & 0xff); to += 3; tolen += 3; } } *to = '\0'; } static void usage(void) { (void) fprintf(stderr, "Usage: %s [-C configfile] [-D] [-S use ssl, if no ssl port is specified all connections will be SSL] [-E certfile] [-SP ssl port] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-l logfile] [-i pidfile] [-T charset] [-L language] [-P P3P] [-M maxage] [-DRN default realm name] [-DRP default realm password file] [-DPF default page file] [-PNF Page to load when 404 Not Found error occurs] \n", argv0); exit(1); } static void value_required(char* name, char* value) { if(value == (char*) 0) { (void) fprintf(stderr, "%s: value required for %s option\n", argv0, name); exit(1); } }