diff --git a/ChangeLog b/ChangeLog index 85ffb0aa..95f10a8f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,7 @@ 24/06/2020 Oleg Moskalenko Mihály Mészáros Version 4.5.2 'dan Eider': - fix null pointer dereference in case of out of memory. (thanks to Thomas Moeller for the report) - - merge PR #517 (by wolmi) + - merge PR #517 (by wolmi) * add prometheus metrics - merge PR #637 (by David Florness) * Delete trailing whitespace in example configuration files @@ -21,6 +21,8 @@ Version 4.5.2 'dan Eider': - update Docker mongoDB and fix with workaround the missing systemctl - merge PR #660 (by Camden Narzt) * fix compilation on macOS Big Sur + - merge PR #546 (by jelmd) + * Add ACME redirect url 24/06/2020 Oleg Moskalenko Mihály Mészáros Version 4.5.1.3 'dan Eider': diff --git a/Makefile.in b/Makefile.in index a4479e15..b869d0e6 100755 --- a/Makefile.in +++ b/Makefile.in @@ -21,7 +21,7 @@ COMMON_MODS = src/apps/common/apputils.c src/apps/common/ns_turn_utils.c src/app COMMON_DEPS = ${LIBCLIENTTURN_DEPS} ${COMMON_MODS} ${COMMON_HEADERS} IMPL_HEADERS = src/apps/relay/ns_ioalib_impl.h src/apps/relay/ns_sm.h src/apps/relay/turn_ports.h -IMPL_MODS = src/apps/relay/ns_ioalib_engine_impl.c src/apps/relay/turn_ports.c src/apps/relay/http_server.c +IMPL_MODS = src/apps/relay/ns_ioalib_engine_impl.c src/apps/relay/turn_ports.c src/apps/relay/http_server.c src/apps/relay/acme.c IMPL_DEPS = ${COMMON_DEPS} ${IMPL_HEADERS} ${IMPL_MODS} HIREDIS_HEADERS = src/apps/common/hiredis_libevent2.h diff --git a/man/man1/turnserver.1 b/man/man1/turnserver.1 index 4fbd2673..3a9131bd 100644 --- a/man/man1/turnserver.1 +++ b/man/man1/turnserver.1 @@ -787,13 +787,6 @@ File name to store the pid of the process. Default is /var/run/turnserver.pid (if superuser account is used) or /var/tmp/turnserver.pid . .TP -.BI --acme-redirect\ URL -Redirect ACME/RFC8555 (like Let's Encrypt challenge) requests, i.e. -HTTP GET requests matching '^/.well-known/acme-challenge/(.*)' -to \fIURL\fR$1 with $1 == (.*). No validation of \fIURL\fR will be done, -so make sure you do not forget the trailing slash. If \fIURL\fR is an empty -string (the default value), no special handling of such requests will be done. -.TP .B \fB\-\-proc\-user\fP User name to run the process. After the initialization, the \fIturnserver\fP process diff --git a/src/apps/relay/acme.c b/src/apps/relay/acme.c new file mode 100644 index 00000000..3e6d0c5e --- /dev/null +++ b/src/apps/relay/acme.c @@ -0,0 +1,111 @@ + +/* + * Copyright (C) 2011, 2012, 2013, 2014 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "acme.h" +#include "ns_ioalib_impl.h" + +static int is_acme_req(char *req, size_t len) { + static const char *A = " - 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz "; + int c, i, k; + + // Check first request line. Should be like: GET path HTTP/1.x + if (strncmp(req, "GET /.well-known/acme-challenge/", 32)) + return -1; + // Usually (for LE) the "method path" is 32 + 43 = 55 chars. But other + // implementations may choose longer pathes. We define PATHMAX = 127 chars + // to be prepared for "DoS" attacks (STUN msg size max. is ~ 64K). + len =- 21; // min size of trailing headers + if (len > 131) + len = 131; + for (i=32; i < (int) len; i++) { + // find the end of the path + if (req[i] != ' ') + continue; + // consider path < 10 chars invalid. Also we wanna see a "trailer". + if (i < 42 || strncmp(req + i, " HTTP/1.", 8)) + return -2; + // finally check for allowed chars + for (k=32; k < i; k++) { + c = req[k]; + if ((c > 127) || (A[c] == ' ')) + return -3; + } + // all checks passed: sufficient for us to answer with a redirect + return i; + } + return -4; // end of path not found +} + +int try_acme_redirect(char *req, size_t len, const char *url, + ioa_socket_handle s) +{ + static const char *HTML = "301 Moved Permanently

