#include "hypercube.h" http_fd_entry http_fds[ASIO_MAX_FDS]; static akbuf_ctxh http_ctx; static akbuf *parsebufs[8]; extern hypercube_config cfg; void http_init(void) { unsigned int i; http_ctx = akbuf_new_ctx(); for (i = 0; i < sizeof(parsebufs) / sizeof(parsebufs[0]); i ++) parsebufs[i] = akbuf_init(http_ctx, 0); for (i = 0; i < ASIO_MAX_FDS; i ++) http_fds[i].state = HTTP_FD_UNUSED; } void http_unset_fd(int fd) { if (http_fds[fd].state != HTTP_FD_UNUSED) { if (http_fds[fd].state != HTTP_FD_CGI) aklogf(LOG_CONNECTION, "Client on socket %d disconnected.", fd); http_fds[fd].state = HTTP_FD_UNUSED; } } void http_handle_sent(int fd, net_fd_entry *net_ent) { DEBUGF("send on socket %d done", fd); if (http_fds[fd].keep_alive == 0) { net_unset_fd(fd); } else { http_fds[fd].state = HTTP_FD_NEW_REQ; } } unsigned char *http_status_msg(unsigned int code) { switch (code) { case HTTP_OK: return "OK"; case HTTP_PARTIAL_CONTENT: return "Partial content"; case HTTP_MOVED_PERM: return "Moved permanently"; case HTTP_BAD_REQUEST: return "Bad request"; case HTTP_FORBIDDEN: return "Forbidden"; case HTTP_NOT_FOUND: return "Not found"; case HTTP_INTERNAL_ERROR: return "Internal error"; case HTTP_SERVER_TOO_BUSY: return "Server too busy"; case HTTP_LOG_HOOK: return "Handled by hook"; case HTTP_FORBIDDEN_BANNED: return "Forbidden (client banned)"; } return "Unspecified"; } unsigned char *http_method_str(unsigned int method) { switch (method) { case HTTP_METHOD_GET: return "GET"; case HTTP_METHOD_HEAD: return "HEAD"; case HTTP_METHOD_POST: return "POST"; } return "(none)"; } void http_log_query(int fd, net_fd_entry *net_ent, unsigned int code) { unsigned char *ua_p, *ref_p, *host_p; if ((host_p = akbuf_table_entry_get_str(http_fds[fd].headers, "Host")) == NULL) host_p = "(none)"; if ((ua_p = akbuf_table_entry_get_str(http_fds[fd].headers, "User-agent")) == NULL) ua_p = "(none)"; if ((ref_p = akbuf_table_entry_get_str(http_fds[fd].headers, "Referer")) == NULL) ref_p = "(none)"; akbuf_asciiz(net_ent->peerbuf); akbuf_asciiz(http_fds[fd].uri); aklogf(LOG_REQUEST, "HTTP/%u.%u %d %s %s \"%s\" %u \"%s\" \"%s\" \"%s\" \"%s\"", http_fds[fd].ver_maj, http_fds[fd].ver_min, fd, akbuf_data(net_ent->peerbuf), http_method_str(http_fds[fd].method), akbuf_data(http_fds[fd].uri), (code == HTTP_LOG_HOOK)? HTTP_OK : code, http_status_msg(code), host_p, ref_p, ua_p); } void http_error(int fd, net_fd_entry *net_ent, unsigned int code) { unsigned char *msg; http_log_query(fd, net_ent, code); msg = http_status_msg(code); if (code >= 2000) code -= 2000; http_fds[fd].keep_alive = 0; akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u %s\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: text/html\r\n" "Connection: close\r\n" "\r\n" "%u %s

%u %s

