/////////////////////////////////////////////////////////////////////////////////////////////// /// This is free and unencumbered software released into the public domain. /// /// /// /// Anyone is free to copy, modify, publish, use, compile, sell, or /// /// distribute this software, either in source code form or as a compiled /// /// binary, for any purpose, commercial or non-commercial, and by any /// /// means. /// /// /// /// In jurisdictions that recognize copyright laws, the author or authors /// /// of this software dedicate any and all copyright interest in the /// /// software to the public domain. We make this dedication for the benefit /// /// of the public at large and to the detriment of our heirs and /// /// successors. We intend this dedication to be an overt act of /// /// relinquishment in perpetuity of all present and future rights to this /// /// software under copyright law. /// /// /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, /// /// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF /// /// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. /// /// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR /// /// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, /// /// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR /// /// OTHER DEALINGS IN THE SOFTWARE. /// /// /// /// For more information, please refer to /// /// /// /// NOTE: Some code was adapted from BearSSL. That code uses the MIT license. /// /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// /// HEADERS /// /////////////////////////////////////////////////////////////////////////////////////////////// // TODO: Clean up these headers #include #include #include #include #include #include #include #include #include #include #include #include // sig_atomic_t #include #include #include #include #include #include #include "log.h" #include "http.h" #include "tinytemplate.h" /////////////////////////////////////////////////////////////////////////////////////////////// /// CONFIGURATION /// /////////////////////////////////////////////////////////////////////////////////////////////// #ifndef HTTPS #define HTTPS 0 #endif #define INPUT_BUFFER_LIMIT_MB 1 /////////////////////////////////////////////////////////////////////////////////////////////// /// OPTIONAL HEADERS /// /////////////////////////////////////////////////////////////////////////////////////////////// #if HTTPS #include #endif /////////////////////////////////////////////////////////////////////////////////////////////// /// TYPES & DEFINITIONS /// /////////////////////////////////////////////////////////////////////////////////////////////// #if HTTPS typedef struct { int type; // BR_KEYTYPE_RSA or BR_KEYTYPE_EC union { br_rsa_private_key rsa; br_ec_private_key ec; }; } PrivateKey; typedef struct { br_x509_certificate *items; int count; int capacity; } CertArray; typedef struct { int code; string name; string comment; } BearSSLErrorInfo; #endif typedef struct { char *data; size_t head; size_t size; size_t capacity; } ByteQueue; struct Connection { int fd; ByteQueue input; ByteQueue output; uint32_t ipaddr; int served_count; bool https; bool closing; bool keep_alive; uint64_t creation_time; uint64_t start_time; #if HTTPS br_ssl_server_context https_context; char https_buffer[BR_SSL_BUFSIZE_BIDI]; #endif }; /////////////////////////////////////////////////////////////////////////////////////////////// /// FORWARD DECLARATIONS /// /////////////////////////////////////////////////////////////////////////////////////////////// static bool url_parse2(string str, size_t *i, url_t *url); static bool url_parse(string str, url_t *url); static bool url_parse_ipv6(string str, uint16_t out[8]); static bool url_parse_ipv4(string str, uint32_t *out); static void byte_queue_init(ByteQueue *q); static void byte_queue_free(ByteQueue *q); static size_t byte_queue_size(ByteQueue *q); static bool byte_queue_ensure_min_free_space(ByteQueue *q, size_t num); static string byte_queue_start_write(ByteQueue *q); static void byte_queue_end_write(ByteQueue *q, size_t num); static string byte_queue_start_read(ByteQueue *q); static void byte_queue_end_read(ByteQueue *q, size_t num); static bool byte_queue_write(ByteQueue *q, string src); static void byte_queue_patch(ByteQueue *q, size_t offset, char *src, size_t len); #if HTTPS static bool load_private_key_from_file(string file, PrivateKey *pkey); static void free_private_key(PrivateKey *pkey); static bool load_certs_from_file(string file, CertArray *array); static void free_certs(CertArray *array); static BearSSLErrorInfo get_bearssl_error_info(int code); #endif static bool set_blocking(int fd, bool blocking); static bool read_from_socket(int fd, ByteQueue *queue); static bool write_to_socket(int fd, ByteQueue *queue); static int create_listening_socket(string addr, int port); /////////////////////////////////////////////////////////////////////////////////////////////// /// GLOBALS /// /////////////////////////////////////////////////////////////////////////////////////////////// static volatile sig_atomic_t stop = 0; static Connection *conns; static int num_conns = 0; static int max_conns = 0; static struct pollfd *pollarray; static uint64_t now; static uint64_t real_now; static int insecure_fd; static int secure_fd; static bool show_io; static bool show_requests; static bool access_log; static int keep_alive_max_requests; static int connection_timeout_sec; static int closing_timeout_sec; static int request_timeout_sec; static int log_flush_timeout_sec; static void (*respond_callback)(Request, ResponseBuilder*); #if HTTPS static PrivateKey pkey; static CertArray certs; #endif /////////////////////////////////////////////////////////////////////////////////////////////// /// REQUEST PARSER /// /////////////////////////////////////////////////////////////////////////////////////////////// // TODO: Make sure every string in request is reasonaly long static int parse_request_head(string str, Request *request) { char *src = str.data; size_t len = str.size; size_t cur; if (len > 2 && src[0] == 'G' && src[1] == 'E' && src[2] == 'T') { request->method = M_GET; cur = 3; } else if (len > 3 && src[0] == 'H' && src[1] == 'E' && src[2] == 'A' && src[3] == 'D') { request->method = M_HEAD; cur = 4; } else if (len > 3 && src[0] == 'P' && src[1] == 'O' && src[2] == 'S' && src[3] == 'T') { request->method = M_POST; cur = 4; } else if (len > 2 && src[0] == 'P' && src[1] == 'U' && src[2] == 'T') { request->method = M_PUT; cur = 3; } else if (len > 5 && src[0] == 'D' && src[1] == 'E' && src[2] == 'L' && src[3] == 'E' && src[4] == 'T' && src[5] == 'E') { request->method = M_DELETE; cur = 6; } else if (len > 6 && src[0] == 'C' && src[1] == 'O' && src[2] == 'N' && src[3] == 'N' && src[4] == 'E' && src[5] == 'C' && src[6] == 'T') { request->method = M_CONNECT; cur = 7; } else if (len > 6 && src[0] == 'O' && src[1] == 'P' && src[2] == 'T' && src[3] == 'I' && src[4] == 'O' && src[5] == 'N' && src[6] == 'S') { request->method = M_OPTIONS; cur = 7; } else if (len > 4 && src[0] == 'T' && src[1] == 'R' && src[2] == 'A' && src[3] == 'C' && src[4] == 'E') { request->method = M_TRACE; cur = 5; } else if (len > 4 && src[0] == 'P' && src[1] == 'A' && src[2] == 'T' && src[3] == 'C' && src[4] == 'H') { request->method = M_PATCH; cur = 5; } else { return P_BADMETHOD; } if (cur == len || src[cur] != ' ') return P_INCOMPLETE; cur++; if (!url_parse2(str, &cur, &request->url)) return P_BADURL; if (cur == len || src[cur] != ' ') return P_INCOMPLETE; cur++; if (cur+4 >= len || src[cur+0] != 'H' || src[cur+1] != 'T' || src[cur+2] != 'T' || src[cur+3] != 'P' || src[cur+4] != '/' || !is_digit(src[cur+5])) return P_BADVERSION; cur += 5; request->major = src[cur] - '0'; cur++; if (cur < len && src[cur] == '.') { cur++; if (cur == len || !is_digit(src[cur])) return P_BADVERSION; request->minor = src[cur] - '0'; cur++; } else { request->minor = 0; } if (cur+1 >= len || src[cur+0] != '\r' || src[cur+1] != '\n') return P_INCOMPLETE; cur += 2; request->nheaders = 0; while (cur+1 >= len || src[cur+0] != '\r' || src[cur+1] != '\n') { string name; string value; size_t start = cur; // TODO: More robust while (cur < len && src[cur] != ':') cur++; name.data = src + start; name.size = cur - start; if (cur == len) return P_BADHEADER; cur++; // : // TODO: More robust start = cur; while (cur < len && src[cur] != '\r') cur++; value.data = src + start; value.size = cur - start; cur++; // \r if (cur == len || src[cur] != '\n') return P_BADHEADER; cur++; // \n if (request->nheaders < MAX_HEADERS) { request->headers[request->nheaders].name = name; request->headers[request->nheaders].value = value; request->nheaders++; } } // cur here points to the \r in \r\n request->content = NULLSTR; return P_OK; } static bool find_header(Request *request, string name, string *value) { for (int i = 0; i < request->nheaders; i++) if (string_match_case_insensitive(request->headers[i].name, name)) { *value = request->headers[i].value; return true; } return false; } static string get_status_string(int status) { switch(status) { case 100: return LIT("Continue"); case 101: return LIT("Switching Protocols"); case 102: return LIT("Processing"); case 200: return LIT("OK"); case 201: return LIT("Created"); case 202: return LIT("Accepted"); case 203: return LIT("Non-Authoritative Information"); case 204: return LIT("No Content"); case 205: return LIT("Reset Content"); case 206: return LIT("Partial Content"); case 207: return LIT("Multi-Status"); case 208: return LIT("Already Reported"); case 300: return LIT("Multiple Choices"); case 301: return LIT("Moved Permanently"); case 302: return LIT("Found"); case 303: return LIT("See Other"); case 304: return LIT("Not Modified"); case 305: return LIT("Use Proxy"); case 306: return LIT("Switch Proxy"); case 307: return LIT("Temporary Redirect"); case 308: return LIT("Permanent Redirect"); case 400: return LIT("Bad Request"); case 401: return LIT("Unauthorized"); case 402: return LIT("Payment Required"); case 403: return LIT("Forbidden"); case 404: return LIT("Not Found"); case 405: return LIT("Method Not Allowed"); case 406: return LIT("Not Acceptable"); case 407: return LIT("Proxy Authentication Required"); case 408: return LIT("Request Timeout"); case 409: return LIT("Conflict"); case 410: return LIT("Gone"); case 411: return LIT("Length Required"); case 412: return LIT("Precondition Failed"); case 413: return LIT("Request Entity Too Large"); case 414: return LIT("Request-URI Too Long"); case 415: return LIT("Unsupported Media Type"); case 416: return LIT("Requested Range Not Satisfiable"); case 417: return LIT("Expectation Failed"); case 418: return LIT("I'm a teapot"); case 420: return LIT("Enhance your calm"); case 422: return LIT("Unprocessable Entity"); case 426: return LIT("Upgrade Required"); case 429: return LIT("Too many requests"); case 431: return LIT("Request Header Fields Too Large"); case 449: return LIT("Retry With"); case 451: return LIT("Unavailable For Legal Reasons"); case 500: return LIT("Internal Server Error"); case 501: return LIT("Not Implemented"); case 502: return LIT("Bad Gateway"); case 503: return LIT("Service Unavailable"); case 504: return LIT("Gateway Timeout"); case 505: return LIT("HTTP Version Not Supported"); case 509: return LIT("Bandwidth Limit Exceeded"); } return LIT("???"); } static size_t parse_content_length(string s) { char *src = s.data; size_t len = s.size; size_t cur = 0; while (cur < len && is_space(src[cur])) cur++; if (cur == len || !is_digit(src[cur])) return -1; size_t x = 0; do { int d = src[cur] - '0'; if (x > (SIZE_MAX - d) / 10) return -1; x = x * 10 + d; cur++; } while (cur < len && is_digit(src[cur])); while (cur < len && is_space(src[cur])) cur++; if (cur != len) return -1; return x; } static int find_and_parse_transfer_encoding(Request *request) { string value; if (!find_header(request, LIT("Transfer-Encoding"), &value)) return 0; int res = 0; char *src = value.data; size_t len = value.size; size_t cur = 0; for (;;) { while (cur < len && (is_space(src[cur]) || src[cur] == ',')) cur++; if (cur+6 < len && src[cur+0] == 'c' && src[cur+1] == 'h' && src[cur+2] == 'u' && src[cur+3] == 'n' && src[cur+4] == 'k' && src[cur+5] == 'e' && src[cur+6] == 'd') { cur += 7; res |= T_CHUNKED; } else if (cur+7 < len && src[cur+0] == 'c' && src[cur+1] == 'o' && src[cur+2] == 'm' && src[cur+3] == 'p' && src[cur+4] == 'r' && src[cur+5] == 'e' && src[cur+6] == 's' && src[cur+7] == 's') { cur += 8; res |= T_COMPRESS; } else if (cur+6 < len && src[cur+0] == 'd' && src[cur+1] == 'e' && src[cur+2] == 'f' && src[cur+3] == 'l' && src[cur+4] == 'a' && src[cur+5] == 't' && src[cur+6] == 'e') { cur += 7; res |= T_DEFLATE; } else if (cur+3 < len && src[cur+0] == 'g' && src[cur+1] == 'z' && src[cur+2] == 'i' && src[cur+3] == 'p') { cur += 4; res |= T_GZIP; } else { return -1; } } return res; } static void response_builder_init(ResponseBuilder *b, Connection *conn) { b->state = R_STATUS; b->conn = conn; b->failed = false; b->keep_alive = true; b->content_length_offset = -1; b->content_offset = -1; } void status_line(ResponseBuilder *b, int status) { if (b->state != R_STATUS) log_fatal(LIT("Appending status line twice\n")); if (!b->failed) { char buf[1<<10]; string status_string = get_status_string(status); int num = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %.*s\r\n", status, (int) status_string.size, status_string.data); assert(num > 0 && num < SIZEOF(buf)); if (!byte_queue_write(&b->conn->output, (string) {buf, num})) b->failed = true; } b->state = R_HEADER; } void add_header(ResponseBuilder *b, string header) { if (b->state != R_HEADER) { if (b->state == R_STATUS) log_fatal(LIT("Didn't write status line before headers\n")); else log_fatal(LIT("Can't add headers after content\n")); } if (b->failed) return; if (!byte_queue_write(&b->conn->output, header) || !byte_queue_write(&b->conn->output, LIT("\r\n"))) { b->failed = true; return; } } void add_header_f(ResponseBuilder *b, const char *fmt, ...) { char buffer[1<<10]; va_list args; va_start(args, fmt); int num = vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); if (num < 0 || num >= (int) sizeof(buffer)) { b->failed = true; return; } buffer[num] = '\0'; add_header(b, (string) {buffer, num}); } static bool should_keep_alive(Connection *conn); static void append_special_headers(ResponseBuilder *b) { if (should_keep_alive(b->conn)) add_header(b, LIT("Connection: Keep-Alive")); else { add_header(b, LIT("Connection: Close")); b->conn->closing = true; b->conn->start_time = now; } b->content_length_offset = byte_queue_size(&b->conn->output) + sizeof("Content-Length: ") - 1; add_header(b, LIT("Content-Length: ")); if (!byte_queue_write(&b->conn->output, LIT("\r\n"))) b->failed = true; b->content_offset = byte_queue_size(&b->conn->output); } void append_content_s(ResponseBuilder *b, string str) { if (b->state == R_HEADER) { append_special_headers(b); b->state = R_CONTENT; } if (b->state != R_CONTENT) log_fatal(LIT("Invalid response builder state\n")); if (b->failed) return; if (!byte_queue_write(&b->conn->output, str)) { b->failed = true; return; } } string append_content_start(ResponseBuilder *b, size_t cap) { if (b->state == R_HEADER) { append_special_headers(b); b->state = R_CONTENT; } if (b->state != R_CONTENT) log_fatal(LIT("Invalid response builder state\n")); if (b->failed) return NULLSTR; if (!byte_queue_ensure_min_free_space(&b->conn->output, cap)) { b->failed = true; return NULLSTR; } return byte_queue_start_write(&b->conn->output); } void append_content_end(ResponseBuilder *b, size_t num) { byte_queue_end_write(&b->conn->output, num); } void append_content_f(ResponseBuilder *b, const char *fmt, ...) { size_t cap = 128; for (;;) { string dst = append_content_start(b, cap); if (dst.size == 0) return; va_list args; va_start(args, fmt); int num = vsnprintf(dst.data, dst.size, fmt, args); assert(num >= 0); va_end(args); if ((size_t) num < cap) { append_content_end(b, num); break; } cap *= 2; } } bool append_file(ResponseBuilder *b, string file) { char copy[1<<12]; if (file.size >= sizeof(copy)) return false; memcpy(copy, file.data, file.size); copy[file.size] = '\0'; struct stat buf; if (stat(copy, &buf)) return false; if (!S_ISREG(buf.st_mode)) return false; int fd; do fd = open(copy, O_RDONLY); while (fd < 0 && errno == EINTR); if (fd < 0) return true; string dst = append_content_start(b, (size_t) buf.st_size); if (dst.size == 0) { close(fd); return false; } assert(dst.size >= (size_t) buf.st_size); size_t copied = 0; while (copied < (size_t) buf.st_size) { int num = read(fd, dst.data + copied, (size_t) buf.st_size - copied); if (num <= 0) { if (num < 0) return false; break; } copied += num; } append_content_end(b, copied); close(fd); return true; } static void response_builder_complete(ResponseBuilder *b) { if (b->state == R_COMPLETE) return; if (b->failed) return; if (b->state == R_HEADER) { append_special_headers(b); if (b->failed) return; } else { if (b->state != R_CONTENT) log_fatal(LIT("Invalid response builder state\n")); } size_t current_offset = byte_queue_size(&b->conn->output); size_t content_length = current_offset - b->content_offset; if (content_length > 1<<30) { // Content larger than 1GB b->failed = true; return; } int content_length_int = (int) content_length; char content_length_string[128]; int n = snprintf(content_length_string, sizeof(content_length_string), "%d", content_length_int); assert(n >= 1 && n <= 9); byte_queue_patch(&b->conn->output, b->content_length_offset, content_length_string, n); b->state = R_COMPLETE; } static bool should_keep_alive(Connection *conn) { // Don't keep alive if the peer doesn't want to if (conn->keep_alive == false) return false; // Don't keep alive if the request is too old if (now - conn->creation_time > (uint64_t) connection_timeout_sec * 1000) return false; // Don't keep alive if we served a lot of requests to this connection if (conn->served_count > keep_alive_max_requests) return false; // Don't keep alive if the server is more than 70% full if (num_conns > 0.7 * max_conns) return false; return true; } static uint64_t deadline_of(Connection *conn) { return conn->start_time + (conn->closing ? closing_timeout_sec : request_timeout_sec) * 1000; } static bool respond_to_available_requests(Connection *conn) { bool remove = false; int pipeline_count = 0; while (!remove) { /* Respond loop start */ string src = byte_queue_start_read(&conn->input); // Look for the \r\n\r\n size_t j = 0; while (j+3 < src.size && (src.data[j] != '\r' || src.data[j+1] != '\n' || src.data[j+2] != '\r' || src.data[j+3] != '\n')) j++; if (j+3 >= src.size) break; // No \r\n\r\n size_t head_length = j+4; if (show_requests) { print_bytes(LIT(""), (string) {src.data, head_length}); log_data(LIT("\n")); } // Found! We got the request head Request request; int res = parse_request_head((string) {src.data, head_length}, &request); if (access_log) { // Log access time_t real_now_in_secs = real_now / 1000; struct tm timeinfo; localtime_r(&real_now_in_secs, &timeinfo); char timebuf[128]; size_t timelen = strftime(timebuf, sizeof(timebuf), "%Y/%m/%d %H:%M:%S", &timeinfo); if (timelen == 0) log_fatal(LIT("Couldn't format time for access log")); timebuf[timelen] = '\0'; char ipbuf[INET_ADDRSTRLEN]; const char *ipstr = inet_ntop(AF_INET, &conn->ipaddr, ipbuf, sizeof(ipbuf)); if (ipstr == NULL) log_fatal(LIT("Couldn't format IP address for access log")); if (res == P_OK) { string user_agent; if (!find_header(&request, LIT("User-Agent"), &user_agent)) user_agent = LIT("No User-Agent"); else user_agent = trim(user_agent); log_format("%s - %s - %.*s - %.*s\n", timebuf, ipstr, (int) request.url.path.size, request.url.path.data, (int) user_agent.size, user_agent.data); } else { log_format("%s - %s - Bad request\n", timebuf, ipstr); } } if (res != P_OK) { // Invalid HTTP request byte_queue_write(&conn->output, LIT( "HTTP/1.1 400 Bad Request\r\n" "Connection: Close\r\n" "\r\n")); conn->closing = true; conn->start_time = now; break; } string content_length_header; size_t content_length; if (!find_header(&request, LIT("Content-Length"), &content_length_header)) { if (find_and_parse_transfer_encoding(&request) & T_CHUNKED) { // Content-Length missing byte_queue_write(&conn->output, LIT( "HTTP/1.1 411 Length Required\r\n" "Connection: Close\r\n" "\r\n")); conn->closing = true; conn->start_time = now; log_data(LIT("Content-Length missing\n")); break; } else content_length = 0; } else { content_length = parse_content_length(content_length_header); if (content_length == (size_t) -1) { // Invalid Content-Length byte_queue_write(&conn->output, LIT( "HTTP/1.1 400 Bad Request\r\n" "Connection: Close\r\n" "\r\n")); conn->closing = true; conn->start_time = now; log_data(LIT("Invalid Content-Length\n")); break; } } if (content_length > 1<<20) { // Request too large byte_queue_write(&conn->output, LIT( "HTTP/1.1 413 Content Too Large\r\n" "Connection: Close\r\n" "\r\n")); conn->closing = true; conn->start_time = now; log_data(LIT("Request too large\n")); break; } size_t request_length = head_length + content_length; if (src.size < request_length) break; // Request wasn't completely received yet request.content.data = src.data + head_length; request.content.size = content_length; // Reset the request timer conn->start_time = now; conn->keep_alive = true; string keep_alive_header; if (find_header(&request, LIT("Connection"), &keep_alive_header)) { if (string_match_case_insensitive(trim(keep_alive_header), LIT("Close"))) conn->keep_alive = false; } // Respond ResponseBuilder builder; response_builder_init(&builder, conn); assert(respond_callback); respond_callback(request, &builder); response_builder_complete(&builder); if (builder.failed) remove = true; else { conn->served_count++; byte_queue_end_read(&conn->input, request_length); if (!conn->keep_alive) { conn->closing = true; conn->start_time = now; } pipeline_count++; if (pipeline_count == 10) { // TODO: We should send a response to the client instead of dropping it log_data(LIT("Pipeline limit reached\n")); remove = true; break; } } } return remove; } void build_poll_array(struct pollfd *pollarray, int *timeout) { pollarray[0].fd = insecure_fd; pollarray[0].events = (num_conns < max_conns ? POLLIN : 0); pollarray[0].revents = 0; #if HTTPS pollarray[1].fd = secure_fd; pollarray[1].events = (num_conns < max_conns ? POLLIN : 0); pollarray[1].revents = 0; #else pollarray[1].fd = -1; pollarray[1].events = 0; pollarray[1].revents = 0; #endif Connection *oldest = NULL; for (int i = 0; i < max_conns; i++) { Connection *conn = &conns[i]; int events = 0; if (conn->fd == -1) { pollarray[i+2].fd = -1; pollarray[i+2].events = 0; pollarray[i+2].revents = 0; continue; } if (conn->https) { #if HTTPS int state = br_ssl_engine_current_state(&conn->https_context.eng); if (state & BR_SSL_SENDREC) events |= POLLOUT; if (state & BR_SSL_RECVREC) events |= POLLIN; #endif } else { if (byte_queue_size(&conn->output) > 0) events |= POLLOUT; if (!conn->closing) events |= POLLIN; } pollarray[i+2].fd = conn->fd; pollarray[i+2].events = events; pollarray[i+2].revents = 0; if (oldest == NULL || deadline_of(oldest) > deadline_of(conn)) oldest = conn; } if (oldest == NULL) *timeout = -1; else { if (deadline_of(oldest) < now) *timeout = 0; else *timeout = deadline_of(oldest) - now; } } void init_connection(Connection *conn, int fd, uint32_t ipaddr, bool https) { byte_queue_init(&conn->input); byte_queue_init(&conn->output); conn->fd = fd; conn->ipaddr = ipaddr; conn->closing = false; conn->https = https; conn->served_count = 0; conn->creation_time = now; conn->start_time = now; #if HTTPS if (https) { if (pkey.type == BR_KEYTYPE_RSA) br_ssl_server_init_full_rsa(&conn->https_context, certs.items, certs.count, &pkey.rsa); else { assert(pkey.type == BR_KEYTYPE_EC); unsigned issuer_key_type = BR_KEYTYPE_RSA; // Not sure if this or BR_KEYTYPE_EC br_ssl_server_init_full_ec(&conn->https_context, certs.items, certs.count, issuer_key_type, &pkey.ec); } br_ssl_engine_set_versions(&conn->https_context.eng, BR_TLS10, BR_TLS12); br_ssl_engine_set_buffer(&conn->https_context.eng, conn->https_buffer, sizeof(conn->https_buffer), 1); br_ssl_server_reset(&conn->https_context); } #endif } void free_connection(Connection *conn) { assert(conn->fd != -1); close(conn->fd); byte_queue_free(&conn->input); byte_queue_free(&conn->output); conn->fd = -1; conn->start_time = -1; conn->closing = false; conn->creation_time = 0; } bool accept_connection(int listen_fd, bool https) { // Look for a connection structure int index = 0; while (index < max_conns && conns[index].fd != -1) index++; if (index == max_conns) return false; // Stop listening for incoming connections struct sockaddr_in accepted_addr; socklen_t accepted_addrlen = sizeof(accepted_addr); int accepted_fd = accept(listen_fd, (struct sockaddr*) &accepted_addr, &accepted_addrlen); if (accepted_fd < 0) { if (errno == EINTR) return true; if (errno == EAGAIN || errno == EWOULDBLOCK) return false; log_perror(LIT("accept")); return false; } uint32_t ipaddr = (uint32_t) accepted_addr.sin_addr.s_addr; if (!set_blocking(accepted_fd, false)) { log_perror(LIT("fcntl")); close(accepted_fd); return true; } Connection *conn = &conns[index]; init_connection(conn, accepted_fd, ipaddr, https); assert(num_conns < max_conns); num_conns++; return true; } // Returns true iff the connection should be dropped bool update_connection_http(Connection *conn, struct pollfd *polldata) { // POLLIN if ((!conn->closing) && (polldata->revents & (POLLIN | POLLHUP | POLLERR))) { if (read_from_socket(conn->fd, &conn->input)) return true; if (respond_to_available_requests(conn)) return true; } // POLLOUT if (polldata->revents & POLLOUT) { if (write_to_socket(conn->fd, &conn->output)) return true; if (byte_queue_size(&conn->output) == 0 && conn->closing) return true; } return false; // Don't close } #if HTTPS // Returns true iff the connection should be dropped bool update_connection_https(Connection *conn, struct pollfd *polldata) { br_ssl_engine_context *cc = &conn->https_context.eng; bool flushed = false; for (;;) { int state = br_ssl_engine_current_state(cc); if (state & BR_SSL_CLOSED) { // Engine is finished, no more I/O (until next reset). int error = br_ssl_engine_last_error(cc); if (error != BR_ERR_OK) { BearSSLErrorInfo error_info = get_bearssl_error_info(error); log_format("SSL failure: %.*s (%.*s)\n", (int) error_info.name.size, error_info.name.data, (int) error_info.comment.size, error_info.comment.data); } return true; } if ((state & BR_SSL_SENDREC) && (polldata->revents & POLLOUT)) { // Engine has some bytes to send to the peer size_t len; unsigned char *buf = br_ssl_engine_sendrec_buf(cc, &len); size_t copied = 0; while (copied < len) { int num = send(conn->fd, buf + copied, len - copied, 0); if (num < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { polldata->revents &= ~POLLOUT; break; } log_perror(LIT("send")); return true; } // TODO: Handle num=0 copied += (size_t) num; } br_ssl_engine_sendrec_ack(cc, copied); flushed = false; } if ((state & BR_SSL_RECVAPP)) { // Engine has obtained some application data from the // peer, that should be read by the caller. size_t len; unsigned char *buf = br_ssl_engine_recvapp_buf(cc, &len); if (!byte_queue_ensure_min_free_space(&conn->input, len)) return true; string dst = byte_queue_start_write(&conn->input); assert(dst.size >= len); memcpy(dst.data, buf, len); if (show_io) print_bytes(LIT("> "), (string) {dst.data, len}); byte_queue_end_write(&conn->input, len); br_ssl_engine_recvapp_ack(cc, len); if (respond_to_available_requests(conn)) return true; flushed = false; } if ((state & BR_SSL_RECVREC) && (polldata->revents & POLLIN)) { // Engine expects some bytes from the peer size_t len; unsigned char *buf = br_ssl_engine_recvrec_buf(cc, &len); size_t copied = 0; while (copied < len) { int num = recv(conn->fd, buf + copied, len - copied, 0); if (num < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { polldata->revents &= ~POLLIN; break; } log_perror(LIT("recv")); return true; } if (num == 0) { return true; } // TODO: Handle num=0 copied += (size_t) num; } br_ssl_engine_recvrec_ack(cc, copied); flushed = false; } if ((state & BR_SSL_SENDAPP) && byte_queue_size(&conn->output) > 0) { // Engine may receive application data to send (or flush). size_t len; unsigned char *buf = br_ssl_engine_sendapp_buf(cc, &len); string src = byte_queue_start_read(&conn->output); size_t copy = MIN(len, src.size); memcpy(buf, src.data, copy); if (show_io) print_bytes(LIT("< "), (string) {src.data, copy}); byte_queue_end_read(&conn->output, copy); br_ssl_engine_sendapp_ack(cc, copy); br_ssl_engine_flush(cc, 0); // TODO: Is this the right time to call it? flushed = false; } if (flushed) break; br_ssl_engine_flush(cc, 0); flushed = true; } int state = br_ssl_engine_current_state(cc); if ((state & BR_SSL_SENDREC) == 0 && conn->closing && byte_queue_size(&conn->output) == 0) return true; return false; // Don't remove } #endif bool update_connection(Connection *conn, struct pollfd *polldata) { bool ok; #if HTTPS if (conn->https) ok = update_connection_https(conn, polldata); else #endif ok = update_connection_http(conn, polldata); return ok; } void http_stop(void) { stop = 1; } HTTPConfig http_default_config(void) { return (HTTPConfig) { .http_port = 8080, .http_addr = LIT("127.0.0.1"), .https_port = 8081, .https_addr = LIT("127.0.0.1"), .cert_file = NULLSTR, .privkey_file = NULLSTR, .access_log = true, .show_io = false, .show_requests = false, .max_connections = 512, .keep_alive_max_requests = 1000, .connection_timeout_sec = 60, .closing_timeout_sec = 2, .request_timeout_sec = 5, .log_flush_timeout_sec = 3, .respond = NULL, }; } void http_init(HTTPConfig config) { show_io = config.show_io; show_requests = config.show_requests; access_log = config.access_log; keep_alive_max_requests = config.keep_alive_max_requests; connection_timeout_sec = config.connection_timeout_sec; closing_timeout_sec = config.closing_timeout_sec; request_timeout_sec = config.request_timeout_sec; log_flush_timeout_sec = config.log_flush_timeout_sec; respond_callback = config.respond; { struct rlimit file_desc_limit; if (getrlimit(RLIMIT_NOFILE, &file_desc_limit)) log_fatal(LIT("Couldn't query RLIMIT_NOFILE\n")); max_conns = config.max_connections; num_conns = 0; if ((size_t) max_conns+2 > file_desc_limit.rlim_cur) log_fatal(LIT("max_connections+2 is higher than the rlimit\n")); conns = mymalloc(max_conns * sizeof(Connection)); if (conns == NULL) log_fatal(LIT("Out of memory")); for (int i = 0; i < max_conns; i++) { conns[i].fd = -1; byte_queue_init(&conns[i].input); byte_queue_init(&conns[i].output); } pollarray = mymalloc((max_conns+2) * sizeof(struct pollfd)); if (pollarray == NULL) log_fatal(LIT("Out of memory")); DEBUG("Connection array created\n"); } // Create plain text listener { string http_addr = config.http_addr; uint32_t http_port = config.http_port; insecure_fd = create_listening_socket(http_addr, http_port); if (insecure_fd < 0) log_fatal(LIT("Couldn't bind\n")); log_format("Listening on %.*s:%d\n", (int) http_addr.size, http_addr.data, http_port); DEBUG("HTTP started\n"); } // Create secure listener { secure_fd = -1; #if HTTPS string https_addr = config.https_addr; uint32_t https_port = config.https_port; string https_cert_file = config.cert_file; string https_key_file = config.privkey_file; secure_fd = create_listening_socket(https_addr, https_port); if (secure_fd < 0) log_fatal(LIT("Couldn't bind\n")); log_format("Listening on %.*s:%d\n", (int) https_addr.size, https_addr.data, https_port); // Load certificate if (!load_certs_from_file(https_cert_file, &certs)) log_fatal(LIT("Couldn't load certificates\n")); DEBUG("Certificates loaded\n"); // Load private key if (!load_private_key_from_file(https_key_file, &pkey)) log_fatal(LIT("Couldn't load private key\n")); DEBUG("Private key loaded\n"); DEBUG("HTTPS started\n"); #endif } } void http_loop(void) { uint64_t last_log_time = 0; while (!stop) { int timeout; build_poll_array(pollarray, &timeout); if (!log_empty()) { int log_timeout = (last_log_time + log_flush_timeout_sec * 1000) - now; if (timeout < 0) timeout = log_timeout; else timeout = MIN(log_timeout, timeout); } int ret = poll(pollarray, max_conns+2, timeout); if (ret < 0) { if (errno == EINTR) break; // TODO: Should this be continue? log_perror(LIT("poll")); exit(-1); } now = get_monotonic_time_ms(); real_now = get_real_time_ms(); if (pollarray[0].revents & POLLIN) while (accept_connection(insecure_fd, false)); #if HTTPS if (pollarray[1].revents & POLLIN) while (accept_connection(secure_fd, true)); #endif for (int i = 0; i < max_conns; i++) { Connection *conn = &conns[i]; if (conn->fd == -1) continue; struct pollfd *polldata = &pollarray[i+2]; bool remove = false; if (now >= deadline_of(conn)) { assert(!remove); if (conn->closing) { // Closing timeout remove = true; log_data(LIT("Closing timeout\n")); } else { // Request timeout if (byte_queue_size(&conn->input) == 0) { // Connection was idle, so just close it remove = true; log_data(LIT("Idle connection timeout\n")); } else { byte_queue_write(&conn->output, LIT( "HTTP/1.1 408 Request Timeout\r\n" "Connection: Close\r\n" "\r\n")); conn->closing = true; conn->start_time = now; log_data(LIT("Request timeout\n")); } } } if (!remove) remove = update_connection(conn, polldata); if (remove) { free_connection(conn); num_conns--; } } if (now - last_log_time > (uint64_t) log_flush_timeout_sec * 1000) { log_flush(); last_log_time = now; } } /* main loop end */ } void http_free(void) { { #if HTTPS free_private_key(&pkey); free_certs(&certs); close(secure_fd); #endif close(insecure_fd); for (int i = 0; i < max_conns; i++) { if (conns[i].fd != -1) { close(conns[i].fd); byte_queue_free(&conns[i].input); byte_queue_free(&conns[i].output); } } myfree(conns, max_conns * sizeof(Connection)); myfree(pollarray, (max_conns+2) * sizeof(struct pollfd)); } } #define PATH_SEP '/' int split_path_components(string src, string *stack, int limit, bool allow_ddots) { size_t cur = 0; // Skip the first slash if (cur < src.size && src.data[cur] == PATH_SEP) cur++; int depth = 0; while (cur < src.size) { if (depth == limit) return -1; size_t start = cur; while (cur < src.size && (is_pcomp(src.data[cur]) || (allow_ddots && src.data[cur] == ':'))) cur++; string comp = substr(src, start, cur); if (comp.size == 0) return -1; // We consider paths with empty components invalid if (streq(comp, LIT(".."))) { if (depth == 0) return -1; depth--; } else { if (!streq(comp, LIT("."))) stack[depth++] = comp; } if (cur == src.size) break; if (src.data[cur] != PATH_SEP) return -1; cur++; } return depth; } /* * Sanitize a path string removing ./ and ../ * components. The final path has an initial * / but not final. */ size_t sanitize_path(string src, char *mem, size_t max) { #define MAX_COMPS 64 string stack[MAX_COMPS]; int depth; depth = split_path_components(src, stack, MAX_COMPS, false); if (depth < 0) return -1; /* * Count how many output bytes are required */ size_t req = depth; for (int i = 0; i < depth; i++) req += stack[i].size; if (req >= max) return -1; // Buffer too small /* * Copy the sanitized path into the output * buffer. */ size_t n = 0; for (int i = 0; i < depth; i++) { mem[n++] = PATH_SEP; memcpy(mem + n, stack[i].data, stack[i].size); n += stack[i].size; } mem[n] = '\0'; return n; } int match_path_format(string path, char *fmt, ...) { #define LIMIT 32 string p_stack[LIMIT]; string f_stack[LIMIT]; int p_depth; int f_depth; p_depth = split_path_components(path, p_stack, LIMIT, false); f_depth = split_path_components(STR(fmt), f_stack, LIMIT, true); if (p_depth < 0 || f_depth < 0) return -1; // Error if (p_depth != f_depth) return 1; // No match va_list args; va_start(args, fmt); for (int i = 0; i < f_depth; i++) { assert(f_stack[i].size > 0); assert(p_stack[i].size > 0); if (f_stack[i].data[0] == ':') { if (f_stack[i].size != 2) { va_end(args); return -1; // Invalid format } switch (f_stack[i].data[1]) { case 's': { string *sl = va_arg(args, string*); *sl = p_stack[i]; } break; case 'n': { uint32_t n = 0; size_t cur = 0; while (cur < p_stack[i].size && is_digit(p_stack[i].data[cur])) { int d = p_stack[i].data[cur] - '0'; if (n > (UINT32_MAX - d) / 10) { va_end(args); return -1; // Overflow } n = n * 10 + d; cur++; } if (cur != p_stack[i].size) { va_end(args); return -1; // Component isn't a number } uint32_t *p = va_arg(args, uint32_t*); *p = n; } break; default: va_end(args); return -1; // Invalid formt } } else { if (f_stack[i].size != p_stack[i].size) { va_end(args); return 1; // No match } if (memcmp(f_stack[i].data, p_stack[i].data, f_stack[i].size)) { va_end(args); return 1; // No match } } } va_end(args); return 0; // Match } struct { string mime; string ext; } mime_table[] = { {LIT("text/javascript"), LIT(".js")}, {LIT("text/javascript"), LIT(".javascript")}, {LIT("text/html"), LIT(".html")}, {LIT("text/html"), LIT(".htm")}, {LIT("image/gif"), LIT(".gif")}, {LIT("image/jpeg"), LIT(".jpg")}, {LIT("image/jpeg"), LIT(".jpeg")}, {LIT("image/svg+xml"), LIT(".svg")}, {LIT("video/mp4"), LIT(".mp4")}, {LIT("video/mpeg"), LIT(".mpeg")}, {LIT("font/ttf"), LIT(".ttf")}, {LIT("font/woff"), LIT(".woff")}, {LIT("font/woff2"), LIT(".woff2")}, {LIT("text/plain"), LIT(".txt")}, {LIT("audio/wav"), LIT(".wav")}, {LIT("application/x-7z-compressed"), LIT(".7z")}, {LIT("application/zip"), LIT(".zip")}, {LIT("application/xml"), LIT(".xml")}, {LIT("application/json"), LIT(".json")}, {NULLSTR, NULLSTR}, }; string mimetype_from_filename(string name) { for (size_t i = 0; i < COUNTOF(mime_table); i++) if (endswith(mime_table[i].ext, name)) return mime_table[i].mime; return NULLSTR; } bool serve_file_or_dir(ResponseBuilder *b, string prefix, string docroot, string reqpath, string mime, bool enable_dir_listing) { // Sanitize the request path char pathmem[1<<10]; string path; { size_t len = sanitize_path(reqpath, pathmem, sizeof(pathmem)); if (len >= sizeof(pathmem)) { status_line(b, 500); return true; } path = (string) {pathmem, len}; path.data[path.size] = '\0'; } // Only handle this request if the prefix matches if (!startswith(prefix, path)) return false; // Remove the matched prefix and put the docroot in its place { if (docroot.size + path.size - prefix.size >= sizeof(pathmem)) { status_line(b, 500); return true; } memmove(pathmem + docroot.size, pathmem + prefix.size, path.size - prefix.size); memcpy(pathmem, docroot.data, docroot.size); path.size -= prefix.size; path.size += docroot.size; path.data[path.size] = '\0'; } struct stat buf; if (stat(path.data, &buf)) { if (errno == ENOENT) return false; status_line(b, 500); return true; } if (S_ISREG(buf.st_mode)) { int fd; do fd = open(path.data, O_RDONLY); while (fd < 0 && errno == EINTR); if (fd < 0) { status_line(b, 500); close(fd); return true; } status_line(b, 200); if (mime.size == 0) mime = mimetype_from_filename(path); if (mime.size > 0) add_header_f(b, "Content-Type: %.*s", (int) mime.size, mime.data); string dst = append_content_start(b, (size_t) buf.st_size); if (dst.size == 0) { status_line(b, 500); close(fd); return true; } assert(dst.size >= (size_t) buf.st_size); size_t copied = 0; while (copied < (size_t) buf.st_size) { int num = read(fd, dst.data + copied, (size_t) buf.st_size - copied); if (num <= 0) { if (num < 0) log_format("Failed reading from '%.*s'\n", (int) path.size, path.data); break; } copied += num; } append_content_end(b, copied); close(fd); return true; } if (enable_dir_listing && S_ISDIR(buf.st_mode)) { DIR *d = opendir(path.data); if (d == NULL) { status_line(b, 500); return true; } status_line(b, 200); append_content_s(b, LIT( "\n" " \n" " \n" " \n" "
    \n" "
  • (parent)
  • ")); // TODO: Add links struct dirent *dir; while ((dir = readdir(d))) { if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue; append_content_f(b, "
  • %s
  • \n", dir->d_name); // TODO: Add links } append_content_s(b, LIT( "