301 Moved Permanently

"; + char http_response[1024]; + size_t plen, rlen; + + if (url == NULL || url[0] == '\0' || req == NULL || s == 0 ) + return 1; + if (len < 64 || len > 512 || (plen = is_acme_req(req, len)) < 33) + return 2; + + + snprintf(http_response, sizeof(http_response) - 1, + "HTTP/1.1 301 Moved Permanently\r\n" + "Content-Type: text/html\r\n" + "Content-Length: %ld\r\n" + "Connection: close\r\n" + "Location: %s%s\r\n" + "\r\n%s", strlen(HTML), url, req + 32, HTML); + + rlen = strlen(http_response); + + // Variant A: direkt write, no eventbuf stuff + /* + if (write(s->fd, http_response, rlen) == -1) { + perror("Sending redirect failed"); + } else if (((turn_turnserver *)s->session->server)->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "ACME redirect to %s%s\n", + url, req + 32); + } + + req[plen] = ' '; + */ + // Variant B: via eventbuf does not send anything for whatever reason + + //set_ioa_socket_app_type(s, HTTP_CLIENT_SOCKET); + ioa_network_buffer_handle nbh_acme = ioa_network_buffer_allocate(s->e); + uint8_t *data = ioa_network_buffer_data(nbh_acme); + bcopy(http_response, data, rlen); + ioa_network_buffer_set_size(nbh_acme, rlen); + send_data_from_ioa_socket_nbh(s, NULL, nbh_acme, TTL_IGNORE, TOS_IGNORE, NULL); + + return 0; +} diff --git a/src/apps/relay/acme.h b/src/apps/relay/acme.h new file mode 100644 index 00000000..133c22d1 --- /dev/null +++ b/src/apps/relay/acme.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011, 2012, 2013, 2014 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_ACME__ +#define __TURN_ACME__ + +#include "ns_turn_utils.h" +#include "ns_turn_server.h" +#include "apputils.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +///////////// ACME ///////////////////// + +int try_acme_redirect(char *req, size_t len, const char *url, ioa_socket_handle s); + +/////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif +/// __TURN_ACME__ /// + diff --git a/src/apps/relay/http_server.c b/src/apps/relay/http_server.c index ca70d77e..ff8e3992 100644 --- a/src/apps/relay/http_server.c +++ b/src/apps/relay/http_server.c @@ -99,83 +99,6 @@ const char* get_http_date_header() return buffer_header; } -static int is_acme_req(char *req, size_t len) { - static const char *A = " - 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz "; - int c, i, k; - - // Check first request line. Should be like: GET path HTTP/1.x - if (strncmp(req, "GET /.well-known/acme-challenge/", 32)) - return -1; - // Usually (for LE) the "method path" is 32 + 43 = 55 chars. But other - // implementations may choose longer pathes. We define PATHMAX = 127 chars - // to be prepared for "DoS" attacks (STUN msg size max. is ~ 64K). - len =- 21; // min size of trailing headers - if (len > 131) - len = 131; - for (i=32; i < (int) len; i++) { - // find the end of the path - if (req[i] != ' ') - continue; - // consider path < 10 chars invalid. Also we wanna see a "trailer". - if (i < 42 || strncmp(req + i, " HTTP/1.", 8)) - return -2; - // finally check for allowed chars - for (k=32; k < i; k++) { - c = req[k]; - if ((c > 127) || (A[c] == ' ')) - return -3; - } - // all checks passed: sufficient for us to answer with a redirect - return i; - } - return -4; // end of path not found -} - -int try_acme_redirect(char *req, size_t len, const char *url, - ioa_socket_handle s) -{ - static const char *HTML = "301 Moved Permanently

301 Moved Permanently