\r\n", code, msg, code, msg, code, msg); net_send(fd); } unsigned char *get_mime_type(unsigned char *filename) { unsigned int i; unsigned char *p; i = strlen(filename); while (i >= 1) { if (filename[i - 1] == '.') break; i --; } if (i > 0) { DEBUGF("get_mime_type() extension %s", &filename[i]); if ((p = akbuf_table_entry_get_str(cfg.mime_types, &filename[i])) != NULL) return p; } return "application/octet-stream"; } void cgi_handle_output(int fd, net_fd_entry *net_ent, akbuf *buf) { if (http_fds[fd].rpipe_net_ent->type == HTTP_FD_UNUSED) { net_unset_fd(fd); return; } akbuf_append_buf(http_fds[fd].rpipe_net_ent->sendbuf, buf); net_send(http_fds[fd].rpipe_fd); } void cgi_handle_sent_content(int fd, net_fd_entry *net_ent, akbuf *buf) { net_unset_fd(fd); } int serve_cgi_to_fd(int fd, net_fd_entry *net_ent, unsigned char *cgi_name) { int cgi_rpipe[2], cgi_wpipe[2]; /* parsebufs: 0,1,2 == env var tmp */ unsigned int has_content; DEBUGF("serving cgi [%s]", cgi_name); cgi_rpipe[0] = cgi_rpipe[1] = -1; if (pipe(cgi_rpipe) != 0 || !FD_VALID(cgi_rpipe[0]) || !FD_VALID(cgi_rpipe[1])) { if (cgi_rpipe[0] != -1) close(cgi_rpipe[0]); if (cgi_rpipe[1] != -1) close(cgi_rpipe[1]); http_error(fd, net_ent, HTTP_SERVER_TOO_BUSY); return -2; } if (http_fds[fd].method == HTTP_METHOD_POST && http_fds[fd].content_len > 0) { has_content = 1; cgi_wpipe[0] = cgi_wpipe[1] = -1; if (pipe(cgi_wpipe) != 0 || !FD_VALID(cgi_wpipe[0]) || !FD_VALID(cgi_wpipe[1])) { if (cgi_wpipe[0] != -1) close(cgi_wpipe[0]); if (cgi_wpipe[1] != -1) close(cgi_wpipe[1]); http_error(fd, net_ent, HTTP_SERVER_TOO_BUSY); return -2; } } else { has_content = 0; } if (fork() == 0) { unsigned char *envs[64]; unsigned int i; int j; if (has_content == 1) { close(cgi_wpipe[1]); dup2(cgi_wpipe[0], 0); } close(cgi_rpipe[0]); dup2(cgi_rpipe[1], 1); i = 0; /* We can use strdup() here because the process will end anyway. */ akbuf_sprintf(parsebufs[0], "GATEWAY_INTERFACE=CGI/1.1"); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_asciiz(http_fds[fd].query); akbuf_sprintf(parsebufs[0], "QUERY_STRING=%s", akbuf_data(http_fds[fd].query)); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_sprintf(parsebufs[0], "PATH=" CGI_DEFAULT_PATH); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_sprintf(parsebufs[0], "SERVER_SOFTWARE=" SERVER_VERSION); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_sprintf(parsebufs[0], "SERVER_PROTOCOL=HTTP/%u.%u", http_fds[fd].ver_maj, http_fds[fd].ver_min); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_clone(parsebufs[1], net_ent->peerbuf); if ((j = akbuf_chr(parsebufs[1], ':')) != -1) { akbuf_split(parsebufs[1], parsebufs[2], j); akbuf_asciiz(parsebufs[1]); akbuf_sprintf(parsebufs[0], "REMOTE_ADDR=%s", akbuf_data(parsebufs[2])); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_sprintf(parsebufs[0], "REMOTE_PORT=%s", akbuf_data(parsebufs[1])); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); } akbuf_sprintf(parsebufs[0], "SCRIPT_FILENAME=%s", cgi_name); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_sprintf(parsebufs[0], "REQUEST_METHOD=%s", http_method_str(http_fds[fd].method)); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); akbuf_asciiz(http_fds[fd].uri); akbuf_sprintf(parsebufs[0], "REQUEST_URI=%s", akbuf_data(http_fds[fd].uri)); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); if (has_content == 1) { akbuf_sprintf(parsebufs[0], "CONTENT_LENGTH=%u", (unsigned int)http_fds[fd].content_len); akbuf_asciiz(parsebufs[0]); envs[i ++] = strdup(akbuf_data(parsebufs[0])); } envs[i] = NULL; execle(cgi_name, cgi_name, NULL, envs); akperror("execve() CGI script"); exit(1); } if (has_content == 1) { close(cgi_wpipe[0]); net_set_fd(cgi_wpipe[1], NET_FD_SEND, NULL, cgi_handle_sent_content, 1); net_send_buf(cgi_wpipe[1], http_fds[fd].content); http_fds[fd].wpipe_fd = cgi_wpipe[1]; } DEBUGF("wpipe_fd %d", http_fds[fd].wpipe_fd); close(cgi_rpipe[1]); net_set_fd(cgi_rpipe[0], NET_FD_READ, cgi_handle_output, NULL, 1); http_fds[cgi_rpipe[0]].rpipe_fd = fd; http_fds[cgi_rpipe[0]].rpipe_net_ent = net_ent; http_fds[cgi_rpipe[0]].ctx = http_fds[fd].ctx; http_fds[cgi_rpipe[0]].state = HTTP_FD_CGI; http_fds[cgi_rpipe[0]].method = HTTP_METHOD_NONE; http_fds[cgi_rpipe[0]].uri = NULL; http_fds[cgi_rpipe[0]].query = NULL; http_fds[cgi_rpipe[0]].ver_maj = http_fds[fd].ver_min = 0; http_fds[cgi_rpipe[0]].keep_alive = 0; http_fds[cgi_rpipe[0]].num_headers = 0; http_fds[cgi_rpipe[0]].headers = NULL; http_fds[cgi_rpipe[0]].num_args = 0; http_fds[cgi_rpipe[0]].args = NULL; http_fds[fd].state = HTTP_FD_PIPING; http_fds[fd].rpipe_fd = cgi_rpipe[0]; net_set_callbacks(fd, NULL, NULL); akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u OK\r\n" "Server: " SERVER_VERSION "\r\n" "Connection: close\r\n", HTTP_OK); net_send(fd); return 0; } /* return: 0 == all ok; -1 == error, show errorpage; -2 == error, errorpage shown */ int serve_doc_to_fd(int fd, net_fd_entry *net_ent, unsigned char *orig_doc_name, unsigned char *doc_name, unsigned int doc_off, AKsize_t doc_size, unsigned int add_gzip_header) { unsigned char *mime_type; unsigned char timebuf[512]; unsigned int code; struct stat st; DEBUGF("serve_doc_to_fd() orig [%s] real [%s]", orig_doc_name, doc_name); mime_type = get_mime_type(orig_doc_name); if (strcasecmp(mime_type, CGI_MIME_TYPE) == 0) { return serve_cgi_to_fd(fd, net_ent, orig_doc_name); } if (access(doc_name, R_OK) != 0) return -1; http_log_query(fd, net_ent, HTTP_OK); if (http_fds[fd].method != HTTP_METHOD_HEAD && (net_ent->send_fd = open(doc_name, O_RDONLY)) < 0) return -1; if (doc_off >= doc_size) doc_off = (doc_size > 0)? doc_size - 1 : 0; net_ent->send_fd_off = doc_off; net_ent->send_fd_len = doc_size - doc_off; code = (doc_off != 0)? HTTP_PARTIAL_CONTENT : HTTP_OK; if (http_fds[fd].ver_maj > 0) { if (stat(doc_name, &st) == 0) { size_t len; struct tm *tmp; time_t t; t = time(NULL); tmp = gmtime(&t); if ((len = strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", tmp)) == 0) timebuf[0] = 0; } else { timebuf[0] = 0; } if (code == HTTP_PARTIAL_CONTENT) { akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u %s\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: %s\r\n" "Content-length: %u\r\n" "Content-range: bytes %u-%u/%u\r\n" "Connection: %s\r\n" "Last-Modified: %s\r\n" "\r\n", code, http_status_msg(code), get_mime_type(orig_doc_name), doc_size - doc_off, doc_off, (doc_size == 0)? 0 : doc_size - 1, doc_size, (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", timebuf); } else { if (add_gzip_header == 1) { akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u %s\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: %s\r\n" "Content-length: %u\r\n" "Connection: %s\r\n" "Last-Modified: %s\r\n" "Content-Encoding: gzip\r\n" "\r\n", code, http_status_msg(code), get_mime_type(orig_doc_name), doc_size - doc_off, (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", timebuf); } else { akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u %s\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: %s\r\n" "Content-length: %u\r\n" "Connection: %s\r\n" "Last-Modified: %s\r\n" "\r\n", code, http_status_msg(code), get_mime_type(orig_doc_name), doc_size - doc_off, (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", timebuf); } } } net_send(fd); return 0; } void http_dir_index(int fd, net_fd_entry *net_ent, unsigned char *dir_path) { DIR *dir; struct dirent *de; struct stat st; /* parsebufs: 4 == generated document, 5 == appended data */ if ((dir = opendir(dir_path)) == NULL) { http_error(fd, net_ent, (errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); return; } http_log_query(fd, net_ent, HTTP_OK); akbuf_asciiz(http_fds[fd].uri); akbuf_sprintf(parsebufs[4], "\n" "Index of %s\n" "\n" "

Index of %s

\n", akbuf_data(http_fds[fd].uri), akbuf_data(http_fds[fd].uri)); while ((de = readdir(dir)) != NULL) { akbuf_sprintf(parsebufs[5], "%s/%s", dir_path, de->d_name); akbuf_asciiz(parsebufs[5]); if (stat(akbuf_data(parsebufs[5]), &st) == 0) { akbuf_sprintf(parsebufs[5], "%s%s %u
\n", de->d_name, (st.st_mode & S_IFDIR)? "/" : "", de->d_name, (st.st_mode & S_IFDIR)? "/" : "", st.st_size); akbuf_append_buf(parsebufs[4], parsebufs[5]); } } closedir(dir); /* :-D */ akbuf_sprintf(parsebufs[5], "\n"); akbuf_append_buf(parsebufs[4], parsebufs[5]); if (http_fds[fd].ver_maj > 0) { akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u OK\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: text/html\r\n" "Content-length: %u\r\n" "Connection: %s\r\n" "\r\n", HTTP_OK, akbuf_idx(parsebufs[4]), (http_fds[fd].keep_alive == 1)? "keep-alive" : "close"); } akbuf_append_buf(net_ent->sendbuf, parsebufs[4]); } unsigned int http_has_access(akbuf *peerbuf, akbuf *uri) { akbuf_table_entry *ent; akbuf *addrbuf; akbuf_ctxh ctx; int i; ctx = akbuf_new_ctx(); addrbuf = akbuf_init(ctx, 0); akbuf_clone(addrbuf, peerbuf); if ((i = akbuf_chr(addrbuf, ':')) != -1) akbuf_set_byte(addrbuf, i, 0); ent = cfg.allow_clients->head; while (ent != NULL) { if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(addrbuf), 0, NULL, 0) == 0) { akbuf_asciiz(ent->data); akbuf_asciiz(uri); if (strncmp(akbuf_data(uri), akbuf_data(ent->data), akbuf_idx(ent->data)) == 0) { DEBUGF("check_access match [%s] [%s]", akbuf_data(addrbuf), akbuf_data(uri)); break; } } ent = ent->next; } if (ent != NULL) return 1; ent = cfg.deny_clients->head; while (ent != NULL) { if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(addrbuf), 0, NULL, 0) == 0) { akbuf_asciiz(ent->data); akbuf_asciiz(uri); if (strncmp(akbuf_data(uri), akbuf_data(ent->data), akbuf_idx(ent->data)) == 0) { DEBUGF("check_access match [%s] [%s]", akbuf_data(addrbuf), akbuf_data(uri)); break; } } ent = ent->next; } akbuf_free_ctx(ctx); return (ent == NULL)? 1 : 0; } void http_do_rewrite(akbuf *rbuf) { akbuf_table_entry *ent; unsigned int i, c, rem; regmatch_t matches[10]; /* parsebufs: 6 == saved original rbuf */ akbuf_asciiz(rbuf); DEBUGF("doing rewrite on [%s]", akbuf_data(rbuf)); ent = cfg.rewrite_rules->head; while (ent != NULL) { if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(rbuf), sizeof(matches) / sizeof(matches[0]), matches, 0) == 0) { DEBUGF("rewrite regexec() match"); akbuf_clone(parsebufs[6], rbuf); akbuf_set_idx(rbuf, 0); i = 0; rem = akbuf_idx(ent->data); while (rem > 0) { akbuf_get_byte(ent->data, i, c); i ++; rem --; switch (c) { case '\\': if (rem >= 1) { akbuf_get_byte(ent->data, i, c); i ++; rem --; akbuf_append_byte(rbuf, c); } else { aklogf(LOG_ERROR, "Error in rewrite rule: Stray \\ at end of line."); } break; case '$': if (rem >= 1) { akbuf_get_byte(ent->data, i, c) i ++; rem --; c -= '0'; if (c >= sizeof(matches) / sizeof(matches[0]) || matches[c].rm_so == -1) { aklogf(LOG_ERROR, "Erorr in rewrite rule: Unknown variable $%u.", c); break; } AKassert(matches[c].rm_eo >= matches[c].rm_so); akbuf_append_data(rbuf, akbuf_data(parsebufs[6]) + matches[c].rm_so, matches[c].rm_eo - matches[c].rm_so); } break; default: akbuf_append_byte(rbuf, c); break; } } return; } else { DEBUGF("rewrite regexec() nomatch"); } ent = ent->next; } } void http_index(int fd, net_fd_entry *net_ent, unsigned char *dir_path, unsigned int doc_off) { unsigned int diridx; unsigned int i; struct stat st; unsigned char *indexdocs[] = { "index.html", "index.htm", NULL }; /* parsebufs: 3 == full path to try */ akbuf_strcpy(parsebufs[3], dir_path); akbuf_append_byte(parsebufs[3], '/'); diridx = akbuf_idx(parsebufs[3]); i = 0; while (indexdocs[i] != NULL) { akbuf_append_str(parsebufs[3], indexdocs[i]); akbuf_asciiz(parsebufs[3]); DEBUGF("trying index '%s'", akbuf_data(parsebufs[3])); if (stat(akbuf_data(parsebufs[3]), &st) == 0 && st.st_mode & S_IFREG) { if (serve_doc_to_fd(fd, net_ent, akbuf_data(parsebufs[3]), akbuf_data(parsebufs[3]), doc_off, st.st_size, 0) == 0) break; } akbuf_set_idx(parsebufs[3], diridx); i ++; } if (indexdocs[i] == NULL) http_dir_index(fd, net_ent, dir_path); } unsigned int http_parse_range(akbuf *range) { int i; unsigned int cur_val, c; /* XXX support for ranges other than bytes=###- */ if ((i = akbuf_chr(range, '=')) == -1 || ++ i >= akbuf_idx(range)) return 0; cur_val = 0; for (; i < akbuf_idx(range); i ++) { akbuf_get_byte(range, i, c); if (c > '9' || c < '0') break; c -= '0'; cur_val *= 10; cur_val += c; } DEBUGF("got range %u", cur_val); return cur_val; } int http_serve_hook(int fd, net_fd_entry *net_ent) { struct serve_hook_entry { unsigned char *uri; void (*serve_fn)(); } serve_hooks[] = { { "announce", tracker_serve_announce }, { "announce.php", tracker_serve_announce }, { "announcephp", tracker_serve_announce }, { "scrape", tracker_serve_scrape }, { "scrape.php", tracker_serve_scrape }, { "scrapephp", tracker_serve_scrape }, { "status", tracker_serve_status }, { "peers", tracker_serve_peers }, { NULL, NULL }, }; unsigned int i; akbuf_ctxh ctx; akbuf *uribuf; akbuf_asciiz(http_fds[fd].uri); ctx = akbuf_new_ctx(); uribuf = akbuf_init(ctx, 0); akbuf_clone(uribuf, http_fds[fd].uri); while (akbuf_idx(uribuf) > 1 && akbuf_data(uribuf)[0] == '/') akbuf_eat_byte(uribuf); akbuf_asciiz(uribuf); i = 0; while (serve_hooks[i].uri != NULL) { if (strcmp(akbuf_data(uribuf), serve_hooks[i].uri) == 0) { http_log_query(fd, net_ent, HTTP_LOG_HOOK); serve_hooks[i].serve_fn(fd, &http_fds[fd], net_ent); akbuf_free_ctx(ctx); return i; } i ++; } akbuf_free_ctx(ctx); return -1; } void http_serve_req(int fd, net_fd_entry *net_ent) { unsigned char *root_dir, *host_p, *p; akbuf_ctxh ctx; akbuf *host_buf, *buf; unsigned char resolved_path[PATH_MAX + 1], resolved_root[PATH_MAX + 1]; struct stat st, st2; int i; unsigned int range_start; /* parsebufs: 0 == req path 1 == req path */ #define SERVE_ERR(code)\ {\ http_error(fd, net_ent, (code));\ akbuf_free_ctx(ctx);\ return;\ } DEBUGF("serving req"); ctx = akbuf_new_ctx(); if ((p = akbuf_table_entry_get_str(http_fds[fd].headers, "Connection")) != NULL) { DEBUGF("header connection = %s", p); if (strcasecmp(p, "keep-alive") == 0) http_fds[fd].keep_alive = 1; else if (strcasecmp(p, "close") == 0) http_fds[fd].keep_alive = 0; } if ((buf = akbuf_table_entry_get(http_fds[fd].headers, "Range")) != NULL) { range_start = http_parse_range(buf); } else { range_start = 0; } if ((buf = akbuf_table_entry_get(http_fds[fd].headers, "Host")) != NULL) { host_buf = akbuf_init(ctx, 0); akbuf_clone(host_buf, buf); akbuf_asciiz(host_buf); root_dir = akbuf_table_entry_get_str(cfg.vhosts, akbuf_data(host_buf)); if (root_dir == NULL && (i = akbuf_chr(buf, ':')) != -1) { akbuf_set_idx(buf, i); akbuf_asciiz(buf); root_dir = akbuf_table_entry_get_str(cfg.vhosts, akbuf_data(buf)); } if (root_dir == NULL) root_dir = cfg.default_root; host_p = akbuf_data(host_buf); } else { if (http_fds[fd].ver_maj > 0 && (http_fds[fd].ver_maj != 1 || http_fds[fd].ver_min != 0)) SERVE_ERR(HTTP_BAD_REQUEST); akbuf_asciiz(net_ent->sockbuf); host_p = akbuf_data(net_ent->sockbuf); root_dir = cfg.default_root; } DEBUGF("host [%s] root [%s]", host_p, root_dir); http_do_rewrite(http_fds[fd].uri); akbuf_asciiz(http_fds[fd].uri); DEBUGF("rewrote => [%s]", akbuf_data(http_fds[fd].uri)); if (http_has_access(net_ent->peerbuf, http_fds[fd].uri) == 0) SERVE_ERR(HTTP_FORBIDDEN_BANNED); if (http_serve_hook(fd, net_ent) >= 0) { akbuf_free_ctx(ctx); return; } if (realpath(root_dir, resolved_root) == NULL) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); resolved_root[sizeof(resolved_root) - 1] = 0; akbuf_strcpy(parsebufs[0], resolved_root); akbuf_append_buf(parsebufs[0], http_fds[fd].uri); akbuf_asciiz(parsebufs[0]); DEBUGF("req path [%s]", akbuf_data(parsebufs[0])); if (realpath(akbuf_data(parsebufs[0]), resolved_path) == NULL) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); resolved_path[sizeof(resolved_path) - 1] = 0; DEBUGF("resolved path [%s] root %s", resolved_path, resolved_root); if (strlen(resolved_path) < strlen(resolved_root) || strncmp(resolved_path, resolved_root, strlen(resolved_root)) != 0) SERVE_ERR(HTTP_NOT_FOUND); if (stat(resolved_path, &st) != 0) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); if (st.st_mode & S_IFDIR) { unsigned int c; if (akbuf_empty(http_fds[fd].uri)) SERVE_ERR(HTTP_BAD_REQUEST); if (access(resolved_path, X_OK) != 0) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); akbuf_get_byte(http_fds[fd].uri, akbuf_idx(http_fds[fd].uri) - 1, c); if (c == '/') { http_index(fd, net_ent, resolved_path, range_start); } else { http_log_query(fd, net_ent, HTTP_MOVED_PERM); akbuf_asciiz(http_fds[fd].uri); buf = akbuf_init(ctx, 0); akbuf_sprintf(buf, "\r\n" "The document has moved\r\n" "New address http://%s%s/\r\n" "\r\n\r\n", host_p, akbuf_data(http_fds[fd].uri), host_p, akbuf_data(http_fds[fd].uri)); akbuf_sprintf(net_ent->sendbuf, "HTTP/1.1 %u Moved permanently\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: text/html\r\n" "Content-length: %u\r\n" "Connection: %s\r\n" "Location: http://%s%s/\r\n" "\r\n", HTTP_MOVED_PERM, akbuf_idx(buf), (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", host_p, akbuf_data(http_fds[fd].uri)); akbuf_append_buf(net_ent->sendbuf, buf); } net_send(fd); akbuf_free_ctx(ctx); return; } akbuf_strcpy(parsebufs[0], resolved_path); akbuf_append_byte(parsebufs[0], '.'); akbuf_append_str(parsebufs[0], GZIP_SUFFIX); akbuf_asciiz(parsebufs[0]); if (akbuf_table_entry_get(http_fds[fd].headers, "Accept-Encoding") != NULL && stat(akbuf_data(parsebufs[0]), &st2) == 0 && !(st2.st_mode & S_IFDIR)) { if (serve_doc_to_fd(fd, net_ent, resolved_path, akbuf_data(parsebufs[0]), range_start, st2.st_size, 1) == -1) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); } else { if (serve_doc_to_fd(fd, net_ent, resolved_path, resolved_path, range_start, st.st_size, 0) == -1) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); } akbuf_free_ctx(ctx); } AKssize_t http_unescape(akbuf *inbuf, akbuf *outbuf) { unsigned int rem, i, c, d; akbuf_ctxh ctx; akbuf *buf; ctx = AKbuf_INVALID_CTX; if (outbuf == NULL) { ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, akbuf_idx(inbuf)); } else { buf = outbuf; } i = 0; rem = akbuf_idx(inbuf); while (rem > 0) { akbuf_get_byte(inbuf, i, d); i ++; rem --; if (d == '%' && rem >= 2) { akbuf_get_byte(inbuf, i, c); i ++; rem --; if (c >= 'a') c -= 'a' - 0xa; else if (c >= 'A') c -= 'A' - 0xa; else if (c >= '0') c -= '0'; else break; d = c << 4; akbuf_get_byte(inbuf, i, c); i ++; rem --; if (c >= 'a') c -= 'a' - 0xa; else if (c >= 'A') c -= 'A' - 0xa; else if (c >= '0') c -= '0'; else break; d |= c; if (d > 0xff) break; } akbuf_append_byte(buf, d); } if (rem > 0) { if (ctx != AKbuf_INVALID_CTX) akbuf_free_ctx(ctx); return -rem; } if (outbuf == NULL) akbuf_clone(inbuf, buf); if (ctx != AKbuf_INVALID_CTX) akbuf_free_ctx(ctx); return i; } void http_parse_arg(akbuf *arg, akbuf *var, akbuf *val) { int i; akbuf_set_idx(var, 0); akbuf_set_idx(val, 0); if ((i = akbuf_chr(arg, '=')) != -1) { akbuf_split(arg, var, i); akbuf_clone(val, arg); } else { akbuf_clone(var, arg); } http_unescape(var, NULL); http_unescape(val, NULL); } void http_parse_args(int fd, net_fd_entry *net_ent, akbuf *args) { akbuf_ctxh ctx; akbuf *splitargs, *splitarg; akbuf *var, *val; int i; ctx = akbuf_new_ctx(); splitargs = akbuf_init(ctx, 0); splitarg = akbuf_init(ctx, 0); var = akbuf_init(ctx, 0); val = akbuf_init(ctx, 0); akbuf_clone(splitargs, args); while ((i = akbuf_chr(splitargs, '&')) != -1) { akbuf_split(splitargs, splitarg, i); http_parse_arg(splitarg, var, val); akbuf_asciiz(var); #ifdef DEBUG akbuf_asciiz(val); DEBUGF("http_parse_args(): var [%s] val [%s]\n", akbuf_data(var), akbuf_data(val)); #endif akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].args, akbuf_data(var), val); } if (akbuf_idx(splitargs) > 0) { http_parse_arg(splitargs, var, val); akbuf_asciiz(var); #ifdef DEBUG akbuf_asciiz(val); DEBUGF("http_parse_args(): last var [%s] val [%s]\n", akbuf_data(var), akbuf_data(val)); #endif akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].args, akbuf_data(var), val); } akbuf_free_ctx(ctx); } void http_handle_action(int fd, net_fd_entry *net_ent, akbuf *linebuf) { int i; unsigned int c; /* parsebufs: 0 == method, 1 == uri */ #define ACTION_ERR()\ {\ http_error(fd, net_ent, HTTP_BAD_REQUEST);\ return;\ } DEBUGF("http action"); if ((i = akbuf_chr(linebuf, ' ')) == -1) ACTION_ERR(); akbuf_split(linebuf, parsebufs[0], i); akbuf_asciiz(parsebufs[0]); if (strcasecmp(akbuf_data(parsebufs[0]), "GET") == 0) http_fds[fd].method = HTTP_METHOD_GET; else if (strcasecmp(akbuf_data(parsebufs[0]), "HEAD") == 0) http_fds[fd].method = HTTP_METHOD_HEAD; else if (strcasecmp(akbuf_data(parsebufs[0]), "POST") == 0) http_fds[fd].method = HTTP_METHOD_POST; else ACTION_ERR(); if ((i = akbuf_chr(linebuf, ' ')) != -1) { unsigned int cur_ver, seen_dot; akbuf_split(linebuf, parsebufs[1], i); if ((i = akbuf_chr(linebuf, '/')) == -1) ACTION_ERR(); if (++ i >= akbuf_idx(linebuf)) ACTION_ERR(); cur_ver = seen_dot = 0; for (; i < akbuf_idx(linebuf); i ++) { akbuf_get_byte(linebuf, i, c); if (c == '.') { if (seen_dot == 0) http_fds[fd].ver_maj = cur_ver; else if (seen_dot == 1) http_fds[fd].ver_min = cur_ver; seen_dot ++; cur_ver = 0; } else { if (c > '9' || c < '0') ACTION_ERR(); cur_ver *= 10; cur_ver += c - '0'; } } if (seen_dot == 0) http_fds[fd].ver_maj = cur_ver; else if (seen_dot == 1) http_fds[fd].ver_min = cur_ver; } else { akbuf_clone(parsebufs[1], linebuf); http_fds[fd].ver_maj = 0; http_fds[fd].ver_min = 9; } if ((i = akbuf_chr(parsebufs[1], '?')) != -1) { if (i == 0) ACTION_ERR(); akbuf_set_data(http_fds[fd].query, akbuf_data(parsebufs[1]) + i + 1, akbuf_idx(parsebufs[1]) - i - 1); akbuf_set_idx(parsebufs[1], i); akbuf_asciiz(http_fds[fd].query); akbuf_asciiz(parsebufs[1]); DEBUGF("got query [%s] rem [%s]", akbuf_data(http_fds[fd].query), akbuf_data(parsebufs[1])); http_parse_args(fd, net_ent, http_fds[fd].query); } else { akbuf_set_idx(http_fds[fd].query, 0); } if (akbuf_empty(parsebufs[1])) ACTION_ERR(); http_unescape(parsebufs[1], http_fds[fd].uri); if (akbuf_empty(http_fds[fd].uri)) ACTION_ERR(); akbuf_asciiz(http_fds[fd].uri); DEBUGF("got http version %u.%u", http_fds[fd].ver_maj, http_fds[fd].ver_min); akbuf_asciiz(net_ent->peerbuf); aklogf(LOG_DEBUG, "%d: %s HTTP/%u.%u \"%s %s\"", fd, akbuf_data(net_ent->peerbuf), http_fds[fd].ver_maj, http_fds[fd].ver_min, akbuf_data(parsebufs[0]), akbuf_data(http_fds[fd].uri)); if (http_fds[fd].ver_maj > 0) http_fds[fd].state = HTTP_FD_HEADERS; else http_serve_req(fd, net_ent); } void http_handle_content(int fd, net_fd_entry *net_ent, akbuf *buf) { unsigned int rem; rem = http_fds[fd].content_len - akbuf_idx(http_fds[fd].content); akbuf_append_data(http_fds[fd].content, akbuf_data(buf), (akbuf_idx(buf) > rem)? rem : akbuf_idx(buf)); if (akbuf_idx(http_fds[fd].content) >= http_fds[fd].content_len) { http_serve_req(fd, net_ent); return; } } void http_handle_headers(int fd, net_fd_entry *net_ent, akbuf *linebuf) { int i; /* parsebufs: 0 == header name */ #define HEADERS_ERR()\ {\ http_error(fd, net_ent, HTTP_BAD_REQUEST);\ return;\ } if (akbuf_empty(linebuf) || (i = akbuf_chr(linebuf, ':')) == -1) { DEBUGF("content-len %u", http_fds[fd].content_len); if (http_fds[fd].content_len != 0) { http_fds[fd].state = HTTP_FD_CONTENT; net_set_callbacks(fd, http_handle_content, http_handle_sent); net_set_type(fd, NET_FD_READ); } else { http_serve_req(fd, net_ent); } return; } if (http_fds[fd].num_headers >= HTTP_MAX_HEADERS) HEADERS_ERR(); akbuf_split(linebuf, parsebufs[0], i); for (i = 0; i < akbuf_idx(linebuf); i ++) if (akbuf_data(linebuf)[i] != ' ' && akbuf_data(linebuf)[i] != '\t') break; akbuf_consume(linebuf, i); akbuf_asciiz(parsebufs[0]); akbuf_asciiz(linebuf); DEBUGF("header [%s] : [%s]", akbuf_data(parsebufs[0]), akbuf_data(linebuf)); if (strcasecmp(akbuf_data(parsebufs[0]), "Content-length") == 0) { http_fds[fd].content_len = atoi(akbuf_data(linebuf)); if (http_fds[fd].content_len > HTTP_MAX_CONTENT_LEN) HEADERS_ERR(); } akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].headers, akbuf_data(parsebufs[0]), linebuf); return; } void http_handle_action_and_headers(int fd, net_fd_entry *net_ent, akbuf *linebuf) { unsigned int i; for (i = 0; i < sizeof(parsebufs) / sizeof(parsebufs[0]); i ++) if (akbuf_size(parsebufs[i]) > BUF_SIZE) akbuf_set_size(parsebufs[i], BUF_SIZE); switch (http_fds[fd].state) { case HTTP_FD_UNUSED: case HTTP_FD_NEW_REQ: http_fds[fd].ctx = net_ent->ctx; http_fds[fd].state = HTTP_FD_ACTION; http_fds[fd].method = HTTP_METHOD_NONE; http_fds[fd].uri = akbuf_init(net_ent->ctx, 0); http_fds[fd].query = akbuf_init(net_ent->ctx, 0); http_fds[fd].content = akbuf_init(net_ent->ctx, 0); http_fds[fd].content_len = 0; http_fds[fd].ver_maj = http_fds[fd].ver_min = 0; http_fds[fd].keep_alive = 0; http_fds[fd].num_headers = 0; http_fds[fd].headers = akbuf_table_init(net_ent->ctx, AKBUF_TABLE_NOCASE); http_fds[fd].num_args = 0; http_fds[fd].args = akbuf_table_init(net_ent->ctx, AKBUF_TABLE_NOCASE); http_fds[fd].rpipe_fd = -1; http_fds[fd].rpipe_net_ent = NULL; http_fds[fd].wpipe_fd = -1; http_fds[fd].wpipe_net_ent = NULL; /* fallthrough */ case HTTP_FD_ACTION: http_handle_action(fd, net_ent, linebuf); break; case HTTP_FD_HEADERS: http_handle_headers(fd, net_ent, linebuf); break; } }