#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_NETINET_TCP_H #include #endif #include #include #include #include #include "alternative.h" #include "mimetype.h" #include "serverconfig.h" #include "libfs.h" #include "liblist.h" #include "libstr.h" #include "cgi.h" #include "userconfig.h" #include "session.h" #include "httpauth.h" #include "send.h" #include "client.h" #include "target.h" #include "log.h" #include "envir.h" #include "global.h" #ifdef HAVE_COMMAND #include "command.h" #endif #ifdef HAVE_SSL #include "libssl.h" #endif #ifdef HAVE_CACHE #include "cache.h" #endif #ifdef HAVE_REWRITE #include "rewrite.h" #endif #define REQUEST_BUFFER_CHUNK 4 * KILOBYTE #define MAIN_LOOP_TIMER 1 #define PTHREAD_STACK_SIZE 512 * KILOBYTE #define fe_MAX_REQUESTSIZE -2 #define fe_TIMEOUT -3 #define fe_CLIENT_DISCONNECTED -4 #define fe_READ_ERROR -5 #define fe_FORCE_QUIT -6 #define rs_NONE 0 #define rs_QUIT_SERVER 1 #define rs_UNBAN_CLIENTS 2 #define rs_TOUCH_LOGFILES 3 #ifdef HAVE_SSL #define RANDOM_BUFFER_SIZE 512 #endif typedef struct { char *config_dir; bool daemon; bool config_check; } t_settings; volatile int received_signal = rs_NONE; char *hs_conlen = "Content-Length: "; char *fb_filesystem = "access denied via filesystem"; char *fb_symlink = "symlink not allowed"; char *fb_accesslist = "access denied via accesslist"; char *fb_alterlist = "access denied via alterlist"; char *version_string = "Hiawatha v"VERSION #ifdef HAVE_CACHE ", cache" #endif #ifdef HAVE_COMMAND ", CommandChannel" #endif #ifdef DEBUG ", debug" #endif #ifdef HAVE_IPV6 ", IPv6" #endif #ifdef HAVE_SSL ", SSL" #endif #ifdef HAVE_REWRITE ", URL rewrite" #endif ; #ifdef DEBUG /* timer functions for debugging */ double t1, t2; struct timeval tp; void start_clock(char *mesg) { fprintf(stderr, "(%5d) START: %s\n", (int)getpid(), mesg); gettimeofday(&tp, NULL); t1 = (double)tp.tv_sec + (1.e-6)*tp.tv_usec; } void stop_clock(void) { gettimeofday(&tp, NULL); t2 = (double)tp.tv_sec + (1.e-6)*tp.tv_usec; fprintf(stderr, "(%5d) time: %f\n", (int)getpid(), t2 - t1); } #endif /* Create all logfiles with the right ownership and accessrights */ void touch_logfiles(t_config *config, char *dir) { t_host *host; touch_logfile(dir, config->system_logfile, 0640, config->server_uid, config->server_gid); if (config->garbage_logfile != NULL) { touch_logfile(dir, config->garbage_logfile, 0640, config->server_uid, config->server_gid); } host = config->first_host; while (host != NULL) { if (host->access_fileptr != NULL) { fflush(host->access_fileptr); } touch_logfile(dir, host->access_logfile, 0640, config->server_uid, config->server_gid); touch_logfile(dir, host->error_logfile, 0640, config->server_uid, config->server_gid); host = host->next; } } /* Check if the requested file is a CGI program. */ t_cgi_type check_target_is_cgi(t_session *session) { t_cgi_handler *cgi; if ((session->fcgi_server = fcgi_server_match(session->config->fcgi_server, &(session->host->fast_cgi), session->file_on_disk)) != NULL) { session->cgi_type = fastcgi; } else if (in_charlist(file_extension(session->file_on_disk), &(session->config->cgi_extension))) { session->cgi_type = program; } else { cgi = session->config->cgi_handler; while (cgi != NULL) { if (in_charlist(file_extension(session->file_on_disk), &(cgi->extension))) { session->cgi_handler = cgi->handler; session->cgi_type = script; break; } cgi = cgi->next; } } return session->cgi_type; } /* Handle an HTTP error. */ int handle_error(t_session *session, int error_code) { char *error_handler = NULL; int length, result = -1; switch (error_code) { case 401: error_handler = session->host->error_401_handler; break; case 403: error_handler = session->host->error_403_handler; break; case 404: error_handler = session->host->error_404_handler; break; case 501: error_handler = session->host->error_501_handler; break; case 503: error_handler = session->host->error_503_handler; break; } if (error_handler == NULL) { return 0; } session->return_code = error_code; session->error_code = error_code; session->handling_error = true; session->mimetype = NULL; if (session->file_on_disk != NULL) { free(session->file_on_disk); } length = session->host->website_root_len; if ((session->file_on_disk = (char*)malloc(length + strlen(error_handler) + 4)) == NULL) { /* + 3 for .gz (gzip encoding) */ result = 500; } else { memcpy(session->file_on_disk, session->host->website_root, length + 1); strcpy(session->file_on_disk + length, error_handler); session->cgi_type = none; check_target_is_cgi(session); if (session->cgi_type != none) { result = execute_cgi(session); } else switch (is_directory(session->file_on_disk)) { case error: result = 500; break; case yes: result = 301; break; case no: result = send_file(session); break; case no_access: result = 403; break; case not_found: result = 404; break; } } switch (result) { case 301: log_file_error(session, session->file_on_disk, "ErrorHandler is a directory"); break; case 403: log_file_error(session, session->file_on_disk, "no access to ErrorHandler"); break; case 404: log_file_error(session, session->file_on_disk, "ErrorHandler not found"); break; case 500: log_file_error(session, error_handler, "internal error for ErrorHandler"); session->keep_alive = false; break; case 503: log_file_error(session, error_handler, "FastCGI for ErrorHandler not available"); break; } return result; } /* Handle the request from a client. */ int handle_request(t_session *session) { int retval = 200; if ((strcmp(session->method, "GET") == 0) || session->head_request) { if (session->cgi_type != none) { session->body = NULL; retval = execute_cgi(session); } else { retval = send_file(session); } if (retval == 404) { if (session->host->show_index && (session->uri[session->uri_len - 1] == '/')) { retval = show_index(session); } } } else if (strcmp(session->method, "POST") == 0) { if (session->cgi_type != none) { retval = execute_cgi(session); } else { retval = 405; } } else if (strcmp(session->method, "OPTIONS") == 0) { retval = handle_options_request(session); } else if (strcmp(session->method, "TRACE") == 0) { if (session->binding->enable_trace) { retval = handle_trace_request(session); } else { retval = 501; } } else if (strcmp(session->method, "PUT") == 0) { if (session->binding->enable_alter) { retval = handle_put_request(session); } else { retval = 501; } } else if (strcmp(session->method, "DELETE") == 0) { if (session->binding->enable_alter) { retval = handle_delete_request(session); } else { retval = 501; } } else if (strcmp(session->method, "CONNECT") == 0) { retval = 501; } else if (strcmp(session->method, "PROPFIND") == 0) { retval = 501; } else if (strcmp(session->method, "PROPPATCH") == 0) { retval = 501; } else if (strcmp(session->method, "MKCOL") == 0) { retval = 501; } else if (strcmp(session->method, "COPY") == 0) { retval = 501; } else if (strcmp(session->method, "MOVE") == 0) { retval = 501; } else if (strcmp(session->method, "LOCK") == 0) { retval = 501; } else if (strcmp(session->method, "UNLOCK") == 0) { retval = 501; } else { retval = 400; } return retval; } /* Read the request from a client socket. */ int fetch_request(t_session *session) { char *new_reqbuf, *strstart, *strend; long bytes_read, header_length = -1, content_length = -1; int result = 200, timer, write_bytes; fd_set read_fds; struct timeval select_timeout; bool keep_reading = true, store_on_disk = false; int upload_handle = -1; select_timeout.tv_sec = 1; select_timeout.tv_usec = 0; if (session->kept_alive == 0) { timer = session->binding->time_for_1st_request; } else { timer = session->binding->time_for_request; } #ifdef DEBUG set_client_status(session, "fetch_request()"); #endif do { /* Check if requestbuffer contains a complete request. */ if (session->request != NULL) { if (header_length == -1) { if ((strstart = strstr(session->request, "\r\n\r\n")) != NULL) { *(strstart + 2) = '\0'; header_length = strstart + 4 - session->request; session->header_length = header_length; session->put_method = (strncmp(session->request, "PUT ", 4) == 0); store_on_disk = (session->put_method && session->binding->enable_alter); if (store_on_disk) { if ((session->uploaded_file = (char*)malloc(session->config->upload_directory_len + 15)) != NULL) { strcpy(session->uploaded_file, session->config->upload_directory); strcpy(session->uploaded_file + session->config->upload_directory_len, "/upload_XXXXXX"); if ((upload_handle = mkstemp(session->uploaded_file)) == -1) { free(session->uploaded_file); session->uploaded_file = NULL; } } if (session->uploaded_file == NULL) { log_error(session, "can't create tempfile for PUT request"); result = 500; break; } session->uploaded_size = session->bytes_in_buffer - header_length; if (write_buffer(upload_handle, session->request + header_length, session->uploaded_size) == -1) { result = 500; break; } session->bytes_in_buffer = header_length; } } } if (header_length != -1) { if (content_length == -1) { if ((strstart = strcasestr(session->request, hs_conlen)) != NULL) { strstart += 16; if ((strend = strstr(strstart, "\r\n")) != NULL) { *strend = '\0'; content_length = str2int(strstart); *strend = '\r'; if (content_length == -1) { result = 400; break; } if (store_on_disk) { session->content_length = 0; if (content_length > session->binding->max_upload_size) { result = 413; break; } session->buffer_size = header_length + REQUEST_BUFFER_CHUNK; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { result = fe_READ_ERROR; break; } } else { session->content_length = content_length; if (header_length + content_length > session->binding->max_request_size) { result = fe_MAX_REQUESTSIZE; break; } if (header_length + content_length > session->buffer_size) { session->buffer_size = header_length + content_length; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { result = fe_READ_ERROR; break; } } } } } else { session->content_length = 0; if (store_on_disk) { result = 411; } break; } } if (content_length > -1) { if (store_on_disk) { if (session->uploaded_size == content_length) { break; } } else { if (session->bytes_in_buffer >= header_length + content_length) { /* Received a complete request */ break; } } } } } #ifdef DEBUG set_client_status(session, "fetch_request()::select()"); #endif FD_ZERO(&read_fds); FD_SET(session->client_socket, &read_fds); switch (select(session->client_socket + 1, &read_fds, NULL, NULL, &select_timeout)) { case -1: if (errno != EINTR) { result = fe_READ_ERROR; keep_reading = false; } break; case 0: if (session->force_quit) { result = fe_FORCE_QUIT; keep_reading = false; } else if (--timer <= 0) { result = fe_TIMEOUT; keep_reading = false; } else { select_timeout.tv_sec = 1; select_timeout.tv_usec = 0; } break; default: if ((content_length == -1) && ((session->buffer_size - session->bytes_in_buffer) < 256)) { session->buffer_size += REQUEST_BUFFER_CHUNK; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { result = fe_READ_ERROR; keep_reading = false; break; } } /* Read from socket. */ #ifdef DEBUG set_client_status(session, "fetch_request()::recv()"); #endif #ifdef HAVE_SSL if (session->binding->use_ssl) { bytes_read = ssl_receive(session->ssl_data, session->request + session->bytes_in_buffer, session->buffer_size - session->bytes_in_buffer); } else #endif bytes_read = recv(session->client_socket, session->request + session->bytes_in_buffer, session->buffer_size - session->bytes_in_buffer, 0); switch (bytes_read) { case -1: if (errno != EINTR) { result = fe_READ_ERROR; keep_reading = false; } break; case 0: result = fe_CLIENT_DISCONNECTED; keep_reading = false; break; default: if (store_on_disk) { /* Write to file on disk */ write_bytes = bytes_read; if (session->uploaded_size + bytes_read > content_length) { write_bytes -= ((session->uploaded_size + bytes_read) - content_length); } if (write_buffer(upload_handle, session->request + header_length, write_bytes) == -1) { result = 500; keep_reading = false; break; } if ((session->uploaded_size += write_bytes) > session->binding->max_upload_size) { result = 413; keep_reading = false; break; } if (write_bytes < bytes_read) { memmove(session->request + header_length, session->request + header_length + write_bytes, bytes_read - write_bytes); session->bytes_in_buffer += bytes_read - write_bytes; keep_reading = false; } } else { /* Read into memory */ session->bytes_in_buffer += bytes_read; *(session->request + session->bytes_in_buffer) = '\0'; if (session->bytes_in_buffer > session->binding->max_request_size) { keep_reading = false; result = fe_MAX_REQUESTSIZE; break; } } } } } while (keep_reading); if (upload_handle != -1) { fsync(upload_handle); close(upload_handle); } #ifdef HAVE_COMMAND increment_transfer(TRANSFER_RECEIVED, header_length + content_length); #endif #ifdef DEBUG set_client_status(session, NULL); fprintf(stderr, "Request size: %ld + %ld\n", header_length, content_length); #endif return result; } /* Convert the requestbuffer to a session record. */ int parse_request(t_session *session, int total_bytes) { int retval = 200; char *request_end, *str_end, *conn; request_end = session->request + total_bytes; /* Request method */ session->method = str_end = session->request; while ((*str_end != ' ') && (str_end != request_end)) { str_end++; } if (str_end == request_end) { return 400; } *str_end = '\0'; session->uri = ++str_end; /* URI */ while ((*str_end != ' ') && (str_end != request_end)) { str_end++; } if (str_end == request_end) { return 400; } *(str_end++) = '\0'; #ifdef HAVE_REWRITE if ((session->script_url = strdup(session->uri)) == NULL) { return -1; } #endif /* Protocol version */ if (min_strlen(str_end, 10) == false) { return 400; } else if (memcmp(str_end, "HTTP/", 5) != 0) { return 400; } session->http_version = str_end; str_end += 7; if ((*(str_end - 1) != '.') || (*(str_end + 1) != '\r') || (*(str_end + 2) != '\n')) { return 400; } else if (*(str_end - 2) != '1') { return 505; } *(str_end + 1) = '\0'; /* Body and other request headerlines */ if (session->content_length > 0) { session->body = session->request + session->header_length; } session->headerfields = parse_headerfields(str_end + 3); session->hostname = get_headerfield("Host:", session->headerfields); if ((conn = get_headerfield("Connection:", session->headerfields)) != NULL) { conn = strlower(remove_spaces(conn)); } session->keep_alive = false; switch (*str_end) { case '0': if ((conn != NULL) && (session->kept_alive < session->binding->max_keepalive)) { if (strcasecmp(conn, "keep-alive") == 0) { session->keep_alive = true; } } break; case '1': if (session->hostname == NULL) { retval = 400; } else if (session->kept_alive < session->binding->max_keepalive) { session->keep_alive = true; if (conn != NULL) { if (strcmp(conn, "close") == 0) { session->keep_alive = false; } } } break; default: retval = 505; break; } if (session->keep_alive) { session->kept_alive++; } if (strcmp(session->method, "HEAD") == 0) { session->head_request = true; } return retval; } /* Convert the request uri to a filename. */ int uri_to_path(t_session *session) { size_t length, alias_length = 0; char *strstart, *strend; t_keyvalue *alias; int retval; /* Requested file in userdirectory? */ if (session->host->user_websites && (session->uri_len >= 3)) { if (*(session->uri + 1) == '~') { strstart = session->uri + 1; if ((strend = strchr(strstart, '/')) == NULL) { return 301; } else if ((length = strend - strstart) > 1) { if ((session->local_user = (char*)malloc(length + 1)) == NULL) { return 500; } memcpy(session->local_user, strstart, length); *(session->local_user + length) = '\0'; if (duplicate_host(session)) { if ((retval = get_homedir(session, session->local_user + 1)) != 200) { return retval; } session->host->error_401_handler = NULL; session->host->error_403_handler = NULL; session->host->error_404_handler = NULL; session->host->error_501_handler = NULL; session->host->error_503_handler = NULL; } else { return 500; } } else { /* uri is '/~/...' */ return 404; } } } /* Search for an alias. */ alias = session->host->alias; while (alias != NULL) { alias_length = strlen(alias->key); if (strncmp(session->uri, alias->key, alias_length) == 0) { if ((*(session->uri + alias_length) == '/') || (*(session->uri + alias_length) == '\0')) { session->alias_used = true; break; } } alias = alias->next; } /* Allocate memory */ if (alias == NULL) { length = session->host->website_root_len; } else { length = strlen(alias->value); } length += session->uri_len + strlen(session->host->start_file); if ((session->file_on_disk = (char*)malloc(length + 4)) == NULL) { /* + 3 for '.gz' (gzip encoding) */ return 500; } /* Copy stuff */ if (alias == NULL) { length = session->host->website_root_len; memcpy(session->file_on_disk, session->host->website_root, length); strstart = session->uri; if (session->local_user != NULL) { strstart += strlen(session->local_user) + 1; } } else { length = strlen(alias->value); memcpy(session->file_on_disk, alias->value, length); strstart = session->uri + alias_length; } strcpy(session->file_on_disk + length, strstart); return 200; } t_access allow_client(t_session *session) { char *x_forwarded_for; t_ip_addr forwarded_ip; t_access access; if ((access = ip_allowed(&(session->ip_address), session->host->access_list)) != allow) { return access; } else if ((x_forwarded_for = get_headerfield("X-Forwarded-For:", session->headerfields)) == NULL) { return allow; } else if (parse_ip(x_forwarded_for, &forwarded_ip) == -1) { return allow; } else if (ip_allowed(&forwarded_ip, session->host->access_list) == deny) { return deny; } return unknown; } int get_path_info(t_session *session) { char *uri, *dot, *slash, *old_cgi_handler; t_cgi_type cgi_type, old_cgi_type; int ofs = 0; int len; if (session->alias_used) { return 0; } len = session->host->website_root_len; if (len >= strlen(session->file_on_disk)) { return -1; } uri = session->file_on_disk + len; if ((dot = strchr(uri, '.')) == NULL) { return 0; } if ((slash = strchr(dot, '/')) == NULL) { return 0; } old_cgi_handler = session->cgi_handler; old_cgi_type = session->cgi_type; *slash = '\0'; cgi_type = check_target_is_cgi(session); *slash = '/'; session->cgi_handler = old_cgi_handler; session->cgi_type = old_cgi_type; if (cgi_type != none) { if ((session->path_info = strdup(slash)) == NULL) { return -1; } ofs = slash - uri; if (ofs < session->uri_len) { *(session->uri + ofs) = '\0'; } else { return -1; } *slash = '\0'; return 1; } return 0; } /* Serve the client that connected to the webserver */ int serve_client(t_session *session) { int result, length; char *search, *conffile, *qmark; t_host *host_record; bool file_not_found = false; t_access access; t_fsbool is_dir; #ifdef HAVE_REWRITE int i; char *new_uri; #endif if ((result = fetch_request(session)) != 200) { return result; } else if ((result = parse_request(session, session->header_length + session->content_length)) != 200) { return result; } if (session->hostname != NULL) { if ((host_record = get_hostrecord(session->config->first_host, session->hostname, session->binding)) != NULL) { session->host = host_record; } } session->host->access_time = time(NULL); #ifdef HAVE_REWRITE /* URL rewrite */ for (i = 0; i < session->host->rewrite_rules.size; i++) { if ((result = rewrite_url(session->uri, session->host->rewrite_rules.item[i], session->config->url_rewrite, &new_uri)) == RW_ERROR) { return 500; } if (new_uri != NULL) { if (register_tempdata(&(session->tempdata), new_uri, tc_data) == -1) { free(new_uri); return 500; } #ifdef DEBUG fprintf(stderr, "Rewrite URL: [%s] --> [%s]\n", session->uri, new_uri); #endif session->uri = new_uri; } if (result == RW_DENY_ACCESS) { return 403; } } #endif /* Find GET data */ qmark = session->uri; while (*qmark != '\0') { if (*qmark == '?') { *qmark = '\0'; session->vars = qmark + 1; break; } qmark++; } session->uri_len = strlen(session->uri); url_decode(session->uri); if ((session->vars != NULL) && (session->host->secure_url)) { remove_dangerous_chars(session->vars); } if (session->host->require_resolve_ip && (session->ip_resolved == false)) { /* See if the client IP-address is resolvable */ if (gethostbyaddr((unsigned char*)&(session->ip_address), 4, AF_INET) == NULL) { log_error(session, "can't resolve IP"); return 403; } session->ip_resolved = true; } if (valid_uri(session->uri) == false) { if (session->put_method) { if (session->binding->enable_alter) { return 403; } return 501; } return 404; } else if ((result = uri_to_path(session)) != 200) { return result; } /* Load configfile from directories */ search = session->file_on_disk; while (*search != '\0') { if (*search == '/') { length = search - session->file_on_disk + 1; if ((conffile = (char*)malloc(length + 10)) != NULL) { memcpy(conffile, session->file_on_disk, length); memcpy(conffile + length, ".hiawatha\0", 10); if (duplicate_host(session)) { if (read_user_configfile(conffile, session->host, &(session->tempdata)) > 0) { log_file_error(session, conffile, "error in configuration file"); result = 500; } } else { result = 500; } free(conffile); } else { result = 500; } } if (result == 200) { search++; } else { return result; } } #ifdef HAVE_SSL if (session->host->require_ssl && (session->binding->use_ssl == false)) { session->cause_of_301 = require_ssl; return 301; } #endif if (client_is_rejected_bot(session)) { log_error(session, "bot rejected"); return 403; } if ((result = copy_directory_settings(session)) != 200) { return result; } switch (access = allow_client(session)) { case deny: log_error(session, fb_accesslist); return 403; case allow: break; case pwd: case unknown: if (http_authentication_oke(session, access == unknown) == false) { return 401; } } switch (is_directory(session->file_on_disk)) { case error: return 500; case yes: session->uri_is_dir = true; break; case no: if ((session->put_method == false) && (session->host->enable_path_info)) { if (get_path_info(session) == -1) { return 500; } } else if ((search = strrchr(session->file_on_disk, '/')) != NULL) { *search = '\0'; is_dir = is_directory(session->file_on_disk); *search = '/'; if (is_dir == no) { return 404; } } break; case no_access: log_error(session, fb_filesystem); return 403; case not_found: if (session->host->use_gz_file) { if ((search = get_headerfield("Accept-Encoding:", session->headerfields)) != NULL) { if ((strstr(search, "gzip")) != NULL) { session->accept_gzip = true; } } } if ((session->accept_gzip == false) && (session->put_method == false)) { if (session->host->fast_cgi.size == 0) { return 404; } else { file_not_found = true; } } } length = strlen(session->file_on_disk); if (*(session->file_on_disk + length - 1) == '/') { if (session->uri_is_dir) { strcpy(session->file_on_disk + length, session->host->start_file); } else { return 404; } } else if (session->uri_is_dir) { return 301; } check_target_is_cgi(session); if (file_not_found && (session->cgi_type != fastcgi)) { return 404; } return handle_request(session); } /* Request has been handled, handle the return code. */ void handle_request_result(t_session *session, int result) { if ((result > 0) && (result != 400)) { log_request(session, result); } else { session->keep_alive = false; } switch (result) { case fe_MAX_REQUESTSIZE: log_system(session, "Maximum request size reached"); send_code(session, 413); if ((session->config->ban_on_max_request_size > 0) && ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_max_request_size, session->config->kick_on_ban); log_system(session, "Client banned because of sending a too large request"); } break; case fe_TIMEOUT: if (session->kept_alive == 0) { send_code(session, 408); if ((session->config->ban_on_timeout > 0) && ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_timeout, session->config->kick_on_ban); log_system(session, "Client banned because of connection timeout"); } else { log_system(session, "Timeout while waiting for request"); } } break; case fe_CLIENT_DISCONNECTED: if (session->kept_alive == 0) { log_system(session, "Client disconnected"); } break; case fe_READ_ERROR: if (errno != ECONNRESET) { log_system(session, "Error while reading request"); #ifdef DEBUG log_system(session, strerror(errno)); #endif } break; case fe_FORCE_QUIT: log_system(session, "Client kicked"); break; case rr_CMD_INJECTION: log_request(session, result); if (ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_cmdi, session->config->kick_on_ban); log_system(session, "Client banned because of command injection"); } send_code(session, 400); break; case rr_SQL_INJECTION: log_request(session, result); if (ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_sqli, session->config->kick_on_ban); log_system(session, "Client banned because of SQL injection"); } send_code(session, 400); break; case 200: break; case 201: case 204: case 304: case 412: if (session->data_sent == false) { send_header(session, result); send_buffer(session, "Content-Length: 0\r\n\r\n", 21); } break; case 411: case 413: session->keep_alive = false; if (session->data_sent == false) { send_header(session, result); send_buffer(session, "Content-Length: 0\r\n\r\n", 21); } break; case 400: log_garbage(session); if (session->data_sent == false) { if (send_code(session, result) == -1) { session->keep_alive = false; } } if ((session->config->ban_on_garbage > 0) && ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_garbage, session->config->kick_on_ban); log_system(session, "Client banned because of sending garbage"); } break; case 401: case 403: case 404: case 501: case 503: if (session->data_sent == false) { switch (handle_error(session, result)) { case -1: session->keep_alive = false; break; case 200: break; default: if (session->data_sent == false) { if (send_code(session, result) == -1) { session->keep_alive = false; } } } } break; case 500: session->keep_alive = false; default: if (session->data_sent == false) { send_code(session, result); } } } /* Handle the connection of a client. */ void connection_handler(t_session *session) { int result; #ifdef HAVE_SSL if (session->binding->use_ssl) { session->socket_open = ssl_accept(session->client_socket, &(session->ssl_data), session->binding->ssl_context); } else #endif session->socket_open = true; if (session->socket_open) { do { result = serve_client(session); handle_request_result(session, result); send_buffer(session, NULL, 0); /* Flush the output-buffer */ reset_session(session); if (session->keep_alive && (session->config->ban_on_flooding > 0)) { if (client_is_flooding(session)) { if (ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), session->config->ban_on_flooding, session->config->kick_on_ban); log_system(session, "Client banned because of flooding"); session->keep_alive = false; } } } } while (session->keep_alive); destroy_session(session); close_socket(session); } else { close(session->client_socket); } if (session->config->reconnect_delay > 0) { mark_client_for_removal(session, session->config->reconnect_delay); } else { remove_client(session, true); } /* Client session ends here */ } /* Signal handlers */ void SEGV_handler(int sig) { syslog(LOG_DAEMON | LOG_ALERT, "segmentation fault!"); exit(EXIT_FAILURE); } void TERM_handler(int sig) { received_signal = rs_QUIT_SERVER; } void USR1_handler(int sig) { received_signal = rs_UNBAN_CLIENTS; } void USR2_handler(int sig) { received_signal = rs_TOUCH_LOGFILES; } /* Fill a filedescriptor set with sockets. */ #ifndef HAVE_COMMAND int fill_read_fds(fd_set *read_fds, t_binding *binding) { #else int fill_read_fds(fd_set *read_fds, t_binding *binding, t_binding *bind_command) { t_admin *admin; #endif int highest_fd = 0; FD_ZERO(read_fds); while (binding != NULL) { FD_SET(binding->socket, read_fds); if (binding->socket > highest_fd) { highest_fd = binding->socket; } binding = binding->next; } #ifdef HAVE_COMMAND while (bind_command != NULL) { FD_SET(bind_command->socket, read_fds); if (bind_command->socket > highest_fd) { highest_fd = bind_command->socket; } bind_command = bind_command->next; } admin = first_admin(); while (admin != NULL) { FD_SET(admin->socket, read_fds); if (admin->socket > highest_fd) { highest_fd = admin->socket; } admin = next_admin(); } #endif return highest_fd; } /* Create a socketlist. */ int bind_sockets(t_binding *binding) { char ip_address[MAX_IP_STR_LEN], separator; struct sockaddr_in saddr4; #ifdef HAVE_IPV6 struct sockaddr_in6 saddr6; #endif int domain, one, result; while (binding != NULL) { #ifdef HAVE_IPV6 domain = (binding->interface.family == AF_INET ? PF_INET : PF_INET6); #else domain = PF_INET; #endif if ((binding->socket = socket(domain, SOCK_STREAM, 0)) == -1) { perror("socket()"); return -1; } one = 1; if (setsockopt(binding->socket, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(int)) == -1) { perror("setsockopt(SOL_SOCKET, SO_REUSEADDR)"); } one = 1; if (setsockopt(binding->socket, IPPROTO_TCP, TCP_NODELAY, (void*)&one, sizeof(int)) == -1) { perror("setsockopt(IPPROTO_TCP, TCP_NODELAY)"); } if (binding->interface.family == AF_INET) { /* IPv4 */ memset(&saddr4, 0, sizeof(struct sockaddr_in)); //saddr4.sin_len = sizeof(struct sockaddr_in); saddr4.sin_family = AF_INET; memcpy(&(saddr4.sin_addr.s_addr), &(binding->interface.value), IPv4_LEN); saddr4.sin_port = htons(binding->port); result = bind(binding->socket, (struct sockaddr*)&saddr4, sizeof(struct sockaddr_in)); separator = ':'; #ifdef HAVE_IPV6 } else if (binding->interface.family == AF_INET6) { /* IPv6 */ memset(&saddr6, 0, sizeof(struct sockaddr_in6)); //saddr6.sin6_len = sizeof(struct sockaddr_in6); saddr6.sin6_family = AF_INET6; memcpy(&(saddr6.sin6_addr.s6_addr), &(binding->interface.value), IPv6_LEN); saddr6.sin6_port = htons(binding->port); result = bind(binding->socket, (struct sockaddr*)&saddr6, sizeof(struct sockaddr_in6)); separator = '.'; #endif } else { fprintf(stderr, "Unknown protocol (family %d).\n", binding->interface.family); return -1; } if (result == -1) { /* Handle error */ if (inet_ntop(binding->interface.family, &(binding->interface.value), ip_address, MAX_IP_STR_LEN) == NULL) { strcpy(ip_address, "?.?.?.?"); } fprintf(stderr, "Error binding %s%c%d\n", ip_address, separator, binding->port); return -1; } binding = binding->next; } return 0; } /* Accept or deny an incoming connection. */ int accept_connection(t_binding *binding, t_config *config) { socklen_t size; bool kick_client; t_session *session; struct sockaddr_in caddr4; #ifdef HAVE_IPV6 struct sockaddr_in6 caddr6; #endif pthread_attr_t child_attr; pthread_t child_thread; int total_conns, one; if ((session = (t_session*)malloc(sizeof(t_session))) == NULL) { return -1; } session->config = config; session->binding = binding; init_session(session); if (binding->interface.family == AF_INET) { /* IPv4 */ size = sizeof(struct sockaddr_in); memset((void*)&caddr4, 0, (size_t)size); if ((session->client_socket = accept(binding->socket, (struct sockaddr*)&caddr4, &size)) == -1) { free(session); if (errno != EINTR) { return -1; } return 0; } session->ip_address.family = AF_INET; session->ip_address.size = IPv4_LEN; memcpy(&(session->ip_address.value), (char*)&caddr4.sin_addr.s_addr, session->ip_address.size); #ifdef HAVE_IPV6 } else if (binding->interface.family == AF_INET6) { /* IPv6 */ size = sizeof(struct sockaddr_in6); memset((void*)&caddr6, 0, (size_t)size); if ((session->client_socket = accept(binding->socket, (struct sockaddr*)&caddr6, &size)) == -1) { free(session); if (errno != EINTR) { return -1; } return 0; } session->ip_address.family = AF_INET6; session->ip_address.size = IPv6_LEN; memcpy(&(session->ip_address.value), (char*)&caddr6.sin6_addr.s6_addr, session->ip_address.size); #endif } else { log_system(session, "Incoming connection via unknown protocol"); return -1; } kick_client = true; if ((total_conns = connection_allowed(&(session->ip_address), config->connections_per_ip, config->total_connections)) >= 0) { if (total_conns < (config->total_connections >> 2)) { one = 1; if (setsockopt(session->client_socket, IPPROTO_TCP, TCP_NODELAY, (void*)&one, sizeof(int)) == -1) { close(session->client_socket); free(session); return -1; } } if (pthread_attr_init(&child_attr) != 0) { log_system(session, "pthread init error"); } else if (pthread_attr_setdetachstate(&child_attr, PTHREAD_CREATE_DETACHED) != 0) { log_system(session, "pthread set detach state error"); } else if (pthread_attr_setstacksize(&child_attr, PTHREAD_STACK_SIZE) != 0) { log_system(session, "pthread set stack size error"); } else if (add_client(session) == 0) { if (pthread_create(&child_thread, &child_attr, (void*)connection_handler, (void*)session) == 0) { /* Thread started */ kick_client = false; } else { remove_client(session, false); } } } else switch (total_conns) { case ca_TOOMUCH_PERIP: if ((config->ban_on_max_per_ip > 0) && ip_allowed(&(session->ip_address), session->config->banlist_mask)) { ban_ip(&(session->ip_address), config->ban_on_max_per_ip, config->kick_on_ban); } log_system(session, "Maximum number of connections for IP address reached"); break; case ca_TOOMUCH_TOTAL: log_system(session, "Maximum number of total connections reached"); break; case ca_BANNED: if (config->reban_during_ban && ip_allowed(&(session->ip_address), session->config->banlist_mask)) { reban_ip(&(session->ip_address)); } #ifdef HAVE_COMMAND increment_counter(COUNTER_DENY); #endif break; } if (kick_client) { close(session->client_socket); free(session); } return 0; } /* Run the Hiawatha webserver. */ int run_server(t_settings *settings) { char *main_configfile = "httpd.conf"; int highest_fd; bool must_quit = false; #ifdef HAVE_COMMAND t_admin *admin; struct sockaddr_in caddr; socklen_t size; int admin_socket; #endif #ifdef HAVE_SSL #ifndef HAVE_DEV_URANDOM char random_buffer[RANDOM_BUFFER_SIZE]; #endif #endif pid_t pid; t_binding *binding; t_config *config; fd_set read_fds; struct timeval select_timeout; struct stat status; mode_t access_rights; signal(SIGPIPE, SIG_IGN); #ifndef OPENBSD signal(SIGCHLD, SIG_IGN); #endif signal(SIGTSTP, SIG_IGN); signal(SIGABRT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGSEGV, SEGV_handler); config = default_config(); if (chdir(settings->config_dir) == -1) { perror(settings->config_dir); return EXIT_FAILURE; } else if (settings->config_check) { printf("Using %s\n", settings->config_dir); } if (read_main_configfile(main_configfile, config, settings->config_check) == -1) { return EXIT_FAILURE; } else if (check_configuration(config) == -1) { return EXIT_FAILURE; } if (read_mimetypes(config->mimetype_config, &(config->mimetype)) == -1) { fprintf(stderr, "Error while reading mimetype configuration.\n"); return EXIT_FAILURE; } if (settings->config_check) { printf("Configuration oke.\n"); return EXIT_SUCCESS; } /* Bind Serverports */ if (bind_sockets(config->binding) == -1) { return EXIT_FAILURE; } #ifdef HAVE_SSL #ifdef HAVE_DEV_URANDOM ssl_init(NULL, 0); #else fill_random_buffer(config, random_buffer, RANDOM_BUFFER_SIZE); ssl_init(random_buffer, RANDOM_BUFFER_SIZE); #endif binding = config->binding; while (binding != NULL) { if (binding->use_ssl) { if ((binding->ssl_context = ssl_binding(binding->server_key, binding->ca_cert, binding->verify_depth, config->dh_file, config->allowed_ciphers)) == NULL) { perror("bind https"); return EXIT_FAILURE; } } binding = binding->next; } #endif #ifdef HAVE_COMMAND /* Bind CommandChannels */ if (bind_sockets(config->command_port) == -1) { return EXIT_FAILURE; } #endif /* Create the upload directory for PUT requests */ if (config->create_upload_dir) { if (mkdir(config->upload_directory, 0733) == -1) { if (errno != EEXIST) { fprintf(stderr, "Error while creating UploadDirectory '%s'\n", config->upload_directory); return EXIT_FAILURE; } } if (stat(config->upload_directory, &status) == -1) { perror("stat(UploadDirectory)"); return EXIT_FAILURE; } access_rights = 01733; if (status.st_uid != 0) { if (getuid() == 0) { if (chown(config->upload_directory, 0, 0) == -1) { perror("chown(UploadDirectory, 0, 0)"); return EXIT_FAILURE; } } else { access_rights = 01333; } } if ((status.st_mode & 07777) != access_rights) { if (chmod(config->upload_directory, access_rights) == -1) { fprintf(stderr, "Can't change access permissions of UploadDirectory '%s'.\n", config->upload_directory); return EXIT_FAILURE; } } } touch_logfiles(config, config->server_root); tzset(); clearenv(); /* Become a daemon */ if (settings->daemon) { switch (pid = fork()) { case -1: perror("fork()"); return EXIT_FAILURE; case 0: if (setsid() == -1) { perror("setsid()"); return EXIT_FAILURE; } break; default: log_pid(config, pid); return EXIT_SUCCESS; } } else { log_pid(config, getpid()); } /* Change server root */ if (config->server_root != NULL) { do { if (chdir(config->server_root) != -1) { if (chroot(config->server_root) != -1) { break; } } fprintf(stderr, "\nError while changing root to %s!\n", config->server_root); return EXIT_FAILURE; } while (false); } else { chdir("/"); } /* Change userid */ if (getuid() == 0) do { if (setgroups(config->groups.number, config->groups.array) != -1) { if (setgid(config->server_gid) != -1) { if (setuid(config->server_uid) != -1) { break; } } } fprintf(stderr, "\nError while changing uid/gid!\n"); return EXIT_FAILURE; } while (false); if (settings->daemon == false) { printf("Press Ctrl-C to shutdown the Hiawatha webserver.\n"); signal(SIGINT, TERM_handler); } else { signal(SIGINT, SIG_IGN); } signal(SIGTERM, TERM_handler); signal(SIGUSR1, USR1_handler); signal(SIGUSR2, USR2_handler); /* Start listening for incoming connections */ binding = config->binding; while (binding != NULL) { if (listen(binding->socket, 16) == -1) { perror("listen(http(s))"); return EXIT_FAILURE; } binding = binding->next; } #ifdef HAVE_COMMAND binding = config->command_port; while (binding != NULL) { if (listen(binding->socket, 1) == -1) { perror("listen(commandchannel)"); return EXIT_FAILURE; } binding = binding->next; } #endif select_timeout.tv_sec = MAIN_LOOP_TIMER; select_timeout.tv_usec = 0; init_log_module(); init_client_module(); init_load_balancer(config->fcgi_server); #ifdef HAVE_CACHE init_cache_module(); #endif #ifdef HAVE_COMMAND init_command_module(); #endif log_string(config->system_logfile, "Hiawatha v"VERSION" started"); do { #ifdef HAVE_COMMAND highest_fd = fill_read_fds(&read_fds, config->binding, config->command_port); #else highest_fd = fill_read_fds(&read_fds, config->binding); #endif switch (select(highest_fd + 1, &read_fds, NULL, NULL, &select_timeout)) { case -1: if (errno != EINTR) { log_string(config->system_logfile, "Fatal error selecting connection"); sleep(1); } break; case 0: select_timeout.tv_sec = MAIN_LOOP_TIMER; select_timeout.tv_usec = 0; /* Client checks */ check_ban_list(config); check_delayed_remove_timers(config); /* FastCGI check */ check_load_balancer(config); /* AccessLogfile check */ log_close(config->first_host, false); #ifdef HAVE_CACHE /* Cache check */ check_cache(); #endif #ifdef HAVE_COMMAND /* CommandChannel check */ check_admin_list(); #endif switch (received_signal) { case rs_NONE: break; case rs_QUIT_SERVER: must_quit = true; break; case rs_UNBAN_CLIENTS: unban_ip(0); received_signal = rs_NONE; break; case rs_TOUCH_LOGFILES: log_close(config->first_host, true); touch_logfiles(config, NULL); received_signal = rs_NONE; break; } break; default: #ifdef HAVE_COMMAND /* Connected admins */ admin = first_admin(); while (admin != NULL) { if (FD_ISSET(admin->socket, &read_fds)) { if (handle_admin(admin, config) == cc_DISCONNECT) { remove_admin(admin->socket); } } admin = next_admin(); } #endif /* HTTP(S) ports */ binding = config->binding; while (binding != NULL) { if (FD_ISSET(binding->socket, &read_fds)) { if (accept_connection(binding, config) != 0) { log_string(config->system_logfile, "Fatal error accepting connection"); usleep(1000); break; } } binding = binding->next; } #ifdef HAVE_COMMAND /* Command ports */ binding = config->command_port; while (binding != NULL) { if (FD_ISSET(binding->socket, &read_fds)) { size = sizeof(struct sockaddr_in); memset((void*)&caddr, 0, (size_t)size); if ((admin_socket = accept(binding->socket, (struct sockaddr*)&caddr, &size)) == -1) { if (errno != EINTR) { log_string(config->system_logfile, "Fatal error accepting CommandChannel connection"); usleep(1000); break; } } else { if (add_admin(admin_socket) == -1) { close(admin_socket); } } } binding = binding->next; } #endif } } while (must_quit == false); signal(SIGTERM, SIG_DFL); disconnect_clients(config); #ifdef HAVE_COMMAND disconnect_admins(); #endif close_bindings(config->binding); #ifdef HAVE_COMMAND binding = config->command_port; while (binding != NULL) { close(binding->socket); binding = binding->next; } #endif log_string(config->system_logfile, "Hiawatha v"VERSION" stopped"); log_close(config->first_host, true); return EXIT_SUCCESS; } void show_help(char *hiawatha) { printf("Usage: %s [options]\n", hiawatha); printf("Options: -c : path to where the configrationfiles are located.\n"); printf(" -d: don't fork to the background.\n"); printf(" -h: show this information and exit.\n"); printf(" -k: check configuration and exit.\n"); printf(" -v: show version and compile information and exit.\n"); } /* Main and stuff... */ int main(int argc, char *argv[]) { int i = 0; t_settings settings; /* Default settings */ settings.config_dir = CONFIG_DIR; settings.daemon = true; settings.config_check = false; /* Read commandline parameters */ while (++i < argc) { if (strcmp(argv[i], "-c") == 0) { if (++i < argc) { settings.config_dir = argv[i]; } else { fprintf(stderr, "Specify a directory.\n"); return EXIT_FAILURE; } } else if (strcmp(argv[i], "-d") == 0) { settings.daemon = false; } else if (strcmp(argv[i], "-h") == 0) { show_help(argv[0]); return EXIT_SUCCESS; } else if (strcmp(argv[i], "-k") == 0) { settings.config_check = true; } else if (strcmp(argv[i], "-v") == 0) { printf("%s\n", version_string); return EXIT_SUCCESS; } else { fprintf(stderr, "Unknown option. Use '-h' for help.\n"); return EXIT_FAILURE; } } /* Run Hiawatha */ return run_server(&settings); }