"; - char http_response[1024]; - int plen, rlen; - - if (url == NULL || url[0] == '\0' || req == NULL || s == 0 ) - return 1; - if (len < 64 || len > 512 || (plen = is_acme_req(req, len)) < 33) - return 2; - - req[plen] = '\0'; - snprintf(http_response, sizeof(http_response) - 1, - "HTTP/1.1 301 Moved Permanently\r\n" - "Content-Type: text/html\r\n" - "Content-Length: %ld\r\n" - "Connection: close\r\n" - "Location: %s%s\r\n" - "\r\n%s", strlen(HTML), url, req + 32, HTML); - - rlen = strlen(http_response); - - // Variant A: direkt write, no eventbuf stuff - if (write(s->fd, http_response, rlen) == -1) { - perror("Sending redirect failed"); - } else if (((turn_turnserver *)s->session->server)->verbose) { - TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "ACME redirect to %s%s\n", - url, req + 32); - } - - req[plen] = ' '; - - // Variant B: via eventbuf does not send anything for whatever reason - /* - set_ioa_socket_app_type(s, HTTP_CLIENT_SOCKET); - ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(s->e); - uint8_t *data = ioa_network_buffer_data(nbh); - bcopy(http_response, data, rlen); - ioa_network_buffer_set_size(nbh, rlen); - send_data_from_ioa_socket_nbh(s, NULL, nbh, TTL_IGNORE, TOS_IGNORE, NULL); - */ - - return 0; -} /////////////////////////////////////////////// static struct headers_list * post_parse(char *data, size_t data_len) diff --git a/src/server/ns_turn_ioalib.h b/src/server/ns_turn_ioalib.h index 3a25b03e..da3e7c5a 100644 --- a/src/server/ns_turn_ioalib.h +++ b/src/server/ns_turn_ioalib.h @@ -285,6 +285,9 @@ int get_default_protocol_port(const char* scheme, size_t slen); ///////////// HTTP //////////////////// void handle_http_echo(ioa_socket_handle s); + +///////////// ACME ///////////////////// + int try_acme_redirect(char *req, size_t len, const char *url, ioa_socket_handle s); /////////////////////////////////////// diff --git a/src/server/ns_turn_server.c b/src/server/ns_turn_server.c index 3d3eaa74..dafe26ee 100644 --- a/src/server/ns_turn_server.c +++ b/src/server/ns_turn_server.c @@ -4624,19 +4624,24 @@ static int read_client_connection(turn_turnserver *server, } else { SOCKET_TYPE st = get_ioa_socket_type(ss->client_socket); if(is_stream_socket(st)) { - char *str = (char*)ioa_network_buffer_data(in_buffer->nbh); - size_t l = ioa_network_buffer_get_size(in_buffer->nbh); - if(is_http(str, l)) { + if(is_http((char*)ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh))) { + const char *proto = "HTTP"; - str[l] = 0; - if ((st == TCP_SOCKET) && (try_acme_redirect(str, l, server->acme_redirect, ss->client_socket) == 0)) { + if ((st == TCP_SOCKET) && + ( + try_acme_redirect((char*)ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + server->acme_redirect, ss->client_socket) == 0 + ) + ) { ss->to_be_closed = 1; return 0; } else if (*server->web_admin_listen_on_workers) { if(st==TLS_SOCKET) { proto = "HTTPS"; set_ioa_socket_app_type(ss->client_socket,HTTPS_CLIENT_SOCKET); - TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s (%s %s) request: %s\n", __FUNCTION__, proto, get_ioa_socket_cipher(ss->client_socket), get_ioa_socket_ssl_method(ss->client_socket), str); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s (%s %s) request: %s\n", __FUNCTION__, proto, get_ioa_socket_cipher(ss->client_socket), get_ioa_socket_ssl_method(ss->client_socket), ioa_network_buffer_get_size(in_buffer->nbh)); if(server->send_https_socket) { TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s socket to be detached: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)ss->client_socket, get_ioa_socket_type(ss->client_socket), get_ioa_socket_app_type(ss->client_socket)); ioa_socket_handle new_s = detach_ioa_socket(ss->client_socket); @@ -4649,7 +4654,7 @@ static int read_client_connection(turn_turnserver *server, } else { set_ioa_socket_app_type(ss->client_socket,HTTP_CLIENT_SOCKET); if(server->verbose) { - TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s request: %s\n", __FUNCTION__, proto, str); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s request: %s\n", __FUNCTION__, proto, ioa_network_buffer_get_size(in_buffer->nbh)); } handle_http_echo(ss->client_socket); }