int write_from_pipe(request * req) { int bytes_written, bytes_to_write = req->header_end - req->header_line; if (bytes_to_write == 0) { if (req->cgi_status == CGI_DONE) return 0; req->status = PIPE_READ; req->header_end = req->header_line = req->buffer; return 1; } bytes_written = write(req->fd, req->header_line, bytes_to_write); if (bytes_written == -1) { if (errno == EWOULDBLOCK || errno == EAGAIN) return -1; /* request blocked at the pipe level, but keep going */ else if (errno == EINTR) return 1; else { req->status = DEAD; send_r_error(req); /* maybe superfluous */ log_error_doc(req); perror("pipe write"); return 0; } } req->header_line += bytes_written; req->filepos += bytes_written; return 1; }
int process_header_end(request * req) { if (!req->logline) { send_r_error(req); return 0; } /* Percent-decode request */ if (unescape_uri(req->request_uri, &(req->query_string)) == 0) { log_error_doc(req); fputs("Problem unescaping uri\n", stderr); send_r_bad_request(req); return 0; } /* clean pathname */ clean_pathname(req->request_uri); if (req->request_uri[0] != '/') { send_r_bad_request(req); return 0; } if (translate_uri(req) == 0) { /* unescape, parse uri */ SQUASH_KA(req); return 0; /* failure, close down */ } if (req->method == M_POST) { req->post_data_fd = create_temporary_file(1, NULL, 0); if (req->post_data_fd == 0) return(0); return(1); /* success */ } if (req->is_cgi) { return init_cgi(req); } req->status = WRITE; return init_get(req); /* get and head */ }
/* * Name: boa_perror * * Description: logs an error to user and error file both * */ void boa_perror(request * req, const char *message) { log_error_doc(req); perror(message); /* don't need to save errno because log_error_doc does */ send_r_error(req); }
static int index_directory(request * req, char *dest_filename) { DIR *request_dir; FILE *fdstream; struct dirent *dirbuf; off_t bytes = 0; char *escname = NULL; if (chdir(req->pathname) == -1) { if (errno == EACCES || errno == EPERM) { send_r_forbidden(req); } else { log_error_doc(req); perror("chdir"); send_r_bad_request(req); } return -1; } request_dir = opendir("."); if (request_dir == NULL) { int errno_save = errno; send_r_error(req); log_error_doc(req); fprintf(stderr, "opendir failed on directory \"%s\": ", req->pathname); errno = errno_save; perror("opendir"); return -1; } fdstream = fopen(dest_filename, "w"); if (fdstream == NULL) { boa_perror(req, "dircache fopen"); closedir(request_dir); return -1; } bytes += fprintf(fdstream, "<HTML><HEAD>\n<TITLE>Index of %s</TITLE>\n</HEAD>\n\n", req->request_uri); bytes += fprintf(fdstream, "<BODY>\n\n<H2>Index of %s</H2>\n\n<PRE>\n", req->request_uri); while ((dirbuf = readdir(request_dir))) { if (!strcmp(dirbuf->d_name, ".")) continue; if (!strcmp(dirbuf->d_name, "..")) { bytes += fprintf(fdstream, " [DIR] <A HREF=\"../\">Parent Directory</A>\n"); continue; } /* FIXME: ought to use (as-yet unwritten) html_escape_string */ escname = escape_string(dirbuf->d_name, NULL); if (escname != NULL) { bytes += fprintf(fdstream, " <A HREF=\"%s\">%s</A>\n", escname, dirbuf->d_name); free(escname); escname = NULL; } } closedir(request_dir); bytes += fprintf(fdstream, "</PRE>\n\n</BODY>\n</HTML>\n"); fclose(fdstream); if (chdir(server_root) == -1) perror("chdir to server root failed"); req->filesize = bytes; /* for logging transfer size */ return 0; /* success */ }
int init_get(request * req) { int data_fd, saved_errno; struct stat statbuf; volatile off_t bytes_free; data_fd = open(req->pathname, O_RDONLY|O_LARGEFILE); saved_errno = errno; /* might not get used */ while (use_lang_rewrite && data_fd == -1 && errno == ENOENT) { /* We cannot open that file - Check whether we can rewrite it * to a different language suffix. We only support filenames * of the format: "foo.ll.html" as an alias for "foo.html". */ unsigned int len; len = strlen(req->pathname); if (len < 6 || strcmp (req->pathname + len - 5, ".html")) break; /* does not end in ".html" */ if (len > 8 && req->pathname[len-8] == '.' && req->pathname[len-7] >= 'a' && req->pathname[len-7] <= 'z' && req->pathname[len-6] >= 'a' && req->pathname[len-6] <= 'z') { /* The request was for a language dependent file. Strip * it and try the generic form. */ char save_name[8]; strcpy (save_name, req->pathname + len - 7); strcpy (req->pathname + len - 7, "html"); data_fd = open(req->pathname, O_RDONLY); if (data_fd == -1) strcpy (req->pathname + len - 7, save_name); break; } else if ( 0 ) { /* Fixme: Other items to try from the list of accepted_languages */ data_fd = open(req->pathname, O_RDONLY); } else break; } #ifdef GUNZIP if (data_fd == -1 && errno == ENOENT) { /* cannot open */ /* it's either a gunzipped file or a directory */ char gzip_pathname[MAX_PATH_LENGTH]; unsigned int len; len = strlen(req->pathname); if (len + 4 > sizeof(gzip_pathname)) { log_error_doc(req); fprintf(stderr, "Pathname + .gz too long! (%s)\n", req->pathname); send_r_bad_request(req); return 0; } memcpy(gzip_pathname, req->pathname, len); memcpy(gzip_pathname + len, ".gz", 3); gzip_pathname[len + 3] = '\0'; data_fd = open(gzip_pathname, O_RDONLY|O_LARGEFILE); if (data_fd != -1) { close(data_fd); req->response_status = R_REQUEST_OK; if (req->pathname) free(req->pathname); req->pathname = strdup(gzip_pathname); if (!req->pathname) { boa_perror(req, "strdup req->pathname for gzipped filename " __FILE__ ":" STR(__LINE__)); return 0; } if (req->http_version != HTTP09) { req_write(req, http_ver_string(req->http_version)); req_write(req, " 200 OK-GUNZIP" CRLF); print_http_headers(req); print_content_type(req); print_last_modified(req); req_write(req, CRLF); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } } #endif if (data_fd == -1) { log_error_doc(req); errno = saved_errno; perror("document open"); if (saved_errno == ENOENT) send_r_not_found(req); else if (saved_errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); return 0; } #ifdef ACCESS_CONTROL if (!access_allow(req->pathname)) { send_r_forbidden(req); return 0; } #endif fstat(data_fd, &statbuf); if (S_ISDIR(statbuf.st_mode)) { /* directory */ close(data_fd); /* close dir */ if (req->pathname[strlen(req->pathname) - 1] != '/') { char buffer[3 * MAX_PATH_LENGTH + 128]; unsigned int len; #ifdef ALLOW_LOCAL_REDIRECT len = strlen(req->request_uri); if (len + 2 > sizeof(buffer)) { send_r_error(req); return 0; } memcpy(buffer, req->request_uri, len); buffer[len] = '/'; buffer[len+1] = '\0'; #else char *host = server_name; unsigned int l2; char *port = NULL; const char *prefix = hsts_header? "https://" : "http://"; static unsigned int l3 = 0; static unsigned int l4 = 0; if (l4 == 0) { l4 = strlen(prefix); } len = strlen(req->request_uri); if (!port && server_port != 80 && !no_redirect_port) { port = strdup(simple_itoa(server_port)); if (port == NULL) { errno = ENOMEM; boa_perror(req, "Unable to perform simple_itoa conversion on server port!"); return 0; } l3 = strlen(port); } /* l3 and l4 are done */ if (req->host) { /* only shows up in vhost mode */ /* what about the port? (in vhost_mode?) */ /* we don't currently report ports that differ * from out "bound" (listening) port, so we don't care */ host = req->host; } l2 = strlen(host); if (server_port != 80 && !no_redirect_port) { if (l4 + l2 + 1 + l3 + len + 1 > sizeof(buffer)) { errno = ENOMEM; boa_perror(req, "buffer not large enough for directory redirect"); return 0; } memcpy(buffer, prefix, l4); memcpy(buffer + l4, host, l2); buffer[l4 + l2] = ':'; memcpy(buffer + l4 + l2 + 1, port, l3); memcpy(buffer + l4 + l2 + 1 + l3, req->request_uri, len); buffer[l4 + l2 + 1 + l3 + len] = '/'; buffer[l4 + l2 + 1 + l3 + len + 1] = '\0'; } else { if (l4 + l2 + len + 1 > sizeof(buffer)) { errno = ENOMEM; boa_perror(req, "buffer not large enough for directory redirect"); return 0; } memcpy(buffer, prefix, l4); memcpy(buffer + l4, host, l2); memcpy(buffer + l4 + l2, req->request_uri, len); buffer[l4 + l2 + len] = '/'; buffer[l4 + l2 + len + 1] = '\0'; } #endif /* ALLOW LOCAL REDIRECT */ send_r_moved_perm(req, buffer); return 0; } data_fd = get_dir(req, &statbuf); /* updates statbuf */ if (data_fd < 0) /* couldn't do it */ return 0; /* errors reported by get_dir */ else if (data_fd == 0 || data_fd == 1) return data_fd; /* else, data_fd contains the fd of the file... */ } if (!S_ISREG(statbuf.st_mode)) { /* regular file */ log_error_doc(req); fprintf(stderr, "Resulting file is not a regular file.\n"); send_r_bad_request(req); close(data_fd); return 0; } /* If-UnModified-Since asks * is the file newer than date located in time_cval * yes -> return 412 * no -> return 200 * * If-Modified-Since asks * is the file date less than or same as the date located in time_cval * yes -> return 304 * no -> return 200 * * If-Unmodified-Since overrides If-Modified-Since */ /* if (req->headers[H_IF_UNMODIFIED_SINCE] && modified_since(&(statbuf.st_mtime), req->headers[H_IF_UNMODIFIED_SINCE])) { send_r_precondition_failed(req); return 0; } else */ if (req->if_modified_since && !modified_since(&(statbuf.st_mtime), req->if_modified_since)) { send_r_not_modified(req); close(data_fd); return 0; } req->filesize = statbuf.st_size; req->last_modified = statbuf.st_mtime; /* ignore if-range without range */ if (req->header_ifrange && !req->ranges) req->header_ifrange = NULL; /* we don't support it yet */ req->header_ifrange = NULL; /* parse ranges now */ /* we have to wait until req->filesize exists to fix them up */ /* fixup handles handles communicating with the client */ /* ranges_fixup logs as appropriate, and sends * send_r_invalid_range on error. */ if (req->filesize == 0) { if (req->http_version < HTTP11) { send_r_request_ok(req); close(data_fd); return 0; } send_r_no_content(req); close(data_fd); return 0; } if (req->ranges && !ranges_fixup(req)) { close(data_fd); return 0; } /* if no range has been set, use default range */ #if 0 DEBUG(DEBUG_RANGE) { log_error_time(); fprintf(stderr, "if-range: %s\time_cval: %d\tmtime: %d\n", req->header_ifrange, req->time_cval, statbuf->st_mtime); } #endif /* If the entity tag given in the If-Range header matches the current entity tag for the entity, then the server should provide the specified sub-range of the entity using a 206 (Partial content) response. If the entity tag does not match, then the server should return the entire entity using a 200 (OK) response. */ /* IF we have range data *and* no if-range or if-range matches... */ #ifdef MAX_FILE_MMAP if (req->filesize > MAX_FILE_MMAP) { req->data_fd = data_fd; req->status = IOSHUFFLE; } else #endif { /* NOTE: I (Jon Nelson) tried performing a read(2) * into the output buffer provided the file data would * fit, before mmapping, and if successful, writing that * and stopping there -- all to avoid the cost * of a mmap. Oddly, it was *slower* in benchmarks. */ req->mmap_entry_var = find_mmap(data_fd, &statbuf); if (req->mmap_entry_var == NULL) { req->data_fd = data_fd; req->status = IOSHUFFLE; } else { req->data_mem = req->mmap_entry_var->mmap; close(data_fd); /* close data file */ } } if (!req->ranges) { req->ranges = range_pool_pop(); req->ranges->start = 0; req->ranges->stop = -1; if (!ranges_fixup(req)) { return 0; } send_r_request_ok(req); } else { /* FIXME: support if-range header here, by the following logic: * if !req->header_ifrange || st_mtime > header_ifrange, * send_r_partial_content * else * reset-ranges, etc... */ if (!req->header_ifrange) { send_r_partial_content(req); } else { /* either no if-range or the if-range does not match */ ranges_reset(req); req->ranges = range_pool_pop(); req->ranges->start = 0; req->ranges->stop = -1; if (!ranges_fixup(req)) { return 0; } send_r_request_ok(req); } } if (req->method == M_HEAD) { return complete_response(req); } bytes_free = 0; if (req->data_mem) { /* things can really go tilt if req->buffer_end > BUFFER_SIZE, * but basically that can't happen */ /* We lose statbuf here, so make sure response has been sent */ bytes_free = BUFFER_SIZE - req->buffer_end; /* 256 bytes for the **trailing** headers */ /* bytes is now how much the buffer can hold * after the headers */ } if (req->data_mem && bytes_free > 256) { unsigned int want; Range *r; r = req->ranges; want = (r->stop - r->start) + 1; if (bytes_free > want) bytes_free = want; else { /* bytes_free <= want */ ; } if (setjmp(env) == 0) { handle_sigbus = 1; memcpy(req->buffer + req->buffer_end, req->data_mem + r->start, bytes_free); handle_sigbus = 0; /* OK, SIGBUS **after** this point is very bad! */ } else { /* sigbus! */ log_error_doc(req); reset_output_buffer(req); send_r_error(req); log_error("Got SIGBUS in memcpy\n"); return 0; } req->buffer_end += bytes_free; req->bytes_written += bytes_free; r->start += bytes_free; if (bytes_free == want) { /* this will fit due to the 256 extra bytes_free */ return complete_response(req); } } /* We lose statbuf here, so make sure response has been sent */ return 1; }
int process_header_end(request * req) { #ifdef CHECK_IP_MAC char mac[18]; /*Naughty, assuming we are on ethernet!!!*/ #endif /*CHECK_IP_MAC*/ if (!req->logline) { send_r_error(req); return 0; } /*MATT2 - I figured this was a good place to check for the MAC address*/ #ifdef CHECK_IP_MAC if(get_mac_from_IP(mac, req->remote_ip_addr)) do_mac_crap(req->remote_ip_addr, mac); else; /*they could be on a remote lan, or just not in the arp cache*/ #endif #ifdef USE_BROWSERMATCH browser_match_request(req); #endif #ifndef NO_REFERER_LOG if (req->referer) log_referer(req); #endif #ifndef NO_AGENT_LOG if (req->user_agent) log_user_agent(req); #endif if (translate_uri(req) == 0) { /* unescape, parse uri */ SQUASH_KA(req); return 0; /* failure, close down */ } #ifdef USE_NLS #ifdef USE_NLS_REFERER_REDIR if (!req->cp_name) { if (!nls_try_redirect(req)) return 0; } #endif nls_set_codepage(req); #endif #ifdef USE_AUTH closelog(); openlog("boa", LOG_PID, LOG_AUTHPRIV); if (!auth_authorize(req)) { openlog("boa", LOG_PID, 0); return 0; } #endif if (req->method == M_POST) { char *tmpfilep = (char *) tmpnam(NULL); if (!tmpfilep) { #if 0 boa_perror(req, "tmpnam"); #endif return 0; } /* open temp file for post data */ if ((req->post_data_fd = open(tmpfilep, O_RDWR | O_CREAT)) == -1) { #if 0 boa_perror(req, "tmpfile open"); #endif return 0; } req->post_file_name = strdup(tmpfilep); return 1; } if (req->is_cgi) return init_cgi(req); req->status = WRITE; return init_get(req); /* get and head */ }
void process_requests(void) { int retval = 0; request *current, *trailer; current = request_ready; while (current) { #ifdef CRASHDEBUG crashdebug_current = current; #endif if (current->buffer_end) { req_flush(current); if (current->status == CLOSE) retval = 0; else retval = 1; } else { switch (current->status) { case READ_HEADER: case ONE_CR: case ONE_LF: case TWO_CR: retval = read_header(current); break; case BODY_READ: retval = read_body(current); break; case BODY_WRITE: retval = write_body(current); break; case WRITE: retval = process_get(current); break; case PIPE_READ: retval = read_from_pipe(current); break; case PIPE_WRITE: retval = write_from_pipe(current); break; default: retval = 0; #if 0 fprintf(stderr, "Unknown status (%d), closing!\n", current->status); #endif break; } } if (lame_duck_mode) SQUASH_KA(current); switch (retval) { case -1: /* request blocked */ trailer = current; current = current->next; block_request(trailer); break; default: /* everything else means an error, jump ship */ send_r_error(current); /* fall-through */ case 0: /* request complete */ trailer = current; current = current->next; free_request(&request_ready, trailer); break; case 1: /* more to do */ current->time_last = time_counter; current = current->next; break; } } #ifdef CRASHDEBUG crashdebug_current = current; #endif }
int init_cgi(request * req) { int child_pid; int pipes[2]; int use_pipes = 0; SQUASH_KA(req); if (req->is_cgi) { if (complete_env(req) == 0) { return 0; } } if (req->is_cgi == CGI || 1) { use_pipes = 1; if (pipe(pipes) == -1) { log_error_time(); perror("pipe"); return 0; } /* set the read end of the socket to non-blocking */ if (set_nonblock_fd(pipes[0]) == -1) { log_error_time(); perror("cgi-fcntl"); close(pipes[0]); close(pipes[1]); return 0; } } child_pid = fork(); switch(child_pid) { case -1: /* fork unsuccessful */ log_error_time(); perror("fork"); if (use_pipes) { close(pipes[0]); close(pipes[1]); } send_r_error(req); /* FIXME: There is aproblem here. send_r_error would work for NPH and CGI, but not for GUNZIP. Fix that. */ /* i'd like to send_r_error, but.... */ return 0; break; case 0: /* child */ if (req->is_cgi == CGI || req->is_cgi == NPH) { char *foo = strdup(req->pathname); char *c; if (!foo) { WARN("unable to strdup pathname for req->pathname"); _exit(1); } c = strrchr(foo, '/'); if (c) { ++c; *c = '\0'; } else { /* we have a serious problem */ log_error_time(); perror("chdir"); if (use_pipes) close(pipes[1]); _exit(1); } if (chdir(foo) != 0) { log_error_time(); perror("chdir"); if (use_pipes) close(pipes[1]); _exit(1); } } if (use_pipes) { close(pipes[0]); /* tie cgi's STDOUT to it's write end of pipe */ if (dup2(pipes[1], STDOUT_FILENO) == -1) { log_error_time(); perror("dup2 - pipes"); close(pipes[1]); _exit(1); } close(pipes[1]); if (set_block_fd(STDOUT_FILENO) == -1) { log_error_time(); perror("cgi-fcntl"); _exit(1); } } else { /* tie stdout to socket */ if (dup2(req->fd, STDOUT_FILENO) == -1) { log_error_time(); perror("dup2 - fd"); _exit(1); } /* Switch socket flags back to blocking */ if (set_block_fd(req->fd) == -1) { log_error_time(); perror("cgi-fcntl"); _exit(1); } } /* tie post_data_fd to POST stdin */ if (req->method == M_POST) { /* tie stdin to file */ lseek(req->post_data_fd, SEEK_SET, 0); dup2(req->post_data_fd, STDIN_FILENO); close(req->post_data_fd); } /* Close access log, so CGI program can't scribble * where it shouldn't */ close_access_log(); /* * tie STDERR to cgi_log_fd * cgi_log_fd will automatically close, close-on-exec rocks! * if we don't tied STDERR (current log_error) to cgi_log_fd, * then we ought to close it. */ //if (!cgi_log_fd) // dup2(devnullfd, STDERR_FILENO); //else // dup2(cgi_log_fd, STDERR_FILENO); if (req->is_cgi) { char *aargv[CGI_ARGC_MAX + 1]; create_argv(req, aargv); execve(req->pathname, aargv, req->cgi_env); } else { if (req->pathname[strlen(req->pathname) - 1] == '/') execl(dirmaker, dirmaker, req->pathname, req->request_uri, NULL); #ifdef GUNZIP else execl(GUNZIP, GUNZIP, "--stdout", "--decompress", req->pathname, NULL); #endif } /* execve failed */ WARN(req->pathname); _exit(1); break; default: /* parent */ /* if here, fork was successful */ if (verbose_cgi_logs) { log_error_time(); fprintf(stderr, "Forked child \"%s\" pid %d\n", req->pathname, child_pid); } if (req->method == M_POST) { close(req->post_data_fd); /* child closed it too */ req->post_data_fd = 0; } /* NPH, GUNZIP, etc... all go straight to the fd */ if (!use_pipes) return 0; close(pipes[1]); req->data_fd = pipes[0]; req->status = PIPE_READ; if (req->is_cgi == CGI) { req->cgi_status = CGI_PARSE; /* got to parse cgi header */ /* for cgi_header... I get half the buffer! */ req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2); } else { req->cgi_status = CGI_BUFFER; /* I get all the buffer! */ req->header_line = req->header_end = req->buffer; } /* reset req->filepos for logging (it's used in pipe.c) */ /* still don't know why req->filesize might be reset though */ req->filepos = 0; break; } return 1; }
int get_dir(request * req, struct stat *statbuf) { char pathname_with_index[MAX_PATH_LENGTH]; int data_fd; if (directory_index) { /* look for index.html first?? */ strcpy(pathname_with_index, req->pathname); strcat(pathname_with_index, directory_index); /* sprintf(pathname_with_index, "%s%s", req->pathname, directory_index); */ data_fd = open(pathname_with_index, O_RDONLY); if (data_fd != -1) { /* user's index file */ strcpy(req->request_uri, directory_index); /* for mimetype */ fstat(data_fd, statbuf); return data_fd; } if (errno == EACCES) { send_r_forbidden(req); return -1; } else if (errno != ENOENT) { /* if there is an error *other* than EACCES or ENOENT */ send_r_not_found(req); return -1; } #ifdef GUNZIP /* if we are here, trying index.html didn't work * try index.html.gz */ strcat(pathname_with_index, ".gz"); data_fd = open(pathname_with_index, O_RDONLY); if (data_fd != -1) { /* user's index file */ close(data_fd); req->response_status = R_REQUEST_OK; SQUASH_KA(req); if (req->pathname) free(req->pathname); req->pathname = strdup(pathname_with_index); if (!req->pathname) { log_error_time(); perror("strdup"); send_r_error(req); return 0; } if (!req->simple) { req_write(req, "HTTP/1.0 200 OK-GUNZIP\r\n"); print_http_headers(req); print_last_modified(req); req_write(req, "Content-Type: "); req_write(req, get_mime_type(directory_index)); req_write(req, "\r\n\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } #endif } /* only here if index.html, index.html.gz don't exist */ if (dirmaker != NULL) { /* don't look for index.html... maybe automake? */ req->response_status = R_REQUEST_OK; SQUASH_KA(req); /* the indexer should take care of all headers */ if (!req->simple) { req_write(req, "HTTP/1.0 200 OK\r\n"); print_http_headers(req); print_last_modified(req); req_write(req, "Content-Type: text/html\r\n\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); /* in this case, 0 means success */ } else if (cachedir) { return get_cachedir_file(req, statbuf); } else { /* neither index.html nor autogenerate are allowed */ send_r_forbidden(req); return -1; /* nothing worked */ } }
int init_get(request * req) { int data_fd, saved_errno, dynamic=0; struct stat statbuf; volatile int bytes; data_fd = open(req->pathname, O_RDONLY); saved_errno = errno; /* might not get used */ #ifdef GUNZIP if (data_fd == -1 && errno == ENOENT) { /* cannot open */ /* it's either a gunzipped file or a directory */ char gzip_pathname[MAX_PATH_LENGTH]; int len; len = strlen(req->pathname); memcpy(gzip_pathname, req->pathname, len); memcpy(gzip_pathname + len, ".gz", 3); gzip_pathname[len + 3] = '\0'; data_fd = open(gzip_pathname, O_RDONLY); if (data_fd != -1) { close(data_fd); req->response_status = R_REQUEST_OK; if (req->pathname) free(req->pathname); req->pathname = strdup(gzip_pathname); if (!req->pathname) { log_error_time(); perror("strdup"); send_r_error(req); return 0; } if (!req->simple) { req_write(req, "HTTP/1.0 200 OK-GUNZIP\r\n"); print_http_headers(req); print_content_type(req); print_last_modified(req); req_write(req, "\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } } #endif if (data_fd == -1) { log_error_doc(req); errno = saved_errno; perror("document open"); if (saved_errno == ENOENT) send_r_not_found(req); else if (saved_errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); return 0; } fstat(data_fd, &statbuf); if (S_ISDIR(statbuf.st_mode)) { /* directory */ close(data_fd); /* close dir */ if (req->pathname[strlen(req->pathname) - 1] != '/') { char buffer[3 * MAX_PATH_LENGTH + 128]; if (server_port != 80) sprintf(buffer, "http://%s:%d%s/", server_name, server_port, req->request_uri); else sprintf(buffer, "http://%s%s/", server_name, req->request_uri); send_r_moved_perm(req, buffer); return 0; } data_fd = get_dir(req, &statbuf); /* updates statbuf */ if (data_fd == -1) /* couldn't do it */ return 0; /* errors reported by get_dir */ else if (data_fd <= 1) /* data_fd == 0 -> close it down, 1 -> continue */ return data_fd; /* else, data_fd contains the fd of the file... */ } #ifdef DAVINCI_IPCAM if (req->http_uri && (req->http_uri->uri_flag & URI_FLAG_NEED_PARSE)) dynamic = 1; #endif if (req->if_modified_since && !dynamic && !modified_since(&(statbuf.st_mtime), req->if_modified_since)) { send_r_not_modified(req); close(data_fd); return 0; } req->filesize = statbuf.st_size; req->last_modified = statbuf.st_mtime; if (req->method == M_HEAD || req->filesize == 0) { send_r_request_ok(req); close(data_fd); return 0; } if (req->filesize > MAX_FILE_MMAP) { send_r_request_ok(req); /* All's well */ req->status = PIPE_READ; req->cgi_status = CGI_BUFFER; req->data_fd = data_fd; req_flush(req); /* this should *always* complete due to the size of the I/O buffers */ req->header_line = req->header_end = req->buffer; return 1; } if (req->filesize == 0) { /* done */ send_r_request_ok(req); /* All's well *so far* */ close(data_fd); return 1; } /* NOTE: I (Jon Nelson) tried performing a read(2) * into the output buffer provided the file data would * fit, before mmapping, and if successful, writing that * and stopping there -- all to avoid the cost * of a mmap. Oddly, it was *slower* in benchmarks. */ req->mmap_entry_var = find_mmap(data_fd, &statbuf); if (req->mmap_entry_var == NULL) { req->buffer_end = 0; if (errno == ENOENT) send_r_not_found(req); else if (errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); close(data_fd); return 0; } req->data_mem = req->mmap_entry_var->mmap; close(data_fd); /* close data file */ if ((long) req->data_mem == -1) { boa_perror(req, "mmap"); return 0; } #ifdef DAVINCI_IPCAM if (dynamic) { char *addr = (char *)malloc(req->filesize + 1025); if (addr) { req->mem_flag |= MFLAG_IS_MEMORY; req->mmap_ptr = req->data_mem; req->mmap_size = req->filesize; memcpy(addr+1024, req->data_mem, req->filesize); addr[req->filesize+1024] = '\0'; req->data_mem = addr; req->filesize = html_argument_parse(req->authority, addr+1024, req->data_mem); send_request_ok_no_cache(req); /* All's well */ return 1; } } #endif // DAVINCI_IPCAM send_r_request_ok(req); /* All's well */ bytes = BUFFER_SIZE - req->buffer_end; /* bytes is now how much the buffer can hold * after the headers */ if (bytes > 0) { if (bytes > req->filesize) bytes = req->filesize; if (sigsetjmp(env, 1) == 0) { handle_sigbus = 1; memcpy(req->buffer + req->buffer_end, req->data_mem, bytes); handle_sigbus = 0; /* OK, SIGBUS **after** this point is very bad! */ } else { /* sigbus! */ log_error_doc(req); reset_output_buffer(req); send_r_error(req); fprintf(stderr, "%sGot SIGBUS in memcpy!\n", get_commonlog_time()); return 0; } req->buffer_end += bytes; req->filepos += bytes; if (req->filesize == req->filepos) { req_flush(req); req->status = DONE; } } /* We lose statbuf here, so make sure response has been sent */ return 1; }
int process_header_end(request * req) { if (!req->logline) { log_error_doc(req); fputs("No logline in process_header_end\n", stderr); send_r_error(req); return 0; } /* Percent-decode request */ if (unescape_uri(req->request_uri, &(req->query_string)) == 0) { log_error_doc(req); fputs("URI contains bogus characters\n", stderr); send_r_bad_request(req); return 0; } /* clean pathname */ clean_pathname(req->request_uri); if (req->request_uri[0] != '/') { log_error("URI does not begin with '/'\n"); send_r_bad_request(req); return 0; } if (vhost_root) { char *c; if (!req->header_host) { req->host = strdup(default_vhost); } else { req->host = strdup(req->header_host); } if (!req->host) { log_error_doc(req); fputs("unable to strdup default_vhost/req->header_host\n", stderr); send_r_error(req); return 0; } strlower(req->host); /* check for port, and remove * we essentially ignore the port, because we cannot * as yet report a different port than the one we are * listening on */ c = strchr(req->host, ':'); if (c) *c = '\0'; if (check_host(req->host) < 1) { log_error_doc(req); fputs("host invalid!\n", stderr); send_r_bad_request(req); return 0; } } if (translate_uri(req) == 0) { /* unescape, parse uri */ /* errors already logged */ SQUASH_KA(req); return 0; /* failure, close down */ } if (req->method == M_POST) { req->post_data_fd = create_temporary_file(1, NULL, 0); if (req->post_data_fd == 0) { /* errors already logged */ send_r_error(req); return 0; } if (fcntl(req->post_data_fd, F_SETFD, 1) == -1) { log_error_doc(req); fputs("unable to set close-on-exec for req->post_data_fd!\n", stderr); close(req->post_data_fd); req->post_data_fd = 0; send_r_error(req); return 0; } return 1; /* success */ } if (req->cgi_type) { return init_cgi(req); } req->status = WRITE; // return complete_response(req); return init_control(req); // return init_get(req); /* get and head */ }
int process_header_end(request * req) { if (!req->logline) { log_error_doc(req); fputs("No logline in process_header_end\n", stderr); send_r_error(req); return 0; } /* Percent-decode request */ if (unescape_uri(req->request_uri, &(req->query_string)) == 0) { log_error_doc(req); fputs("URI contains bogus characters\n", stderr); send_r_bad_request(req); return 0; } /* clean pathname */ clean_pathname(req->request_uri); if (req->request_uri[0] != '/') { log_error("URI does not begin with '/'\n"); send_r_bad_request(req); return 0; } if (use_caudium_hack) { /* We check whether the path is of the form "/(ll)/foo/..." which is used by the Caudium webserver for caching purposes and people have bookmarked it. To cope with this we simply strip it of. */ if (req->request_uri[0] == '/' && req->request_uri[1] == '(' && req->request_uri[2] >= 'a' && req->request_uri[2] <= 'z' && req->request_uri[3] >= 'a' && req->request_uri[3] <= 'z' && req->request_uri[4] == ')' && req->request_uri[5] == '/' && req->request_uri[6] ) { unsigned int len = strlen(req->request_uri); memmove (req->request_uri, req->request_uri+5, len - 5 + 1); } } if (vhost_root) { char *c; if (!req->header_host) { req->host = strdup(default_vhost); } else { req->host = strdup(req->header_host); } if (!req->host) { log_error_doc(req); fputs("unable to strdup default_vhost/req->header_host\n", stderr); send_r_error(req); return 0; } strlower(req->host); /* check for port, and remove * we essentially ignore the port, because we cannot * as yet report a different port than the one we are * listening on */ c = strchr(req->host, ':'); if (c) *c = '\0'; if (check_host(req->host) < 1) { log_error_doc(req); fputs("host invalid!\n", stderr); send_r_bad_request(req); return 0; } } if (translate_uri(req) == 0) { /* unescape, parse uri */ /* errors already logged */ SQUASH_KA(req); return 0; /* failure, close down */ } if (req->method == M_POST) { req->post_data_fd = create_temporary_file(1, NULL, 0); if (req->post_data_fd == 0) { /* errors already logged */ send_r_error(req); return 0; } if (fcntl(req->post_data_fd, F_SETFD, 1) == -1) { boa_perror(req, "unable to set close-on-exec for req->post_data_fd!"); close(req->post_data_fd); req->post_data_fd = 0; return 0; } return 1; /* success */ } if (req->cgi_type) { return init_cgi(req); } req->status = WRITE; return init_get(req); /* get and head */ }
int init_cgi(request * req) { int child_pid; int pipes[2]; int use_pipes = 0; SQUASH_KA(req); if (req->is_cgi) { if (complete_env(req) == 0) { return 0; } } #ifdef FASCIST_LOGGING { int i; for (i = 0; i < req->cgi_env_index; ++i) fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n", __FILE__, req->cgi_env[i]); } #endif if (req->is_cgi == CGI || 1) { use_pipes = 1; if (pipe(pipes) == -1) { log_error_time(); perror("pipe"); return 0; } if (set_nonblock_fd(pipes[0]) == -1) { log_error_time(); perror("cgi-fcntl"); close(pipes[0]); close(pipes[1]); return 0; } } child_pid = fork(); switch(child_pid) { case -1: log_error_time(); perror("fork"); if (use_pipes) { close(pipes[0]); close(pipes[1]); } send_r_error(req); /* FIXME: There is aproblem here. send_r_error would work for NPH and CGI, but not for GUNZIP. Fix that. */ return 0; break; case 0: if (req->is_cgi == CGI || req->is_cgi == NPH) { char *foo = strdup(req->pathname); char *c; if (!foo) { WARN("unable to strdup pathname for req->pathname"); _exit(1); } c = strrchr(foo, '/'); if (c) { ++c; *c = '\0'; } else { log_error_time(); perror("chdir"); if (use_pipes) close(pipes[1]); _exit(1); } if (chdir(foo) != 0) { log_error_time(); perror("chdir"); if (use_pipes) close(pipes[1]); _exit(1); } } if (use_pipes) { close(pipes[0]); if (dup2(pipes[1], STDOUT_FILENO) == -1) { log_error_time(); perror("dup2 - pipes"); close(pipes[1]); _exit(1); } close(pipes[1]); if (set_block_fd(STDOUT_FILENO) == -1) { log_error_time(); perror("cgi-fcntl"); _exit(1); } } else { if (dup2(req->fd, STDOUT_FILENO) == -1) { log_error_time(); perror("dup2 - fd"); _exit(1); } if (set_block_fd(req->fd) == -1) { log_error_time(); perror("cgi-fcntl"); _exit(1); } } if (req->method == M_POST) { lseek(req->post_data_fd, SEEK_SET, 0); dup2(req->post_data_fd, STDIN_FILENO); close(req->post_data_fd); } /* Close access log, so CGI program can't scribble * where it shouldn't */ close_access_log(); /* * tie STDERR to cgi_log_fd * cgi_log_fd will automatically close, close-on-exec rocks! * if we don't tied STDERR (current log_error) to cgi_log_fd, * then we ought to close it. */ if (!cgi_log_fd) dup2(devnullfd, STDERR_FILENO); else dup2(cgi_log_fd, STDERR_FILENO); if (req->is_cgi) { char *aargv[CGI_ARGC_MAX + 1]; create_argv(req, aargv); execve(req->pathname, aargv, req->cgi_env); } else { if (req->pathname[strlen(req->pathname) - 1] == '/') execl(dirmaker, dirmaker, req->pathname, req->request_uri, NULL); #ifdef GUNZIP else execl(GUNZIP, GUNZIP, "--stdout", "--decompress", req->pathname, NULL); #endif } WARN(req->pathname); _exit(1); break; default: if (verbose_cgi_logs) { log_error_time(); fprintf(stderr, "Forked child \"%s\" pid %d\n", req->pathname, child_pid); } if (req->method == M_POST) { close(req->post_data_fd); req->post_data_fd = 0; } if (!use_pipes) return 0; close(pipes[1]); req->data_fd = pipes[0]; req->status = PIPE_READ; if (req->is_cgi == CGI) { req->cgi_status = CGI_PARSE; req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2); } else { req->cgi_status = CGI_BUFFER; req->header_line = req->header_end = req->buffer; } req->filepos = 0; break; } return 1; }
int init_get(request * req) { int data_fd; struct stat statbuf; data_fd = open(req->pathname, O_RDONLY); if (data_fd == -1) { /* cannot open */ /* it's either a gunzipped file or a directory */ #ifdef GUNZIP char gzip_pathname[MAX_PATH_LENGTH]; int len; len = strlen(req->pathname); memcpy(gzip_pathname, req->pathname, len); memcpy(gzip_pathname + len, ".gz", 3); gzip_pathname[len + 3] = '\0'; data_fd = open(gzip_pathname, O_RDONLY); if (data_fd == -1) { #endif int errno_save = errno; log_error_doc(req); errno = errno_save; perror("document open"); errno = errno_save; if (errno == ENOENT) send_r_not_found(req); else if (errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); return 0; #ifdef GUNZIP } close(data_fd); req->response_status = R_REQUEST_OK; if (!req->simple) { req_write(req, "HTTP/1.0 200 OK-GUNZIP\r\n"); print_http_headers(req); print_content_type(req); print_last_modified(req); req_write(req, "\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; if (req->pathname) free(req->pathname); req->pathname = strdup(gzip_pathname); return init_cgi(req); #endif } fstat(data_fd, &statbuf); if (S_ISDIR(statbuf.st_mode)) { /* directory */ close(data_fd); /* close dir */ if (req->pathname[strlen(req->pathname) - 1] != '/') { char buffer[3 * MAX_PATH_LENGTH + 128]; if (server_port != 80) sprintf(buffer, "http://%s:%d%s/", server_name, server_port, req->request_uri); else sprintf(buffer, "http://%s%s/", server_name, req->request_uri); send_redirect_perm(req, buffer); return 0; } data_fd = get_dir(req, &statbuf); /* updates statbuf */ if (data_fd == -1) /* couldn't do it */ return 0; /* errors reported by get_dir */ else if (data_fd <= 1) /* data_fd == 0 -> close it down, 1 -> continue */ return data_fd; /* else, data_fd contains the fd of the file... */ } if (req->if_modified_since && !modified_since(&(statbuf.st_mtime), req->if_modified_since)) { send_r_not_modified(req); close(data_fd); return 0; } req->filesize = statbuf.st_size; req->last_modified = statbuf.st_mtime; if (req->method == M_HEAD) { send_r_request_ok(req); close(data_fd); return 0; } if (req->filesize > MAX_FILE_MMAP) { send_r_request_ok(req); /* All's well */ req->status = PIPE_READ; req->cgi_status = CGI_BUFFER; req->data_fd = data_fd; req_flush(req); /* this should *always* complete due to the size of the I/O buffers */ req->header_line = req->header_end = req->buffer; return 1; } req->mmap_entry_var = find_mmap(data_fd, &statbuf); if (req->mmap_entry_var == NULL) { send_r_error(req); close(data_fd); return 0; } req->data_mem = req->mmap_entry_var->mmap; close(data_fd); /* close data file */ if ((long) req->data_mem == -1) { boa_perror(req, "mmap"); return 0; } send_r_request_ok(req); /* All's well */ { /* combine first part of file with headers */ int bob; bob = BUFFER_SIZE - req->buffer_end; /* bob is now how much the buffer can hold * after the headers */ if (bob > 0) { if (bob > req->filesize) bob = req->filesize; memcpy(req->buffer + req->buffer_end, req->data_mem, bob); req->buffer_end += bob; req->filepos += bob; if (req->filesize == req->filepos) { req_flush(req); req->status = DONE; return 1; /* get it flushed next time around if need be */ } } } /* We lose statbuf here, so make sure response has been sent */ return 1; }
int init_get(request * req) { int data_fd, saved_errno; struct stat statbuf; volatile int bytes; data_fd = open(req->pathname, O_RDONLY); saved_errno = errno; #ifdef GUNZIP if (data_fd == -1 && errno == ENOENT) { char gzip_pathname[MAX_PATH_LENGTH]; int len; len = strlen(req->pathname); memcpy(gzip_pathname, req->pathname, len); memcpy(gzip_pathname + len, ".gz", 3); gzip_pathname[len + 3] = '\0'; data_fd = open(gzip_pathname, O_RDONLY); if (data_fd != -1) { close(data_fd); req->response_status = R_REQUEST_OK; if (req->pathname) free(req->pathname); req->pathname = strdup(gzip_pathname); if (!req->pathname) { log_error_time(); perror("strdup"); send_r_error(req); return 0; } if (!req->simple) { req_write(req, "HTTP/1.0 200 OK-GUNZIP\r\n"); print_http_headers(req); print_content_type(req); print_last_modified(req); req_write(req, "\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } } #endif if (data_fd == -1) { log_error_doc(req); errno = saved_errno; perror("document open"); if (saved_errno == ENOENT) send_r_not_found(req); else if (saved_errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); return 0; } fstat(data_fd, &statbuf); if (S_ISDIR(statbuf.st_mode)) { close(data_fd); if (req->pathname[strlen(req->pathname) - 1] != '/') { char buffer[3 * MAX_PATH_LENGTH + 128]; if (server_port != 80) sprintf(buffer, "http://%s:%d%s/", server_name, server_port, req->request_uri); else sprintf(buffer, "http://%s%s/", server_name, req->request_uri); send_r_moved_perm(req, buffer); return 0; } data_fd = get_dir(req, &statbuf); if (data_fd == -1) return 0; else if (data_fd <= 1) return data_fd; } if (req->if_modified_since && !modified_since(&(statbuf.st_mtime), req->if_modified_since)) { send_r_not_modified(req); close(data_fd); return 0; } req->filesize = statbuf.st_size; req->last_modified = statbuf.st_mtime; if (req->method == M_HEAD || req->filesize == 0) { send_r_request_ok(req); close(data_fd); return 0; } if (req->filesize > MAX_FILE_MMAP) { send_r_request_ok(req); req->status = PIPE_READ; req->cgi_status = CGI_BUFFER; req->data_fd = data_fd; req_flush(req); /* this should *always* complete due to the size of the I/O buffers */ req->header_line = req->header_end = req->buffer; return 1; } if (req->filesize == 0) { send_r_request_ok(req); close(data_fd); return 1; } /* NOTE: I (Jon Nelson) tried performing a read(2) * into the output buffer provided the file data would * fit, before mmapping, and if successful, writing that * and stopping there -- all to avoid the cost * of a mmap. Oddly, it was *slower* in benchmarks. */ req->mmap_entry_var = find_mmap(data_fd, &statbuf); if (req->mmap_entry_var == NULL) { req->buffer_end = 0; if (errno == ENOENT) send_r_not_found(req); else if (errno == EACCES) send_r_forbidden(req); else send_r_bad_request(req); close(data_fd); return 0; } req->data_mem = req->mmap_entry_var->mmap; close(data_fd); if ((long) req->data_mem == -1) { boa_perror(req, "mmap"); return 0; } send_r_request_ok(req); bytes = BUFFER_SIZE - req->buffer_end; /* bytes is now how much the buffer can hold * after the headers */ if (bytes > 0) { if (bytes > req->filesize) bytes = req->filesize; if (sigsetjmp(env, 1) == 0) { handle_sigbus = 1; memcpy(req->buffer + req->buffer_end, req->data_mem, bytes); handle_sigbus = 0; } else { log_error_doc(req); reset_output_buffer(req); send_r_error(req); fprintf(stderr, "%sGot SIGBUS in memcpy!\n", get_commonlog_time()); return 0; } req->buffer_end += bytes; req->filepos += bytes; if (req->filesize == req->filepos) { req_flush(req); req->status = DONE; } } return 1; }
int get_dir(request * req, struct stat *statbuf) { char pathname_with_index[MAX_PATH_LENGTH]; int data_fd; if (directory_index) { strcpy(pathname_with_index, req->pathname); strcat(pathname_with_index, directory_index); /* sprintf(pathname_with_index, "%s%s", req->pathname, directory_index); */ data_fd = open(pathname_with_index, O_RDONLY); if (data_fd != -1) { strcpy(req->request_uri, directory_index); fstat(data_fd, statbuf); return data_fd; } if (errno == EACCES) { send_r_forbidden(req); return -1; } else if (errno != ENOENT) { send_r_not_found(req); return -1; } #ifdef GUNZIP /* if we are here, trying index.html didn't work * try index.html.gz */ strcat(pathname_with_index, ".gz"); data_fd = open(pathname_with_index, O_RDONLY); if (data_fd != -1) { close(data_fd); req->response_status = R_REQUEST_OK; SQUASH_KA(req); if (req->pathname) free(req->pathname); req->pathname = strdup(pathname_with_index); if (!req->pathname) { log_error_time(); perror("strdup"); send_r_error(req); return 0; } if (!req->simple) { req_write(req, "HTTP/1.0 200 OK-GUNZIP\r\n"); print_http_headers(req); print_last_modified(req); req_write(req, "Content-Type: "); req_write(req, get_mime_type(directory_index)); req_write(req, "\r\n\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } #endif } if (dirmaker != NULL) { req->response_status = R_REQUEST_OK; SQUASH_KA(req); if (!req->simple) { req_write(req, "HTTP/1.0 200 OK\r\n"); print_http_headers(req); print_last_modified(req); req_write(req, "Content-Type: text/html\r\n\r\n"); req_flush(req); } if (req->method == M_HEAD) return 0; return init_cgi(req); } else if (cachedir) { return get_cachedir_file(req, statbuf); } else { send_r_forbidden(req); return -1; } }