From 95946b16669a34c9e5d573aa74e913cbf2555c7e Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Thu, 7 Jan 2021 21:58:45 +0100 Subject: [PATCH] Reverse proxy working --- src/client.c | 340 +++++++----------------------------------- src/config.c | 16 ++ src/config.h | 3 +- src/fastcgi.c | 37 ++--- src/http.c | 49 +++--- src/http.h | 2 + src/necronda-server.c | 44 ++---- src/necronda-server.h | 13 +- src/rev_proxy.c | 251 +++++++++++++++++++++++++++++++ src/rev_proxy.h | 11 ++ src/sock.c | 104 +++++++++++++ src/sock.h | 32 ++++ 12 files changed, 515 insertions(+), 387 deletions(-) create mode 100644 src/rev_proxy.c create mode 100644 src/rev_proxy.h create mode 100644 src/sock.c create mode 100644 src/sock.h diff --git a/src/client.c b/src/client.c index aa84c3c..14e84eb 100644 --- a/src/client.c +++ b/src/client.c @@ -18,10 +18,6 @@ char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ *client_host_str, *client_geoip; struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0}; -struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0}; - -int rev_proxy_sock = 0; -char *rev_proxy_host = NULL; host_config *get_host_config(const char *host) { for (int i = 0; i < MAX_HOST_CONFIG; i++) { @@ -43,7 +39,8 @@ int client_websocket_handler() { int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) { struct timespec begin, end; - int ret, client_keep_alive; + long ret; + int client_keep_alive; char buf0[1024], buf1[1024]; char msg_buf[4096], msg_pre_buf[4096], err_msg[256]; char buffer[CHUNK_SIZE]; @@ -106,7 +103,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int } hdr_connection = http_get_header_field(&req.hdr, "Connection"); - client_keep_alive = hdr_connection != NULL && strcmp(hdr_connection, "keep-alive") == 0; + client_keep_alive = hdr_connection != NULL && + (strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0); host_ptr = http_get_header_field(&req.hdr, "Host"); if (host_ptr != NULL && strlen(host_ptr) > 255) { host[0] = 0; @@ -140,7 +138,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int } http_uri uri; - ret = uri_init(&uri, conf->local.webroot, req.uri, conf->local.dir_mode); + unsigned char dir_mode = conf->type == CONFIG_TYPE_LOCAL ? conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION; + ret = uri_init(&uri, conf->local.webroot, req.uri, dir_mode); if (ret != 0) { if (ret == 1) { sprintf(err_msg, "Invalid URI: has to start with slash."); @@ -151,20 +150,22 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int goto respond; } - ssize_t size = sizeof(buf0); - url_decode(req.uri, buf0, &size); - int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc; - if (strcmp(uri.uri, buf0) != 0 || change_proto) { - res.status = http_get_status(308); - size = sizeof(buf0); - encode_url(uri.uri, buf0, &size); - if (change_proto) { - sprintf(buf1, "https://%s%s", host, buf0); - http_add_header_field(&res.hdr, "Location", buf1); - } else { - http_add_header_field(&res.hdr, "Location", buf0); + if (dir_mode != URI_DIR_MODE_NO_VALIDATION) { + ssize_t size = sizeof(buf0); + url_decode(req.uri, buf0, &size); + int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc; + if (strcmp(uri.uri, buf0) != 0 || change_proto) { + res.status = http_get_status(308); + size = sizeof(buf0); + encode_url(uri.uri, buf0, &size); + if (change_proto) { + sprintf(buf1, "https://%s%s", host, buf0); + http_add_header_field(&res.hdr, "Location", buf1); + } else { + http_add_header_field(&res.hdr, "Location", buf0); + } + goto respond; } - goto respond; } if (conf->type == CONFIG_TYPE_LOCAL) { @@ -296,7 +297,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int if (strcmp(req.method, "POST") == 0 || strcmp(req.method, "PUT") == 0) { char *client_content_length = http_get_header_field(&req.hdr, "Content-Length"); - unsigned long client_content_len = 0; + unsigned long client_content_len; if (client_content_length == NULL) { goto fastcgi_end; } @@ -352,165 +353,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int } } else if (conf->type != CONFIG_TYPE_LOCAL) { print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port); - int tries = 0; - int off = 0; - - if (rev_proxy_sock != 0 && rev_proxy_host == conf->name) { - goto rev_proxy; - } else if (rev_proxy_sock != 0) { - shutdown(rev_proxy_sock, SHUT_RDWR); - close(rev_proxy_sock); - rev_proxy_sock = 0; - } - - rev_proxy_sock = socket(AF_INET6, SOCK_STREAM, 0); - if (rev_proxy_sock < 0) { - print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno)); - res.status = http_get_status(500); - goto respond; - } - - server_timeout.tv_sec = SERVER_TIMEOUT; - server_timeout.tv_usec = 0; - if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0) - goto rev_proxy_timeout_err; - if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) { - rev_proxy_timeout_err: - res.status = http_get_status(502); - print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno)); - sprintf(err_msg, "Unable to set timeout for socket: %s", strerror(errno)); - goto proxy_err; - } - - struct hostent *host_ent = gethostbyname(conf->rev_proxy.hostname); - if (host_ent == NULL) { - res.status = http_get_status(502); - print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR); - sprintf(err_msg, "Unable to connect to server: Name or service not known."); - goto proxy_err; - } - - struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)}; - if (host_ent->h_addrtype == AF_INET6) { - memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length); - } else if (host_ent->h_addrtype == AF_INET) { - unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0}; - memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length); - memcpy(&address.sin6_addr, addr, 16); - } - - if (connect(rev_proxy_sock, (struct sockaddr *) &address, sizeof(address)) < 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to connect to server: %s" CLR_STR, strerror(errno)); - sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno)); - goto proxy_err; - } - rev_proxy_host = conf->name; - inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buf0, sizeof(buf0)); - print("Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buf0, conf->rev_proxy.port); - - rev_proxy: - off += sprintf(buffer + off, "%s %s HTTP/%s\r\n", req.method, req.uri, req.version); - for (int i = 0; i < req.hdr.field_num; i++) { - off += sprintf(buffer + off, "%s: %s\r\n", req.hdr.fields[i][0], req.hdr.fields[i][1]); - } - off += sprintf(buffer + off, "\r\n"); - - ret = send(rev_proxy_sock, buffer, off, 0); - if (ret < 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to send request to server: %s" CLR_STR, strerror(errno)); - sprintf(err_msg, "Unable to send request to server: %s.", strerror(errno)); - goto proxy_err; - } else if (ret != off) { - res.status = http_get_status(502); - print(ERR_STR "Unable to send request to server" CLR_STR); - sprintf(err_msg, "Unable to send response to server."); - goto proxy_err; - } - - // TODO send request body - - ret = recv(rev_proxy_sock, buffer, sizeof(buffer), MSG_PEEK); - if (ret < 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to receive response from server: %s" CLR_STR, strerror(errno)); - sprintf(err_msg, "Unable to receive response from server: %s.", strerror(errno)); - goto proxy_err; - } - - char *buf = buffer; - unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4); - - if (header_len <= 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header: End of header not found" CLR_STR); - sprintf(err_msg, "Unable to parser header: End of header not found."); - goto proxy_err; - } - - for (int i = 0; i < header_len; i++) { - if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR); - sprintf(err_msg, "Unable to parse header: Header contains illegal characters."); - goto proxy_err; - } - } - - char *ptr = buf; - while (header_len != (ptr - buf)) { - char *pos0 = strstr(ptr, "\r\n"); - if (pos0 == NULL) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR); - sprintf(err_msg, "Unable to parse header: Invalid header format."); - goto proxy_err; - } - if (ptr == buf) { - if (strncmp(ptr, "HTTP/", 5) != 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR); - sprintf(err_msg, "Unable to parse header: Invalid header format."); - goto proxy_err; - } - int status_code = (int) strtol(ptr + 9, NULL, 10); - res.status = http_get_status(status_code); - if (res.status == NULL && status_code >= 100 && status_code <= 999) { - custom_status.code = status_code; - strcpy(custom_status.type, ""); - strcpy(custom_status.msg, ptr + 13); - res.status = &custom_status; - } else if (res.status == NULL) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR); - sprintf(err_msg, "Unable to parse header: Invalid or unknown status code."); - goto proxy_err; - } - } else { - ret = http_parse_header_field(&res.hdr, ptr, pos0); - if (ret != 0) { - res.status = http_get_status(502); - print(ERR_STR "Unable to parse header" CLR_STR); - sprintf(err_msg, "Unable to parse header."); - goto proxy_err; - } - } - if (pos0[2] == '\r' && pos0[3] == '\n') { - break; - } - ptr = pos0 + 2; - } - recv(rev_proxy_sock, buffer, header_len, 0); - use_rev_proxy = 1; - - if (0) { - proxy_err: - print("Closing proxy connection"); - shutdown(rev_proxy_sock, SHUT_RDWR); - close(rev_proxy_sock); - rev_proxy_sock = 0; - } + ret = rev_proxy_init(&req, &res, conf, client, &custom_status, err_msg); + use_rev_proxy = ret == 0; } else { print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type); res.status = http_get_status(501); @@ -544,7 +388,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int http_add_header_field(&res.hdr, "Server", SERVER_STR); } char *conn = http_get_header_field(&res.hdr, "Connection"); - int close_proxy = conn == NULL || strcmp(conn, "keep-alive") != 0; + int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0); http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL); http_remove_header_field(&res.hdr, "Keep-Alive", HTTP_REMOVE_ALL); if (server_keep_alive && client_keep_alive) { @@ -559,46 +403,30 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int clock_gettime(CLOCK_MONOTONIC, &end); char *location = http_get_header_field(&res.hdr, "Location"); unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000; - print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg, - location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR); + print("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), use_rev_proxy ? "-> " : "", res.status->code, + res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "", + format_duration(micros, buf0), CLR_STR); if (strcmp(req.method, "HEAD") != 0) { unsigned long snd_len = 0; - unsigned long len = 0; + unsigned long len; if (msg_buf[0] != 0) { - while (snd_len < content_length) { - if (client->enc) { - ret = SSL_write(client->ssl, msg_buf, (int) (content_length - snd_len)); - if (ret <= 0) { - print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret)); - } - } else { - ret = send(client->socket, msg_buf, content_length - snd_len, 0); - if (ret <= 0) { - print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno)); - } - } - if (ret <= 0) break; - snd_len += ret; + ret = sock_send(client, msg_buf, content_length, 0); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client)); } + snd_len += ret; } else if (file != NULL) { while (snd_len < content_length) { len = fread(buffer, 1, CHUNK_SIZE, file); if (snd_len + len > content_length) { len = content_length - snd_len; } - if (client->enc) { - ret = SSL_write(client->ssl, buffer, (int) len); - if (ret <= 0) { - print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret)); - } - } else { - ret = send(client->socket, buffer, len, 0); - if (ret <= 0) { - print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno)); - } + sock_send(client, buffer, len, feof(file) ? 0 : MSG_MORE); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client)); + break; } - if (ret <= 0) break; snd_len += ret; } } else if (use_fastcgi) { @@ -616,80 +444,28 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int if (content_len != NULL) { len_to_send = strtol(content_len, NULL, 10); } - - do { - if (chunked) { - ret = recv(rev_proxy_sock, buf0, 16, MSG_PEEK); - if (ret < 0) { - print("Unable to receive: %s", strerror(errno)); - break; - } else if (ret == 0) { - print("Unable to receive: closed"); - break; - } - - len_to_send = strtol(buf0, NULL, 16); - char *pos = strstr(buf0, "\r\n"); - len = pos - buf0 + 2; - if (client->enc) { - ret = SSL_write(client->ssl, buf0, (int) len); - if (ret <= 0) goto snd_err_ssl; - } else { - ret = send(client->socket, buf0, len, 0); - if (ret <= 0) goto snd_err; - } - recv(rev_proxy_sock, buf1, len, 0); - if (ret <= 0) break; - } - snd_len = 0; - while (snd_len < len_to_send) { - len = recv(rev_proxy_sock, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0); - if (client->enc) { - ret = SSL_write(client->ssl, buffer, (int) len); - if (ret <= 0) goto snd_err_ssl; - } else { - ret = send(client->socket, buffer, len, 0); - if (ret <= 0) goto snd_err; - } - if (ret <= 0) break; - snd_len += ret; - } - if (ret <= 0) break; - if (chunked) { - recv(rev_proxy_sock, buf0, 2, 0); - if (client->enc) { - ret = SSL_write(client->ssl, "\r\n", 2); - if (ret <= 0) { - snd_err_ssl: - print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret)); - } - } else { - ret = send(client->socket, "\r\n", 2, 0); - if (ret <= 0) { - snd_err: - print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno)); - } - } - if (ret <= 0) break; - } - } while (chunked && len_to_send > 0); + rev_proxy_send(client, chunked, len_to_send); } } - if (close_proxy && rev_proxy_sock != 0) { - print("Closing proxy connection"); - shutdown(rev_proxy_sock, SHUT_RDWR); - close(rev_proxy_sock); - rev_proxy_sock = 0; + if (close_proxy && rev_proxy.socket != 0) { + print(BLUE_STR "Closing proxy connection" CLR_STR); + sock_close(&rev_proxy); } + fflush(stdout); + clock_gettime(CLOCK_MONOTONIC, &end); micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000; print("Transfer complete: %s", format_duration(micros, buf0)); uri_free(&uri); abort: - if (php_fpm.socket != 0) close(php_fpm.socket); + if (php_fpm.socket != 0) { + shutdown(php_fpm.socket, SHUT_RDWR); + close(php_fpm.socket); + php_fpm.socket = 0; + } http_free_req(&req); http_free_res(&res); if (client->buf != NULL) { @@ -802,8 +578,11 @@ int client_connection_handler(sock *client, unsigned long client_num) { SSL_set_accept_state(client->ssl); ret = SSL_accept(client->ssl); + client->_last_ret = ret; + client->_errno = errno; + client->_ssl_error = ERR_get_error(); if (ret <= 0) { - print(ERR_STR "Unable to perform handshake: %s" CLR_STR, ssl_get_error(client->ssl, ret)); + print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client)); goto close; } } @@ -816,18 +595,11 @@ int client_connection_handler(sock *client, unsigned long client_num) { } close: - if (client->enc) { - SSL_shutdown(client->ssl); - SSL_free(client->ssl); - } - shutdown(client->socket, SHUT_RDWR); - close(client->socket); + sock_close(client); - if (rev_proxy_sock != 0) { - print("Closing proxy connection"); - shutdown(rev_proxy_sock, SHUT_RDWR); - close(rev_proxy_sock); - rev_proxy_sock = 0; + if (rev_proxy.socket != 0) { + print(BLUE_STR "Closing proxy connection" CLR_STR); + sock_close(&rev_proxy); } clock_gettime(CLOCK_MONOTONIC, &end); diff --git a/src/config.c b/src/config.c index 1118b05..fedffee 100644 --- a/src/config.c +++ b/src/config.c @@ -132,6 +132,22 @@ int config_load(const char *filename) { } else { hc->type = CONFIG_TYPE_REVERSE_PROXY; } + } else if (strcmp(ptr, "http") == 0) { + if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) { + goto err; + } else { + hc->type = CONFIG_TYPE_REVERSE_PROXY; + hc->rev_proxy.enc = 0; + } + continue; + } else if (strcmp(ptr, "https") == 0) { + if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) { + goto err; + } else { + hc->type = CONFIG_TYPE_REVERSE_PROXY; + hc->rev_proxy.enc = 1; + } + continue; } } char *end_ptr = source + strlen(source) - 1; diff --git a/src/config.h b/src/config.h index fc7b144..f4ffb6c 100644 --- a/src/config.h +++ b/src/config.h @@ -21,10 +21,11 @@ typedef struct { struct { char hostname[256]; unsigned short port; + unsigned char enc:1; } rev_proxy; struct { char webroot[256]; - unsigned char dir_mode; + unsigned char dir_mode:2; } local; }; } host_config; diff --git a/src/fastcgi.c b/src/fastcgi.c index 702a59c..08e95d5 100644 --- a/src/fastcgi.c +++ b/src/fastcgi.c @@ -452,11 +452,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) { } if (flags & FASTCGI_CHUNKED) { - if (client->enc) { - SSL_write(client->ssl, "0\r\n\r\n", 5); - } else { - send(client->socket, "0\r\n\r\n", 5, 0); - } + sock_send(client, "0\r\n\r\n", 5, 0); } return 0; @@ -480,15 +476,9 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) { } if (buf_len != 0) { len = sprintf(buf0, "%X\r\n", buf_len); - if (client->enc) { - if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, buf0, len); - SSL_write(client->ssl, ptr, buf_len); - if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, "\r\n", 2); - } else { - if (flags & FASTCGI_CHUNKED) send(client->socket, buf0, len, 0); - send(client->socket, ptr, buf_len, 0); - if (flags & FASTCGI_CHUNKED) send(client->socket, "\r\n", 2, 0); - } + if (flags & FASTCGI_CHUNKED) sock_send(client, buf0, len, 0); + sock_send(client, ptr, buf_len, 0); + if (flags & FASTCGI_CHUNKED) sock_send(client, "\r\n", 2, 0); } } while ((flags & FASTCGI_COMPRESS) && strm.avail_out == 0); if (finish_comp) goto finish; @@ -502,7 +492,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) { int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) { unsigned long rcv_len = 0; char *buf[16384]; - int ret; + long ret; FCGI_Header header = { .version = FCGI_VERSION_1, .type = FCGI_STDIN, @@ -521,19 +511,12 @@ int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) { } while (rcv_len < len) { - if (client->enc) { - ret = SSL_read(client->ssl, buf, sizeof(buf)); - if (ret <= 0) { - print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len)); - return -1; - } - } else { - ret = recv(client->socket, buf, sizeof(buf), 0); - if (ret <= 0) { - print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno)); - return -1; - } + ret = sock_recv(client, buf, sizeof(buf), 0); + if (ret <= 0) { + print(ERR_STR "Unable to receive: %s" CLR_STR, sock_strerror(client)); + return -1; } + send: rcv_len += ret; header.contentLengthB1 = (ret >> 8) & 0xFF; diff --git a/src/http.c b/src/http.c index 877a5e7..5e11cd3 100644 --- a/src/http.c +++ b/src/http.c @@ -72,7 +72,7 @@ int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) } int http_receive_request(sock *client, http_req *req) { - unsigned long rcv_len, len; + long rcv_len, len; char *ptr, *pos0, *pos1, *pos2; char buf[CLIENT_MAX_HEADER_SIZE]; memset(buf, 0, sizeof(buf)); @@ -82,22 +82,9 @@ int http_receive_request(sock *client, http_req *req) { req->hdr.field_num = 0; while (1) { - if (client->enc) { - rcv_len = SSL_read(client->ssl, buf, CLIENT_MAX_HEADER_SIZE); - if (rcv_len < 0) { - print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len)); - return -1; - } - } else { - rcv_len = recv(client->socket, buf, CLIENT_MAX_HEADER_SIZE, 0); - if (rcv_len < 0) { - print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno)); - return -1; - } - } - - if (rcv_len == 0) { - print("Unable to receive: closed"); + rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0); + if (rcv_len <= 0) { + print("Unable to receive: %s", sock_strerror(client)); return -1; } @@ -233,21 +220,27 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) { int http_send_response(sock *client, http_res *res) { char buf[CLIENT_MAX_HEADER_SIZE]; - int len = 0; - int snd_len = 0; - - len += sprintf(buf + len, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg); + long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg); for (int i = 0; i < res->hdr.field_num; i++) { - len += sprintf(buf + len, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]); + off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]); } - len += sprintf(buf + len, "\r\n"); - - if (client->enc) { - snd_len = SSL_write(client->ssl, buf, len); - } else { - snd_len = send(client->socket, buf, len, 0); + off += sprintf(buf + off, "\r\n"); + if (sock_send(client, buf, off, 0) < 0) { + return -1; } + return 0; +} +int http_send_request(sock *server, http_req *req) { + char buf[CLIENT_MAX_HEADER_SIZE]; + long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version); + for (int i = 0; i < req->hdr.field_num; i++) { + off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]); + } + off += sprintf(buf + off, "\r\n"); + if (sock_send(server, buf, off, 0) <= 0) { + return -1; + } return 0; } diff --git a/src/http.h b/src/http.h index 401294f..1b37953 100644 --- a/src/http.h +++ b/src/http.h @@ -192,6 +192,8 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode); int http_send_response(sock *client, http_res *res); +int http_send_request(sock *server, http_req *req); + http_status *http_get_status(unsigned short status_code); http_error_msg *http_get_error_msg(unsigned short status_code); diff --git a/src/necronda-server.c b/src/necronda-server.c index 539f07d..491a7c5 100644 --- a/src/necronda-server.c +++ b/src/necronda-server.c @@ -13,7 +13,9 @@ #include "utils.c" #include "uri.c" #include "cache.c" +#include "sock.c" #include "http.c" +#include "rev_proxy.c" #include "client.c" #include "fastcgi.c" @@ -29,39 +31,6 @@ void openssl_init() { OpenSSL_add_all_algorithms(); } -char *ssl_get_error(SSL *ssl, int ret) { - if (ret > 0) { - return NULL; - } - - unsigned long ret2 = ERR_get_error(); - char *err2 = strerror(errno); - char *err1 = (char *) ERR_reason_error_string(ret2); - - switch (SSL_get_error(ssl, ret)) { - case SSL_ERROR_NONE: - return "none"; - case SSL_ERROR_ZERO_RETURN: - return "closed"; - case SSL_ERROR_WANT_READ: - return "want read"; - case SSL_ERROR_WANT_WRITE: - return "want write"; - case SSL_ERROR_WANT_CONNECT: - return "want connect"; - case SSL_ERROR_WANT_ACCEPT: - return "want accept"; - case SSL_ERROR_WANT_X509_LOOKUP: - return "want x509 lookup"; - case SSL_ERROR_SYSCALL: - return ((ret2 == 0) ? ((ret == 0) ? "protocol violation" : err2) : err1); - case SSL_ERROR_SSL: - return err1; - default: - return "unknown error"; - } -} - void destroy() { fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n"); int status = 0; @@ -187,10 +156,10 @@ int main(int argc, const char *argv[]) { {.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)} }; - if (setvbuf(stdout, NULL, _IONBF, 0) != 0) { + /*if (setvbuf(stdout, NULL, _IONBF, 0) != 0) { fprintf(stderr, ERR_STR "Unable to set stdout to unbuffered mode: %s" CLR_STR, strerror(errno)); return 1; - } + }*/ printf("Necronda Web Server\n"); ret = config_init(); @@ -305,6 +274,11 @@ int main(int argc, const char *argv[]) { SSL_CTX_set_cipher_list(client.ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); SSL_CTX_set_ecdh_auto(client.ctx, 1); + rev_proxy.buf = NULL; + rev_proxy.buf_len = 0; + rev_proxy.buf_off = 0; + rev_proxy.ctx = SSL_CTX_new(TLS_client_method()); + if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) { fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n", ERR_reason_error_string(ERR_get_error()), cert_file); diff --git a/src/necronda-server.h b/src/necronda-server.h index d1dc120..9fb6b64 100644 --- a/src/necronda-server.h +++ b/src/necronda-server.h @@ -50,6 +50,7 @@ #define CLR_STR "\x1B[0m" #define BLD_STR "\x1B[1m" #define WRN_STR "\x1B[1;33m" +#define BLUE_STR "\x1B[34m" #define HTTP_STR "\x1B[1;31m" #define HTTPS_STR "\x1B[1;32m" @@ -80,16 +81,4 @@ int sockets[NUM_SOCKETS]; pid_t children[MAX_CHILDREN]; MMDB_s mmdbs[MAX_MMDB]; -typedef struct { - unsigned int enc:1; - int socket; - SSL_CTX *ctx; - SSL *ssl; - char *buf; - unsigned long buf_len; - unsigned long buf_off; -} sock; - -char *ssl_get_error(SSL *ssl, int ret); - #endif //NECRONDA_SERVER_NECRONDA_SERVER_H diff --git a/src/rev_proxy.c b/src/rev_proxy.c new file mode 100644 index 0000000..18e5210 --- /dev/null +++ b/src/rev_proxy.c @@ -0,0 +1,251 @@ +/** + * Necronda Web Server + * Reverse proxy + * src/rev_proxy.c + * Lorenz Stechauner, 2021-01-07 + */ + +#include "rev_proxy.h" + +sock rev_proxy; +char *rev_proxy_host = NULL; +struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0}; + + +int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status, + char * err_msg) { + int tries = 0; + int off = 0; + char buffer[CHUNK_SIZE]; + long ret; + + if (rev_proxy.socket != 0 && rev_proxy_host == conf->name) { + goto rev_proxy; + } else if (rev_proxy.socket != 0) { + sock_close(&rev_proxy); + } + + rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0); + if (rev_proxy.socket < 0) { + print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno)); + res->status = http_get_status(500); + return -1; + } + + server_timeout.tv_sec = SERVER_TIMEOUT; + server_timeout.tv_usec = 0; + if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0) + goto rev_proxy_timeout_err; + if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) { + rev_proxy_timeout_err: + res->status = http_get_status(502); + print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno)); + sprintf(err_msg, "Unable to set timeout for socket: %s", strerror(errno)); + goto proxy_err; + } + + struct hostent *host_ent = gethostbyname(conf->rev_proxy.hostname); + if (host_ent == NULL) { + res->status = http_get_status(502); + print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR); + sprintf(err_msg, "Unable to connect to server: Name or service not known."); + goto proxy_err; + } + + struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)}; + if (host_ent->h_addrtype == AF_INET6) { + memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length); + } else if (host_ent->h_addrtype == AF_INET) { + unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0}; + memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length); + memcpy(&address.sin6_addr, addr, 16); + } + + if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to connect to server: %s" CLR_STR, strerror(errno)); + sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno)); + goto proxy_err; + } + + if (conf->rev_proxy.enc) { + rev_proxy.ssl = SSL_new(rev_proxy.ctx); + SSL_set_fd(rev_proxy.ssl, rev_proxy.socket); + SSL_set_connect_state(rev_proxy.ssl); + + ret = SSL_do_handshake(rev_proxy.ssl); + rev_proxy._last_ret = ret; + rev_proxy._errno = errno; + rev_proxy._ssl_error = ERR_get_error(); + rev_proxy.enc = 1; + if (ret < 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy)); + sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy)); + goto proxy_err; + } + } + + rev_proxy_host = conf->name; + inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer)); + print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port); + + rev_proxy: + http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL); + http_add_header_field(&req->hdr, "Connection", "keep-alive"); + + ret = http_send_request(&rev_proxy, req); + if (ret < 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to send request to server: %s" CLR_STR, sock_strerror(&rev_proxy)); + sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy)); + goto proxy_err; + } + + char *content_length = http_get_header_field(&req->hdr, "Content-Length"); + if (content_length != NULL) { + unsigned long content_len = strtoul(content_length, NULL, 10); + if (client->buf_len - client->buf_off > 0) { + ret = sock_send(&rev_proxy, client->buf, client->buf_len - client->buf_off, 0); + if (ret <= 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to send request to server: %s" CLR_STR, sock_strerror(&rev_proxy)); + sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy)); + goto proxy_err; + } + content_len -= client->buf_len - client->buf_off; + } + if (content_len > 0) { + ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len); + if (ret <= 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to send request to server: %s" CLR_STR, sock_strerror(&rev_proxy)); + sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy)); + goto proxy_err; + } + } + } + + ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK); + if (ret <= 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy)); + sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy)); + goto proxy_err; + } + + char *buf = buffer; + unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4); + + if (header_len <= 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header: End of header not found" CLR_STR); + sprintf(err_msg, "Unable to parser header: End of header not found."); + goto proxy_err; + } + + for (int i = 0; i < header_len; i++) { + if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR); + sprintf(err_msg, "Unable to parse header: Header contains illegal characters."); + goto proxy_err; + } + } + + char *ptr = buf; + while (header_len != (ptr - buf)) { + char *pos0 = strstr(ptr, "\r\n"); + if (pos0 == NULL) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR); + sprintf(err_msg, "Unable to parse header: Invalid header format."); + goto proxy_err; + } + if (ptr == buf) { + if (strncmp(ptr, "HTTP/", 5) != 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR); + sprintf(err_msg, "Unable to parse header: Invalid header format."); + goto proxy_err; + } + int status_code = (int) strtol(ptr + 9, NULL, 10); + res->status = http_get_status(status_code); + if (res->status == NULL && status_code >= 100 && status_code <= 999) { + custom_status->code = status_code; + strcpy(custom_status->type, ""); + strncpy(custom_status->msg, ptr + 13, strchr(ptr, '\r') - ptr - 13); + res->status = custom_status; + } else if (res->status == NULL) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR); + sprintf(err_msg, "Unable to parse header: Invalid or unknown status code."); + goto proxy_err; + } + } else { + ret = http_parse_header_field(&res->hdr, ptr, pos0); + if (ret != 0) { + res->status = http_get_status(502); + print(ERR_STR "Unable to parse header" CLR_STR); + sprintf(err_msg, "Unable to parse header."); + goto proxy_err; + } + } + if (pos0[2] == '\r' && pos0[3] == '\n') { + break; + } + ptr = pos0 + 2; + } + sock_recv(&rev_proxy, buffer, header_len, 0); + + return 0; + + proxy_err: + print(BLUE_STR "Closing proxy connection" CLR_STR); + sock_close(&rev_proxy); + + return -1; +} + +int rev_proxy_send(sock *client, int chunked, unsigned long len_to_send) { + long ret; + char buffer[CHUNK_SIZE]; + long len, snd_len; + do { + if (chunked) { + ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK); + if (ret <= 0) { + print("Unable to receive: %s", sock_strerror(&rev_proxy)); + break; + } + + len_to_send = strtol(buffer, NULL, 16); + char *pos = strstr(buffer, "\r\n"); + len = pos - buffer + 2; + ret = sock_send(client, buffer, len, 0); + + sock_recv(&rev_proxy, buffer, len, 0); + if (ret <= 0) break; + } + snd_len = 0; + while (snd_len < len_to_send) { + len = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0); + ret = sock_send(client, buffer, len, 0); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client)); + break; + } + snd_len += ret; + } + if (ret <= 0) break; + if (chunked) { + sock_recv(&rev_proxy, buffer, 2, 0); + ret = sock_send(client, "\r\n", 2, 0); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client)); + break; + } + } + } while (chunked && len_to_send > 0); + return 0; +} diff --git a/src/rev_proxy.h b/src/rev_proxy.h new file mode 100644 index 0000000..b9b07d4 --- /dev/null +++ b/src/rev_proxy.h @@ -0,0 +1,11 @@ +/** + * Necronda Web Server + * Reverse proxy (header file) + * src/rev_proxy.h + * Lorenz Stechauner, 2021-01-07 + */ + +#ifndef NECRONDA_SERVER_REV_PROXY_H +#define NECRONDA_SERVER_REV_PROXY_H + +#endif //NECRONDA_SERVER_REV_PROXY_H diff --git a/src/sock.c b/src/sock.c new file mode 100644 index 0000000..4d090a4 --- /dev/null +++ b/src/sock.c @@ -0,0 +1,104 @@ +/** + * Necronda Web Server + * Basic TCP and TLS socket + * src/sock.c + * Lorenz Stechauner, 2021-01-07 + */ + +#include "sock.h" + +const char *sock_strerror(sock *s) { + if (s->_last_ret == 0) { + return "closed"; + } else if (s->enc) { + if (s->_last_ret > 0) { + return NULL; + } + const char *err1 = ERR_reason_error_string(s->_ssl_error); + const char *err2 = strerror(errno); + switch (SSL_get_error(s->ssl, (int) s->_last_ret)) { + case SSL_ERROR_NONE: + return NULL; + case SSL_ERROR_ZERO_RETURN: + return "closed"; + case SSL_ERROR_WANT_READ: + return "want read"; + case SSL_ERROR_WANT_WRITE: + return "want write"; + case SSL_ERROR_WANT_CONNECT: + return "want connect"; + case SSL_ERROR_WANT_ACCEPT: + return "want accept"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "want x509 lookup"; + case SSL_ERROR_SYSCALL: + return ((s->_ssl_error == 0) ? ((s->_last_ret == 0) ? "protocol violation" : err2) : err1); + case SSL_ERROR_SSL: + return err1; + default: + return "unknown error"; + } + } else { + return strerror(s->_errno); + } +} + +long sock_send(sock *s, void *buf, unsigned long len, int flags) { + long ret; + if (s->enc) { + ret = SSL_write(s->ssl, buf, (int) len); + } else { + ret = send(s->socket, buf, len, flags); + } + s->_last_ret = ret; + s->_errno = errno; + s->_ssl_error = ERR_get_error(); + return ret >= 0 ? ret : -1; +} + +long sock_recv(sock *s, void *buf, unsigned long len, int flags) { + long ret; + if (s->enc) { + if (flags & MSG_PEEK) { + ret = SSL_peek(s->ssl, buf, (int) len); + } else { + ret = SSL_read(s->ssl, buf, (int) len); + } + } else { + ret = recv(s->socket, buf, len, flags); + } + s->_last_ret = ret; + s->_errno = errno; + s->_ssl_error = ERR_get_error(); + return ret >= 0 ? ret : -1; +} + +long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) { + long ret; + unsigned long send_len = 0; + unsigned long next_len; + while (send_len < len) { + next_len = buf_len < len - send_len ? buf_len : len - send_len; + ret = sock_recv(src, buf, next_len, 0); + if (ret < 0) return -2; + if (ret != next_len) return -3; + ret = sock_send(dst, buf, next_len, send_len + next_len < len ? MSG_MORE : 0); + if (ret < 0) return -1; + if (ret != next_len) return -3; + send_len += next_len; + } + return (long) send_len; +} + +int sock_close(sock *s) { + if ((int) s->enc && s->ssl != NULL) { + SSL_shutdown(s->ssl); + SSL_free(s->ssl); + } + shutdown(s->socket, SHUT_RDWR); + close(s->socket); + s->socket = 0; + s->enc = 0; + s->ssl = NULL; + return 0; +} diff --git a/src/sock.h b/src/sock.h new file mode 100644 index 0000000..5673dd5 --- /dev/null +++ b/src/sock.h @@ -0,0 +1,32 @@ +/** + * Necronda Web Server + * Basic TCP and TLS socket (header file) + * src/sock.h + * Lorenz Stechauner, 2021-01-07 + */ + +#ifndef NECRONDA_SERVER_SOCK_H +#define NECRONDA_SERVER_SOCK_H + +typedef struct { + unsigned int enc:1; + int socket; + SSL_CTX *ctx; + SSL *ssl; + char *buf; + unsigned long buf_len; + unsigned long buf_off; + long _last_ret; + int _errno; + unsigned long _ssl_error; +} sock; + +const char *sock_strerror(sock *s); + +long sock_send(sock *s, void *buf, unsigned long len, int flags); + +long sock_recv(sock *s, void *buf, unsigned long len, int flags); + +long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len); + +#endif //NECRONDA_SERVER_SOCK_H