\n" " \n" "\n")); closedir(d); return true; } return false; } /////////////////////////////////////////////////////////////////////////////////////////////// /// BYTE QUEUE /// /////////////////////////////////////////////////////////////////////////////////////////////// void byte_queue_init(ByteQueue *q) { q->data = NULL; q->head = 0; q->size = 0; q->capacity = 0; } void byte_queue_free(ByteQueue *q) { myfree(q->data, q->capacity); byte_queue_init(q); } bool byte_queue_ensure_min_free_space(ByteQueue *q, size_t num) { size_t total_free_space = q->capacity - q->size; size_t free_space_after_data = q->capacity - q->size - q->head; if (free_space_after_data < num) { if (total_free_space < num) { // Resize required size_t capacity = MAX(2 * q->capacity, q->size + num); char *data = mymalloc(capacity); if (!data) return false; if (q->size > 0) memcpy(data, q->data + q->head, q->size); myfree(q->data, q->capacity); q->data = data; q->head = 0; q->capacity = capacity; } else { // Move required memmove(q->data, q->data + q->head, q->size); q->head = 0; } } return true; } string byte_queue_start_write(ByteQueue *q) { if (q->data == NULL) return NULLSTR; return (string) { .data = q->data + (q->head + q->size), .size = q->capacity - (q->head + q->size), }; } void byte_queue_end_write(ByteQueue *q, size_t num) { q->size += num; } string byte_queue_start_read(ByteQueue *q) { if (q->data == NULL) return NULLSTR; return (string) { .data = q->data + q->head, .size = q->size, }; } size_t byte_queue_size(ByteQueue *q) { return q->size; } void byte_queue_end_read(ByteQueue *q, size_t num) { q->head += num; q->size -= num; } bool byte_queue_write(ByteQueue *q, string src) { if (!byte_queue_ensure_min_free_space(q, src.size)) return false; string dst = byte_queue_start_write(q); assert(dst.size >= src.size); memcpy(dst.data, src.data, src.size); byte_queue_end_write(q, src.size); return true; } void byte_queue_patch(ByteQueue *q, size_t offset, char *src, size_t len) { // TODO: Safety checks memcpy(q->data + q->head + offset, src, len); } /////////////////////////////////////////////////////////////////////////////////////////////// /// SOCKET UTILITIES /// /////////////////////////////////////////////////////////////////////////////////////////////// void print_bytes(string prefix, string str) { char *src = str.data; size_t len = str.size; bool line_start = true; size_t i = 0; while (i < len) { size_t substr_offset = i; while (i < len && src[i] != '\r' && src[i] != '\n') i++; size_t substr_length = i - substr_offset; if (line_start) { log_data(prefix); line_start = false; } log_data((string) { src + substr_offset, substr_length }); if (i < len) { if (src[i] == '\r') log_data(LIT("\\r")); else { log_data(LIT("\\n\n")); line_start = true; } i++; } } if (!line_start) log_data(LIT("\n")); } bool read_from_socket(int fd, ByteQueue *queue) { bool remove = false; for (;;) { if (!byte_queue_ensure_min_free_space(queue, 512)) { remove = true; break; } string dst = byte_queue_start_write(queue); int num = recv(fd, dst.data, dst.size, 0); if (num < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) break; log_perror(LIT("recv")); remove = true; break; } if (num == 0) { remove = true; break; } if (show_io) print_bytes(LIT("> "), (string) {dst.data, num}); byte_queue_end_write(queue, (size_t) num); // Input buffer can't go over 20Mb if (byte_queue_size(queue) > (size_t) INPUT_BUFFER_LIMIT_MB * 1024 * 1024) { remove = true; break; } } return remove; } bool write_to_socket(int fd, ByteQueue *queue) { bool remove = false; for (;;) { string src = byte_queue_start_read(queue); if (src.size == 0) break; int num = send(fd, src.data, src.size, 0); if (num < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) break; log_perror(LIT("send")); remove = true; break; } if (show_io) print_bytes(LIT("< "), (string) {src.data, num}); byte_queue_end_read(queue, (size_t) num); } return remove; } int create_listening_socket(string addr, int port) { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { log_perror(LIT("socket")); return -1; } if (!set_blocking(listen_fd, false)) { log_perror(LIT("fcntl")); return -1; } int one = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)); struct in_addr addr2; if (addr.size == 0) addr2.s_addr = htonl(INADDR_ANY); else { assert(addr.data); char addr_copy[INET_ADDRSTRLEN]; if (addr.size >= SIZEOF(addr_copy)) log_fatal(LIT("Invalid IP address (too long)\n")); memcpy(addr_copy, addr.data, addr.size); addr_copy[addr.size] = '\0'; int res = inet_pton(AF_INET, addr_copy, &addr2); if (res == 0) log_fatal(LIT("Invalid IP address (too long)\n")); if (res != 1) log_fatal(LIT("Could not parse IP address (internal error)\n")); } struct sockaddr_in addr3; addr3.sin_family = AF_INET; addr3.sin_port = htons(port); addr3.sin_addr = addr2; if (bind(listen_fd, (struct sockaddr*) &addr3, sizeof(addr3))) { log_perror(LIT("bind")); return -1; } if (listen(listen_fd, 32)) { log_perror(LIT("listen")); return -1; } return listen_fd; } bool set_blocking(int fd, bool blocking) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) return false; if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags)) return false; return true; } /////////////////////////////////////////////////////////////////////////////////////////////// /// URI PARSER /// /////////////////////////////////////////////////////////////////////////////////////////////// static bool is_lower_alpha(char c) { return c >= 'a' && c <= 'z'; } static bool is_upper_alpha(char c) { return c >= 'A' && c <= 'Z'; } static bool is_hex_digit(char c) { return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } static bool is_unreserved(char c) { return is_alpha(c) || is_digit(c) || c == '-' || c == '.' || c == '_' || c == '~'; } static bool is_subdelim(char c) { return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || c == '='; } static bool is_pchar(char c) { return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '@'; } static bool is_schema_first(char c) { return is_alpha(c); } static bool is_schema(char c) { return is_alpha(c) || is_digit(c) || c == '+' || c == '-' || c == '.'; } // Symbol: parse_schema // Parse the schema of an url, if there is one. // // Arguments: // (in) src: The source string. // // len: The number of bytes pointed by [src]. // // (in/out) i: Offset from where the parsing should // start. When the function returns, its // value is incremented by the number of // parsed bytes (therefore pointing to the // first non-parsed byte). If the url in // the source has no schema, it's value // is left unchanged. // // (out) schema: Non zero-terminated string containing // the parsed schema. If the url didn't // have a schema, it's set to NULL. // // (out) schema_len: Length of the parsed schema, or 0 if // there wasn't one. // // Returns: // - [i] is incremented by the number of parsed bytes. // // - [schema] points to the schema string (or NULL if // there wasn't one). // // - [schema_len] is the length of the string pointed // by [schema], or 0 if there was no schema. // // Notes: // - This function can never fail. // static void parse_schema(string str, size_t *i, string *schema) { char *src = str.data; size_t len = str.size; size_t peek = *i; // Local cursor bool no_schema; size_t schema_off; size_t schema_end; // If there is a schema, set [no_schema] to // [false], [schema_off] to the start of // the schema substring (relative to [src]) // and [schema_end] to its end. { if (peek == len || !is_schema_first(src[peek])) // The first character can't be the start // of a schema. no_schema = true; else { // The current character is a valid start // for a schema. We'll assume it is until // proven otherwise. // Keep track of the current posizion, then // consume all characters that compose the // schema. schema_off = peek; do peek++; while (peek < len && is_schema(src[peek])); schema_end = peek; // If the character following the schema // isn't a ':', it wasn't a schema after // all. if (peek == len || src[peek] != ':') no_schema = true; else { no_schema = false; peek++; // Skip the ':' } } } // Update the output parameters. if (no_schema) *schema = NULLSTR; // Don't unpdate [i] else { *schema = substr(str, schema_off, schema_end); *i = peek; // Commit changes. } } static bool is_username(char c) { return is_unreserved(c) || is_subdelim(c); } static bool is_username_first(char c) { return is_username(c); } static bool is_password(char c) { return is_username(c); } static bool is_password_first(char c) { return is_password(c); } // Symbol: parse_userinfo // Parse the userinfo component of an URL, // if there is one. // // The userinfo component is a subcomponent // of the authority. It contains a username // and, optionally, a password: // // http://username@example.com // http://username:password@example.com // // This functions parses the portion that // goes from the byte following "//" to // the '@'. // // Arguments: // (in) src: The source string. // // len: The number of bytes pointed by [src] // // (in/out) i: Offset from where the parsing should // start. When the function returns, its // value is incremented by the number of // parsed bytes (therefore pointing to the // first non-parsed byte). If the url in // the source has no userinfo, it's value // is left unchanged. // // (out) userinfo: The result of the parsing. // // Returns: // - [i] is incremented by the number of parsed bytes. // // - [userinfo] contains the parsed username and // password. These strings are not zero-terminated. // If there was no userinfo component, then // [userinfo.username] and [userinfo.password] are // NULL and [userinfo.username_len] and // [userinfo.password_len] are 0. If the username // is specified but the password isn't, then only // [userinfo.password] is NULL and // [userinfo.password_len] is 0. // // Notes: // - This function can never fail. // static void parse_userinfo(string str, size_t *i, url_userinfo *userinfo) { char *src = str.data; size_t len = str.size; size_t peek = *i; // Local cursor bool no_username; bool no_password; size_t username_off; size_t username_end; size_t password_off; size_t password_len; // If the userinfo subcomponent is present, // set [no_username] to false, [username_off] // to the offset of the username relative to // [src] and [username_end] to its end. // If the password was also specified, do the // same. { if (peek == len || !is_username_first(src[peek])) { // The first character can't be the first // of an username, therefore there's no // userinfo subcomponent. no_username = true; no_password = true; } else { // The first character is a valid start // for an username, therefore we'll assume // that there's a userinfo subcomponent // until proven otherwise. // Scan the username while keeping track // of it's offset and length. username_off = peek; do peek++; while (peek < len && is_username(src[peek])); username_end = peek; // If the username is followed by a ':' and // a valid password character, we also expect // a password. if (peek+1 < len && src[peek] == ':' && is_password_first(src[peek+1])) { peek++; // Skip the ':' // Scan the password while keeping track // of it's offset and length. password_off = peek; do peek++; while (peek < len && is_password(src[peek])); password_len = peek; if (peek == len || src[peek] != '@') { // If the password isn't followed by a '@', // then this wasn't a userinfo subcomponent // after all. no_username = true; no_password = true; } else { // All done. no_username = false; no_password = false; peek++; // Skip the '@' } } else if (peek == len || src[peek] != '@') { // Since no password was specified, if the // username isn't followed by a '@', then // this wasn't a userinfo subcomponent after // all. no_username = true; no_password = true; } else { // All done. no_username = false; no_password = true; peek++; // Skip the '@' } } } // Update the output parameters. if (no_username) { assert(no_password); userinfo->username = NULLSTR; userinfo->password = NULLSTR; // Don't update [i] } else { userinfo->username = substr(str, username_off, username_end); userinfo->password = no_password ? NULLSTR : substr(str, password_off, password_len); *i = peek; // Commit changes. } } static bool parse_ipv4_byte(string str, size_t *i, uint8_t *out) { char *src = str.data; size_t len = str.size; size_t peek = *i; // If the cursor refers to a digit, then // there's a byte to parse. bool start_with_digit = (peek < len && is_digit(src[peek])); if (start_with_digit) { uint8_t byte = 0; // TODO: Don't allow arbitrary sequence of // 0s at the start. do { int d = src[peek] - '0'; if (byte > (UINT8_MAX - d) / 10) break; // Overflow! This digit isn't part of the byte. byte = byte * 10 + d; peek++; } while (peek < len && is_digit(src[peek])); *i = peek; *out = byte; } return start_with_digit; } static uint32_t pack(uint8_t *bytes) { return ((uint32_t) bytes[0] << 24) | ((uint32_t) bytes[1] << 16) | ((uint32_t) bytes[2] << 8) | ((uint32_t) bytes[3] << 0); } static bool parse_ipv4(string str, size_t *i, uint32_t *ipv4) { char *src = str.data; size_t len = str.size; size_t peek = *i; uint8_t unpacked_ipv4[4]; for (int u = 0; u < 3; u++) { if (!parse_ipv4_byte(str, &peek, unpacked_ipv4 + u)) return false; if (peek == len || src[peek] != '.') return false; peek++; // Skip the dot } if (!parse_ipv4_byte(str, &peek, unpacked_ipv4 + 3)) return false; *ipv4 = pack(unpacked_ipv4); *i = peek; return true; } static int hex_digit_to_int(char c) { assert(is_hex_digit(c)); if (is_lower_alpha(c)) return c - 'a' + 10; if (is_upper_alpha(c)) return c - 'A' + 10; assert(is_digit(c)); return c - '0'; } static bool parse_ipv6_word(string str, size_t *i, uint16_t *out) { char *src = str.data; size_t len = str.size; size_t peek = *i; // If there's at least one hex digit at the // current position, then we can parse a word // for sure. bool start_with_hex_digit = (peek < len && is_hex_digit(src[peek])); if (start_with_hex_digit) { uint16_t word = 0; // TODO: Don't allow arbitrary sequence of // 0s at the start. do { int d = hex_digit_to_int(src[peek]); if (word > (UINT16_MAX - d) / 16) break; // Overflow! This hex digit isn't part of the word. word = word * 16 + d; peek++; } while (peek < len && is_hex_digit(src[peek])); *i = peek; *out = word; } return start_with_hex_digit; } static bool parse_ipv6(string str, size_t *i, uint16_t ipv6[static 8]) { char *src = str.data; size_t len = str.size; size_t k = *i; uint16_t tail[8]; size_t head_count = 0; size_t tail_count = 0; if (k+1 < len && src[k] == ':' && src[k+1] == ':') k += 2; else { while (1) { uint16_t word; if (!parse_ipv6_word(str, &k, &word)) return false; ipv6[head_count++] = word; if (head_count == 8) break; if (k == len || src[k] != ':') return false; k++; // Skip the ':' if (k < len && src[k] == ':') { k++; break; } } } if (head_count + tail_count < 8) { while (k < len && is_hex_digit(src[k])) { // We know the current character is a // hex digit, therefore [parse_ipv6_word] // won't fail. uint16_t word; (void) parse_ipv6_word(str, &k, &word); tail[tail_count++] = word; if (head_count + tail_count == 7) break; if (k == len || src[k] != ':') break; k++; // Skip the ':' } } for (size_t p = 0; p < 8 - head_count - tail_count; p++) ipv6[head_count + p] = 0; for (size_t p = 0; p < tail_count; p++) ipv6[8 - tail_count + p] = tail[p]; *i = k; return true; } static void parse_port(string str, size_t *i, bool *no_port, uint16_t *port) { char *src = str.data; size_t len = str.size; size_t k = *i; if (k+1 < len && src[k] == ':' && is_digit(src[k+1])) { k++; // Skip the ':' uint16_t p = 0; do { int d = src[k] - '0'; if (p > (UINT16_MAX - d) / 10) break; p = p * 10 + d; k++; } while (k < len && is_digit(src[k])); *port = p; *no_port = false; } else { *port = 0; *no_port = true; } *i = k; } static bool is_hostname(char c) { return is_unreserved(c) || is_subdelim(c); } static bool is_hostname_first(char c) { return is_hostname(c); } static bool parse_host(string str, size_t *i, url_host *host) { char *src = str.data; size_t len = str.size; size_t k = *i; if (k == len) return false; if (src[k] == '[') { k++; // Skip the '[' // IPv6 if (!parse_ipv6(str, &k, host->ipv6)) return false; if (k == len || src[k] != ']') return false; k++; // Skip the ']' host->mode = URL_HOSTMODE_IPV6; } else { uint32_t ipv4; bool is_ipv4; if (is_digit(src[k])) is_ipv4 = parse_ipv4(str, &k, &ipv4); else is_ipv4 = false; if (is_ipv4) { host->ipv4 = ipv4; host->mode = URL_HOSTMODE_IPV4; } else { if (!is_hostname_first(src[k])) return false; size_t name_off = k; do k++; while (k < len && is_hostname(src[k])); size_t name_end = k; host->mode = URL_HOSTMODE_NAME; host->name = substr(str, name_off, name_end); } } uint16_t port; bool no_port; parse_port(str, &k, &no_port, &port); host->port = port; host->no_port = no_port; *i = k; return true; } static bool parse_path(string str, size_t *i, string *path) { char *src = str.data; size_t len = str.size; size_t k = *i; size_t path_off; path_off = k; if (k < len && src[k] == '/') k++; else { if (k == len || !is_pchar(src[k])) return false; } while (k < len && is_pchar(src[k])) { do k++; while (k < len && is_pchar(src[k])); if (k == len || src[k] != '/') break; k++; // Skip the '/' } *i = k; *path = substr(str, path_off, k); return true; } static bool is_query(char c) { return is_pchar(c) || c == '/' || c == '?'; } static void parse_query(string str, size_t *i, string *query) { char *src = str.data; size_t len = str.size; bool no_query; size_t query_off; size_t query_end; size_t peek = *i; if (peek < len && src[peek] == '?') { peek++; // Skip the '?' query_off = peek; while (peek < len && is_query(src[peek])) peek++; query_end = peek; no_query = false; } else { query_off = 0; query_end = 0; no_query = true; } if (no_query) *query = NULLSTR; else { *i = peek; *query = substr(str, query_off, query_end); } } static bool is_fragment(char c) { return is_pchar(c) || c == '/'; } static void parse_fragment(string str, size_t *i, string *fragment) { char *src = str.data; size_t len = str.size; bool no_fragment; size_t fragment_off; size_t fragment_end; size_t peek = *i; if (peek < len && src[peek] == '#') { peek++; // Skip the '#' fragment_off = peek; while (peek < len && is_fragment(src[peek])) peek++; fragment_end = peek; no_fragment = false; } else { fragment_off = 0; fragment_end = 0; no_fragment = true; } if (no_fragment) *fragment = NULLSTR; else { *i = peek; *fragment = substr(str, fragment_off, fragment_end); } } static bool follows_authority(string str, size_t i) { return i+1 < str.size && str.data[i] == '/' && str.data[i+1] == '/'; } static bool url_parse2(string str, size_t *i, url_t *url) { char *src = str.data; size_t len = str.size; size_t maybe; if (i == NULL) { maybe = 0; i = &maybe; } parse_schema(str, i, &url->schema); if (follows_authority(str, *i)) { *i += 2; // Skip the "//" parse_userinfo(str, i, &url->userinfo); if (!parse_host(str, i, &url->host)) return false; if (*i < len && src[*i] == '/') { /* absolute path */ // The parsing of the path can't fail // because we already know there's at // leat a '/' for it. (void) parse_path(str, i, &url->path); } else url->path = NULLSTR; } else { url->host.mode = URL_HOSTMODE_NAME; url->host.name = NULLSTR; url->host.no_port = true; url->host.port = 0; url->userinfo.username = NULLSTR; url->userinfo.password = NULLSTR; // TODO: Since there was no authority, // the path is non optional. if (*i == len || src[*i] == '?' || src[*i] == '#') return false; // Missing path if (!parse_path(str, i, &url->path)) return false; } parse_query(str, i, &url->query); parse_fragment(str, i, &url->fragment); return true; } static bool url_parse(string str, url_t *url) { size_t i = 0; bool result = url_parse2(str, &i, url); return result && i == str.size; } static bool url_parse_ipv4(string str, uint32_t *out) { size_t i = 0; return parse_ipv4(str, &i, out); } static bool url_parse_ipv6(string str, uint16_t out[8]) { size_t i = 0; return parse_ipv6(str, &i, out); } /////////////////////////////////////////////////////////////////////////////////////////////// /// URI PARSER /// /////////////////////////////////////////////////////////////////////////////////////////////// static bool hex_to_num(char x, int *n) { // TODO: This is inefficient if (x >= 'A' && x <= 'F') *n = x - 'A'; else if (x >= 'a' && x <= 'f') *n = x - 'a'; else if (x >= '0' && x <= '9') *n = x - '0'; else return false; return true; } static size_t skip_any_valid_chars_not_percent_encoded(string src, size_t cur) { while (cur < src.size && src.data[cur] != '=' // Start of the value && src.data[cur] != '&' // End of the parameter (no value) && src.data[cur] != '%' // The name is percent encoded && src.data[cur] != '+' // Also percent encoded && is_print(src.data[cur])) // Query stirng contains invalid chracters cur++; return cur; } // This function is like the "parse" version but doesn't // return the decoded string. static bool skip_percent_encoded_substr(string src, size_t *cur_) { size_t cur = *cur_; cur = skip_any_valid_chars_not_percent_encoded(src, cur); while (cur < src.size && (src.data[cur] == '+' || src.data[cur] == '%')) { if (src.data[cur] == '+') cur++; else { int u, v; if (cur+2 >= src.size || !hex_to_num(src.data[cur+1], &u) || !hex_to_num(src.data[cur+2], &v)) return false; // Invalid percent encoding cur += 3; } cur = skip_any_valid_chars_not_percent_encoded(src, cur); } *cur_ = cur; return true; } // Percent decodes the source string until a query string // separator ('=', '&') or an invalid character. The resulting // slice (out) may point into the source string or the // destination buffer (dst). It only returns false if copying // was required and the buffer was too small. static bool parse_percent_encoded_substr(string src, string dst, size_t *cur_, string *out) { size_t cur = *cur_; size_t start = cur; cur = skip_any_valid_chars_not_percent_encoded(src, cur); // Use secondary pointer so the key to avoid pointing // into the source buffer. If we found something to // decode (eg a %xx token) we'll need to copy the key // into the destination buffer and make this pointer // refer to that. *out = substr(src, start, cur); // If the name is percent encoded, copying into // a seprate buffer to decode it is necessary if (cur < src.size && (src.data[cur] == '%' || src.data[cur] == '+')) { // Copy what was already scanned into the // output buffer. if (out->size > dst.size) return -1; // Output buffer is too small memcpy(dst.data, out->data, out->size); size_t copied = out->size; do { assert(cur < src.size); if (src.data[cur] == '+') { if (copied == dst.size) return false; // Output buffer is too small dst.data[copied++] = ' '; cur++; } else { // We know there is a '%' at this point int u, v; if (cur+2 >= src.size || !hex_to_num(src.data[cur+1], &u) || !hex_to_num(src.data[cur+2], &v)) return false; // Invalid percent encoding cur += 3; assert(u >= 0 && u < 16); assert(v >= 0 && v < 16); uint8_t byte = (u << 4) | v; if (copied == dst.size) return false; // Output buffer is too small dst.data[copied++] = (char) byte; } // Skip all plain characters and memcopy them at once start = cur; cur = skip_any_valid_chars_not_percent_encoded(src, cur); if (copied + cur - start > dst.size) return false; // Buffer is too small memcpy(dst.data + copied, src.data + start, cur - start); copied += cur - start; } while (cur < src.size && (src.data[cur] == '%' || src.data[cur] == '+')); out->data = dst.data; out->size = copied; } *cur_ = cur; return true; } bool get_query_string_param(string str, string key, string dst, string *out) { assert(key.size); size_t cur = 0; if (cur < str.size && str.data[cur] == '?') cur++; while (cur < str.size) { string key2; if (!parse_percent_encoded_substr(str, dst, &cur, &key2)) return false; // Buffer too small if (cur < str.size && !is_print(str.data[cur])) return false; // Invalid query string if (key2.size == key.size && !memcmp(key2.data, key.data, key2.size)) { if (cur < str.size && str.data[cur] == '=') { cur++; // = if (!parse_percent_encoded_substr(str, dst, &cur, out)) return false; } else { out->data = ""; out->size = 0; } return true; } else { if (cur < str.size && str.data[cur] == '=') { cur++; // = if (!skip_percent_encoded_substr(str, &cur)) return false; } } if (cur < str.size && str.data[cur] != '&') return false; cur++; // & } return false; } /////////////////////////////////////////////////////////////////////////////////////////////// /// COOKIE PARSER /// /////////////////////////////////////////////////////////////////////////////////////////////// static bool is_cookie_name(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'; } static bool is_cookie_value(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'; } bool get_cookie(Request *request, string name, string *out) { string value; if (!find_header(request, LIT("Cookie"), &value)) return false; char *src = value.data; size_t len = value.size; size_t cur = 0; while (cur < len) { size_t start; while (cur < len && is_space(src[cur])) cur++; if (cur == len) break; if (!is_cookie_name(src[cur])) return false; start = cur; do cur++; while (cur < len && is_cookie_name(src[cur])); string cookie_name = substr(value, start, cur); while (cur < len && is_space(src[cur])) cur++; if (cur == len || src[cur] != '=') return false; cur++; while (cur < len && is_space(src[cur])) cur++; if (!is_cookie_value(src[cur])) return false; start = cur; do cur++; while (cur < len && is_cookie_value(src[cur])); string cookie_value = substr(value, start, cur); while (cur < len && is_space(src[cur])) cur++; if (cur < len) { if (src[cur] != ';') return false; cur++; } if (streq(cookie_name, name)) { *out = cookie_value; return true; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////// /// CERTIFICATE AND PRIVATE KEY PARSING (Adapted from BearSSL) /// /////////////////////////////////////////////////////////////////////////////////////////////// /// Copyright (c) 2016 Thomas Pornin /// /// /// /// Permission is hereby granted, free of charge, to any person obtaining /// /// a copy of this software and associated documentation files (the /// /// "Software"), to deal in the Software without restriction, including /// /// without limitation the rights to use, copy, modify, merge, publish, /// /// distribute, sublicense, and/or sell copies of the Software, and to /// /// permit persons to whom the Software is furnished to do so, subject to /// /// the following conditions: /// /// /// /// The above copyright notice and this permission notice shall be /// /// included in all copies or substantial portions of the Software. /// /// /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, /// /// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF /// /// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND /// /// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS /// /// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN /// /// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE /// /// SOFTWARE. /// /////////////////////////////////////////////////////////////////////////////////////////////// #if HTTPS BearSSLErrorInfo bearssl_error_table[] = { { BR_ERR_BAD_PARAM, LIT("BR_ERR_BAD_PARAM"), LIT("Caller-provided parameter is incorrect.") }, { BR_ERR_BAD_STATE, LIT("BR_ERR_BAD_STATE"), LIT("Operation requested by the caller cannot be applied with the current context state (e.g. reading data while outgoing data is waiting to be sent).") }, { BR_ERR_UNSUPPORTED_VERSION, LIT("BR_ERR_UNSUPPORTED_VERSION"), LIT("Incoming protocol or record version is unsupported.") }, { BR_ERR_BAD_VERSION, LIT("BR_ERR_BAD_VERSION"), LIT("Incoming record version does not match the expected version.") }, { BR_ERR_BAD_LENGTH, LIT("BR_ERR_BAD_LENGTH"), LIT("Incoming record length is invalid.") }, { BR_ERR_TOO_LARGE, LIT("BR_ERR_TOO_LARGE"), LIT("Incoming record is too large to be processed, or buffer is too small for the handshake message to send.") }, { BR_ERR_BAD_MAC, LIT("BR_ERR_BAD_MAC"), LIT("Decryption found an invalid padding, or the record MAC is not correct.") }, { BR_ERR_NO_RANDOM, LIT("BR_ERR_NO_RANDOM"), LIT("No initial entropy was provided, and none can be obtained from the OS.") }, { BR_ERR_UNKNOWN_TYPE, LIT("BR_ERR_UNKNOWN_TYPE"), LIT("Incoming record type is unknown.") }, { BR_ERR_UNEXPECTED, LIT("BR_ERR_UNEXPECTED"), LIT("Incoming record or message has wrong type with regards to the current engine state.") }, { BR_ERR_BAD_CCS, LIT("BR_ERR_BAD_CCS"), LIT("ChangeCipherSpec message from the peer has invalid contents.") }, { BR_ERR_BAD_ALERT, LIT("BR_ERR_BAD_ALERT"), LIT("Alert message from the peer has invalid contents (odd length).") }, { BR_ERR_BAD_HANDSHAKE, LIT("BR_ERR_BAD_HANDSHAKE"), LIT("Incoming handshake message decoding failed.") }, { BR_ERR_OVERSIZED_ID, LIT("BR_ERR_OVERSIZED_ID"), LIT("ServerHello contains a session ID which is larger than 32 bytes.") }, { BR_ERR_BAD_CIPHER_SUITE, LIT("BR_ERR_BAD_CIPHER_SUITE"), LIT("Server wants to use a cipher suite that we did not claim to support. This is also reported if we tried to advertise a cipher suite that we do not support.") }, { BR_ERR_BAD_COMPRESSION, LIT("BR_ERR_BAD_COMPRESSION"), LIT("Server wants to use a compression that we did not claim to support.") }, { BR_ERR_BAD_FRAGLEN, LIT("BR_ERR_BAD_FRAGLEN"), LIT("Server's max fragment length does not match client's.") }, { BR_ERR_BAD_SECRENEG, LIT("BR_ERR_BAD_SECRENEG"), LIT("Secure renegotiation failed.") }, { BR_ERR_EXTRA_EXTENSION, LIT("BR_ERR_EXTRA_EXTENSION"), LIT("Server sent an extension type that we did not announce, or used the same extension type several times in a single ServerHello.") }, { BR_ERR_BAD_SNI, LIT("BR_ERR_BAD_SNI"), LIT("Invalid Server Name Indication contents (when used by the server, this extension shall be empty).") }, { BR_ERR_BAD_HELLO_DONE, LIT("BR_ERR_BAD_HELLO_DONE"), LIT("Invalid ServerHelloDone from the server (length is not 0).") }, { BR_ERR_LIMIT_EXCEEDED, LIT("BR_ERR_LIMIT_EXCEEDED"), LIT("Internal limit exceeded (e.g. server's public key is too large).") }, { BR_ERR_BAD_FINISHED, LIT("BR_ERR_BAD_FINISHED"), LIT("Finished message from peer does not match the expected value.") }, { BR_ERR_RESUME_MISMATCH, LIT("BR_ERR_RESUME_MISMATCH"), LIT("Session resumption attempt with distinct version or cipher suite.") }, { BR_ERR_INVALID_ALGORITHM, LIT("BR_ERR_INVALID_ALGORITHM"), LIT("Unsupported or invalid algorithm (ECDHE curve, signature algorithm, hash function).") }, { BR_ERR_BAD_SIGNATURE, LIT("BR_ERR_BAD_SIGNATURE"), LIT("Invalid signature in ServerKeyExchange or CertificateVerify message.") }, { BR_ERR_WRONG_KEY_USAGE, LIT("BR_ERR_WRONG_KEY_USAGE"), LIT("Peer's public key does not have the proper type or is not allowed for the requested operation.") }, { BR_ERR_NO_CLIENT_AUTH, LIT("BR_ERR_NO_CLIENT_AUTH"), LIT("Client did not send a certificate upon request, or the client certificate could not be validated.") }, { BR_ERR_IO, LIT("BR_ERR_IO"), LIT("I/O error or premature close on transport stream.") }, { BR_ERR_X509_INVALID_VALUE, LIT("BR_ERR_X509_INVALID_VALUE"), LIT("Invalid value in an ASN.1 structure.") }, { BR_ERR_X509_TRUNCATED, LIT("BR_ERR_X509_TRUNCATED"), LIT("Truncated certificate or other ASN.1 object.") }, { BR_ERR_X509_EMPTY_CHAIN, LIT("BR_ERR_X509_EMPTY_CHAIN"), LIT("Empty certificate chain (no certificate at all).") }, { BR_ERR_X509_INNER_TRUNC, LIT("BR_ERR_X509_INNER_TRUNC"), LIT("Decoding error: inner element extends beyond outer element size.") }, { BR_ERR_X509_BAD_TAG_CLASS, LIT("BR_ERR_X509_BAD_TAG_CLASS"), LIT("Decoding error: unsupported tag class (application or private).") }, { BR_ERR_X509_BAD_TAG_VALUE, LIT("BR_ERR_X509_BAD_TAG_VALUE"), LIT("Decoding error: unsupported tag value.") }, { BR_ERR_X509_INDEFINITE_LENGTH, LIT("BR_ERR_X509_INDEFINITE_LENGTH"), LIT("Decoding error: indefinite length.") }, { BR_ERR_X509_EXTRA_ELEMENT, LIT("BR_ERR_X509_EXTRA_ELEMENT"), LIT("Decoding error: extraneous element.") }, { BR_ERR_X509_UNEXPECTED, LIT("BR_ERR_X509_UNEXPECTED"), LIT("Decoding error: unexpected element.") }, { BR_ERR_X509_NOT_CONSTRUCTED, LIT("BR_ERR_X509_NOT_CONSTRUCTED"), LIT("Decoding error: expected constructed element, but is primitive.") }, { BR_ERR_X509_NOT_PRIMITIVE, LIT("BR_ERR_X509_NOT_PRIMITIVE"), LIT("Decoding error: expected primitive element, but is constructed.") }, { BR_ERR_X509_PARTIAL_BYTE, LIT("BR_ERR_X509_PARTIAL_BYTE"), LIT("Decoding error: BIT STRING length is not multiple of 8.") }, { BR_ERR_X509_BAD_BOOLEAN, LIT("BR_ERR_X509_BAD_BOOLEAN"), LIT("Decoding error: BOOLEAN value has invalid length.") }, { BR_ERR_X509_OVERFLOW, LIT("BR_ERR_X509_OVERFLOW"), LIT("Decoding error: value is off-limits.") }, { BR_ERR_X509_BAD_DN, LIT("BR_ERR_X509_BAD_DN"), LIT("Invalid distinguished name.") }, { BR_ERR_X509_BAD_TIME, LIT("BR_ERR_X509_BAD_TIME"), LIT("Invalid date/time representation.") }, { BR_ERR_X509_UNSUPPORTED, LIT("BR_ERR_X509_UNSUPPORTED"), LIT("Certificate contains unsupported features that cannot be ignored.") }, { BR_ERR_X509_LIMIT_EXCEEDED, LIT("BR_ERR_X509_LIMIT_EXCEEDED"), LIT("Key or signature size exceeds internal limits.") }, { BR_ERR_X509_WRONG_KEY_TYPE, LIT("BR_ERR_X509_WRONG_KEY_TYPE"), LIT("Key type does not match that which was expected.") }, { BR_ERR_X509_BAD_SIGNATURE, LIT("BR_ERR_X509_BAD_SIGNATURE"), LIT("Signature is invalid.") }, { BR_ERR_X509_TIME_UNKNOWN, LIT("BR_ERR_X509_TIME_UNKNOWN"), LIT("Validation time is unknown.") }, { BR_ERR_X509_EXPIRED, LIT("BR_ERR_X509_EXPIRED"), LIT("Certificate is expired or not yet valid.") }, { BR_ERR_X509_DN_MISMATCH, LIT("BR_ERR_X509_DN_MISMATCH"), LIT("Issuer/Subject DN mismatch in the chain.") }, { BR_ERR_X509_BAD_SERVER_NAME, LIT("BR_ERR_X509_BAD_SERVER_NAME"), LIT("Expected server name was not found in the chain.") }, { BR_ERR_X509_CRITICAL_EXTENSION, LIT("BR_ERR_X509_CRITICAL_EXTENSION"), LIT("Unknown critical extension in certificate.") }, { BR_ERR_X509_NOT_CA, LIT("BR_ERR_X509_NOT_CA"), LIT("Not a CA, or path length constraint violation.") }, { BR_ERR_X509_FORBIDDEN_KEY_USAGE, LIT("BR_ERR_X509_FORBIDDEN_KEY_USAGE"), LIT("Key Usage extension prohibits intended usage.") }, { BR_ERR_X509_WEAK_PUBLIC_KEY, LIT("BR_ERR_X509_WEAK_PUBLIC_KEY"), LIT("Public key found in certificate is too small.") }, { BR_ERR_X509_NOT_TRUSTED, LIT("BR_ERR_X509_NOT_TRUSTED"), LIT("Chain could not be linked to a trust anchor.") }, }; static BearSSLErrorInfo get_bearssl_error_info(int code) { for (int i = 0; i < COUNTOF(bearssl_error_table); i++) if (bearssl_error_table[i].code == code) return bearssl_error_table[i]; BearSSLErrorInfo fallback; fallback.code = code; fallback.name = LIT("Unknown"); fallback.comment = LIT(":/"); return fallback; } typedef struct { string name; string content; } PemObject; typedef struct { bool failed; char *buffer; size_t buffer_count; size_t buffer_capacity; } PemDecodeContext; static int is_ign(int c) { if (c == 0) { return 0; } if (c <= 32 || c == '-' || c == '_' || c == '.' || c == '/' || c == '+' || c == ':') { return 1; } return 0; } // Get next non-ignored character, normalised: // ASCII letters are converted to lowercase // control characters, space, '-', '_', '.', '/', '+' and ':' are ignored // A terminating zero is returned as 0. static char next_char(string *s) { size_t i = 0; while (i < s->size && is_ign(s->data[i])) i++; char c; if (i == s->size) c = '\0'; else { c = s->data[i++]; assert(c != '\0'); } s->data += i; s->size -= i; return to_lower(c); } static bool eqstr__(string a, string b) { for (;;) { char c1 = next_char(&a); char c2 = next_char(&b); if (c1 != c2) return false; if (c1 == 0) return true; } } static void append_bytes(void *userptr, const void *str, size_t len) { PemDecodeContext *context = userptr; if (context->failed) return; if (context->buffer_capacity - context->buffer_count < len) { size_t newcap = MAX(2 * context->buffer_capacity, context->buffer_count + len); void *newstr = mymalloc(newcap); if (newstr == NULL) { context->failed = true; return; } if (context->buffer) { memcpy(newstr, context->buffer, context->buffer_count); myfree(context->buffer, context->buffer_capacity); } context->buffer = newstr; context->buffer_capacity = newcap; } memcpy(context->buffer + context->buffer_count, str, len); context->buffer_count += len; } typedef struct { PemObject *items; int count; int capacity; } PemArray; static bool append_pem(PemArray *arr, PemObject obj) { if (arr->count == arr->capacity) { int newcap = MAX(2 * arr->capacity, 4); PemObject *newitems = mymalloc(newcap * sizeof(PemObject)); if (newitems == NULL) return false; if (arr->count) memcpy(arr->items, newitems, arr->count * sizeof(PemObject)); myfree(arr->items, arr->capacity * sizeof(PemObject)); arr->items = newitems; arr->capacity = newcap; } arr->items[arr->count++] = obj; return true; } static void free_pem_array(PemArray *arr) { for (int i = 0; i < arr->count; i++) { string name = arr->items[i].name; myfree(name.data, name.size); string content = arr->items[i].content; if (content.data) myfree(content.data, content.size); } myfree(arr->items, arr->capacity * sizeof(PemObject)); } static bool decode_pem(string src, PemArray *array) { br_pem_decoder_context context; br_pem_decoder_init(&context); PemDecodeContext context2; context2.failed = false; context2.buffer = NULL; context2.buffer_count = 0; context2.buffer_capacity = 0; array->items = NULL; array->count = 0; array->capacity = 0; PemObject po; po.name = NULLSTR; po.content = NULLSTR; bool inside_object = false; bool extra_newline = true; while (src.size > 0) { size_t n = br_pem_decoder_push(&context, src.data, src.size); src.data += n; src.size -= n; switch (br_pem_decoder_event(&context)) { case BR_PEM_BEGIN_OBJ: { const char *name = br_pem_decoder_name(&context); size_t name_len = strlen(name); po.name.data = mymalloc(name_len); po.name.size = name_len; if (po.name.data == NULL) { myfree(context2.buffer, context2.buffer_capacity); free_pem_array(array); return false; } memcpy(po.name.data, name, name_len); br_pem_decoder_setdest(&context, append_bytes, &context2); inside_object = true; } break; case BR_PEM_END_OBJ: if (inside_object) { void *copy = mymalloc(context2.buffer_count); if (copy == NULL) { myfree(po.name.data, po.name.size); myfree(context2.buffer, context2.buffer_capacity); free_pem_array(array); return false; } memcpy(copy, context2.buffer, context2.buffer_count); po.content.data = copy; po.content.size = context2.buffer_count; if (!append_pem(array, po)) { myfree(po.name.data, po.name.size); myfree(context2.buffer, context2.buffer_capacity); free_pem_array(array); return false; } po.name = NULLSTR; po.content = NULLSTR; context2.buffer_count = 0; inside_object = false; } break; case BR_PEM_ERROR: myfree(po.name.data, po.name.size); myfree(context2.buffer, context2.buffer_capacity); free_pem_array(array); log_data(LIT("Invalid PEM")); return false; } if (src.size == 0 && extra_newline) { src.data = "\n"; src.size = 1; extra_newline = false; } } if (context2.buffer) myfree(context2.buffer, context2.buffer_capacity); if (context2.failed) { myfree(po.name.data, po.name.size); free_pem_array(array); return false; } if (inside_object) { myfree(po.name.data, po.name.size); free_pem_array(array); log_data(LIT("Unfinished PEM")); return false; } return true; } static int looks_like_DER(string content) { int fb; size_t dlen; if (content.size < 2) { return 0; } if (*content.data ++ != 0x30) { return 0; } fb = *content.data ++; content.size -= 2; if (fb < 0x80) { return (size_t)fb == content.size; } else if (fb == 0x80) { return 0; } else { fb -= 0x80; if (content.size < (size_t)fb + 2) { return 0; } content.size -= (size_t)fb; dlen = 0; while (fb -- > 0) { if (dlen > (content.size >> 8)) { return 0; } dlen = (dlen << 8) + (size_t)*content.data ++; } return dlen == content.size; } } static bool decode_key(string src, PrivateKey *pkey) { br_skey_decoder_context context; br_skey_decoder_init(&context); br_skey_decoder_push(&context, src.data, src.size); int err = br_skey_decoder_last_error(&context); if (err) { BearSSLErrorInfo error_info = get_bearssl_error_info(err); log_format("Error decoding key: %.*s: (code=%d, %.*s)\n", (int) error_info.name.size, error_info.name.data, err, (int) error_info.comment.size, error_info.comment.data); return false; } switch (br_skey_decoder_key_type(&context)) { const br_rsa_private_key *rsa_key; const br_ec_private_key *ec_key; case BR_KEYTYPE_RSA: { rsa_key = br_skey_decoder_get_rsa(&context); unsigned char *mem = mymalloc(rsa_key->plen + rsa_key->qlen + rsa_key->dplen + rsa_key->dqlen + rsa_key->iqlen); if (mem == NULL) return false; pkey->type = BR_KEYTYPE_RSA; pkey->rsa.n_bitlen = rsa_key->n_bitlen; pkey->rsa.p = mem; pkey->rsa.q = mem + rsa_key->plen; pkey->rsa.dp = mem + rsa_key->plen + rsa_key->qlen; pkey->rsa.dq = mem + rsa_key->plen + rsa_key->qlen + rsa_key->dplen; pkey->rsa.iq = mem + rsa_key->plen + rsa_key->qlen + rsa_key->dplen + rsa_key->dqlen; memcpy(pkey->rsa.p, rsa_key->p, rsa_key->plen); memcpy(pkey->rsa.q, rsa_key->q, rsa_key->qlen); memcpy(pkey->rsa.dp, rsa_key->dp, rsa_key->dplen); memcpy(pkey->rsa.dq, rsa_key->dq, rsa_key->dqlen); memcpy(pkey->rsa.iq, rsa_key->iq, rsa_key->iqlen); pkey->rsa.plen = rsa_key->plen; pkey->rsa.qlen = rsa_key->qlen; pkey->rsa.dplen = rsa_key->dplen; pkey->rsa.dqlen = rsa_key->dqlen; pkey->rsa.iqlen = rsa_key->iqlen; } break; case BR_KEYTYPE_EC: { ec_key = br_skey_decoder_get_ec(&context); pkey->type = BR_KEYTYPE_EC; pkey->ec.curve = ec_key->curve; pkey->ec.x = mymalloc(ec_key->xlen); if (pkey->ec.x == NULL) return false; memcpy(pkey->ec.x, ec_key->x, ec_key->xlen); pkey->ec.xlen = ec_key->xlen; } break; default: log_format("Unknown key type: %d\n", br_skey_decoder_key_type(&context)); return false; } return true; } static bool load_private_key_from_file(string file, PrivateKey *pkey) { string file_contents; if (!load_file_contents(file, &file_contents)) return false; DEBUG("loading key: file contents loaded\n"); bool ok; if (looks_like_DER(file_contents)) { DEBUG("loading key: detected DER file\n"); ok = decode_key(file_contents, pkey); } else { DEBUG("loading key: detected PEM file\n"); PemArray pem_array; if (!decode_pem(file_contents, &pem_array)) { myfree(file_contents.data, file_contents.size); return false; } bool found = false; bool decoded = false; for (int i = 0; i < pem_array.count; i++) if (eqstr__(pem_array.items[i].name, LIT("RSA PRIVATE KEY")) || eqstr__(pem_array.items[i].name, LIT("EC PRIVATE KEY")) || eqstr__(pem_array.items[i].name, LIT("PRIVATE KEY"))) { DEBUG("loading key: found key in PEM file\n"); if (decode_key(pem_array.items[i].content, pkey)) decoded = true; found = true; break; } ok = false; if (!found) log_data(LIT("Missing private key in file\n")); else { if (!decoded) log_data(LIT("Couldn't decode key\n")); else ok = true; } free_pem_array(&pem_array); } myfree(file_contents.data, file_contents.size); return ok; } static void free_private_key(PrivateKey *pkey) { switch (pkey->type) { case BR_KEYTYPE_RSA: myfree(pkey->rsa.p, pkey->rsa.plen + pkey->rsa.qlen + pkey->rsa.dplen + pkey->rsa.dqlen + pkey->rsa.iqlen); break; case BR_KEYTYPE_EC: myfree(pkey->ec.x, pkey->ec.xlen); break; } } static bool append_cert(CertArray *arr, br_x509_certificate cert) { if (arr->count == arr->capacity) { int newcap = MAX(2 * arr->capacity, 4); br_x509_certificate *newitems = mymalloc(newcap * sizeof(br_x509_certificate)); if (newitems == NULL) return false; if (arr->count) memcpy(arr->items, newitems, arr->count * sizeof(br_x509_certificate)); myfree(arr->items, arr->capacity * sizeof(br_x509_certificate)); arr->items = newitems; arr->capacity = newcap; } arr->items[arr->count++] = cert; return true; } static bool load_certs_from_file(string file, CertArray *array) { string file_contents; if (!load_file_contents(file, &file_contents)) return false; DEBUG("loading certs: file contents loaded\n"); array->items = NULL; array->count = 0; array->capacity = 0; if (looks_like_DER(file_contents)) { DEBUG("loading certs: detected DER file\n"); br_x509_certificate xc = { (unsigned char*) file_contents.data, file_contents.size, }; if (!append_cert(array, xc)) { myfree(file_contents.data, file_contents.size); return false; } DEBUG("loading certs: DER file parsed\n"); } else { DEBUG("loading certs: detected PEM file\n"); PemArray pem_array; if (!decode_pem(file_contents, &pem_array)) { myfree(file_contents.data, file_contents.size); return false; } DEBUG("loading certs: PEM file parsed (%d entries)\n", pem_array.count); for (int i = 0; i < pem_array.count; i++) { PemObject po = pem_array.items[i]; if (eqstr__(po.name, LIT("CERTIFICATE")) || eqstr__(po.name, LIT("X509 CERTIFICATE"))) { DEBUG("loading certs: found certificate in PEM file\n"); br_x509_certificate xc = { (unsigned char*) po.content.data, po.content.size }; if (!append_cert(array, xc)) { free_pem_array(&pem_array); free_certs(array); myfree(file_contents.data, file_contents.size); return false; } pem_array.items[i].content = NULLSTR; } else { DEBUG("loading certs: ignoring entry [%.*s] in PEM file\n", (int) po.name.size, po.name.data); } } DEBUG("loading certs: finished loading certificates from PEM file\n"); if (array->count == 0) { free_pem_array(&pem_array); free_certs(array); myfree(file_contents.data, file_contents.size); log_data(LIT("No certificates in file\n")); return false; } free_pem_array(&pem_array); myfree(file_contents.data, file_contents.size); } DEBUG("loading certs: certificate loaded\n"); return true; } static void free_certs(CertArray *array) { for (int i = 0; i < array->count; i++) { br_x509_certificate item = array->items[i]; myfree(item.data, item.data_len); } myfree(array->items, array->capacity * sizeof(br_x509_certificate)); } #endif /* HTTPS */