From 5105dfafd36c2d06f7a4d52310a39155cb5dde6b Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Mon, 18 May 2020 07:21:56 +0200 Subject: [PATCH 1/2] support of --acme-redirect --- src/apps/relay/http_server.c | 77 ++++++++++++++++++++++++++++++++++++ src/apps/relay/mainrelay.c | 14 ++++++- src/apps/relay/mainrelay.h | 1 + src/apps/relay/netengine.c | 1 + src/server/ns_turn_ioalib.h | 1 + src/server/ns_turn_server.c | 17 +++++--- src/server/ns_turn_server.h | 4 ++ 7 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/apps/relay/http_server.c b/src/apps/relay/http_server.c index ff8e3992..ca70d77e 100644 --- a/src/apps/relay/http_server.c +++ b/src/apps/relay/http_server.c @@ -99,6 +99,83 @@ 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/apps/relay/mainrelay.c b/src/apps/relay/mainrelay.c index 2ac08fcf..56b4c8af 100644 --- a/src/apps/relay/mainrelay.c +++ b/src/apps/relay/mainrelay.c @@ -109,7 +109,7 @@ NULL, PTHREAD_MUTEX_INITIALIZER, //////////////// Common params //////////////////// TURN_VERBOSE_NONE,0,0,0,0, -"/var/run/turnserver.pid", +"/var/run/turnserver.pid","", DEFAULT_STUN_PORT,DEFAULT_STUN_TLS_PORT,0,0,0,1, 0,0,0,0,0, "", @@ -615,6 +615,8 @@ static char Usage[] = "Usage: turnserver [options]\n" " --pidfile <\"pid-file-name\"> File name to store the pid of the process.\n" " Default is /var/run/turnserver.pid (if superuser account is used) or\n" " /var/tmp/turnserver.pid .\n" +" --acme-redirect <\"URL\"> Redirect HTTP GET requests matching '^/.well-known/acme-challenge/(.*)' to '<\"URL\">$1'\n" +" Default is '', i.e. no special handling for such requests.\n" " --secure-stun Require authentication of the STUN Binding request.\n" " By default, the clients are allowed anonymous access to the STUN Binding functionality.\n" " --proc-user User name to run the turnserver process.\n" @@ -793,7 +795,8 @@ enum EXTRA_OPTS { OAUTH_OPT, NO_SOFTWARE_ATTRIBUTE_OPT, NO_HTTP_OPT, - SECRET_KEY_OPT + SECRET_KEY_OPT, + ACME_REDIRECT_OPT }; struct myoption { @@ -922,6 +925,7 @@ static const struct myoption long_options[] = { { "no-tlsv1_2", optional_argument, NULL, NO_TLSV1_2_OPT }, { "secret-key-file", required_argument, NULL, SECRET_KEY_OPT }, { "keep-address-family", optional_argument, NULL, 'K' }, + { "acme-redirect", required_argument, NULL, ACME_REDIRECT_OPT }, { NULL, no_argument, NULL, 0 } }; @@ -1560,6 +1564,9 @@ static void set_option(int c, char *value) case PIDFILE_OPT: STRCPY(turn_params.pidfile,value); break; + case ACME_REDIRECT_OPT: + STRCPY(turn_params.acme_redirect,value); + break; case 'C': if(value && *value) { turn_params.rest_api_separator=*value; @@ -2238,6 +2245,9 @@ int main(int argc, char **argv) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Domain name: %s\n",turn_params.domain); TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Default realm: %s\n",get_realm(NULL)->options.name); + if(turn_params.acme_redirect[0]) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "ACME redirect URL: %s\n",turn_params.acme_redirect); + } if(turn_params.oauth && turn_params.oauth_server_name[0]) { TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "oAuth server name: %s\n",turn_params.oauth_server_name); } diff --git a/src/apps/relay/mainrelay.h b/src/apps/relay/mainrelay.h index 4b584f74..75ba63b8 100644 --- a/src/apps/relay/mainrelay.h +++ b/src/apps/relay/mainrelay.h @@ -219,6 +219,7 @@ typedef struct _turn_params_ { int do_not_use_config_file; char pidfile[1025]; + char acme_redirect[1025]; //////////////// Listener server ///////////////// diff --git a/src/apps/relay/netengine.c b/src/apps/relay/netengine.c index ca88056e..20f558cd 100644 --- a/src/apps/relay/netengine.c +++ b/src/apps/relay/netengine.c @@ -1667,6 +1667,7 @@ static void setup_relay_server(struct relay_server *rs, ioa_engine_handle e, int allocate_bps, turn_params.oauth, turn_params.oauth_server_name, + turn_params.acme_redirect, turn_params.keep_address_family); if(to_set_rfc5780) { diff --git a/src/server/ns_turn_ioalib.h b/src/server/ns_turn_ioalib.h index 6737711d..3a25b03e 100644 --- a/src/server/ns_turn_ioalib.h +++ b/src/server/ns_turn_ioalib.h @@ -285,6 +285,7 @@ int get_default_protocol_port(const char* scheme, size_t slen); ///////////// HTTP //////////////////// void handle_http_echo(ioa_socket_handle s); +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 38a15134..3d3eaa74 100644 --- a/src/server/ns_turn_server.c +++ b/src/server/ns_turn_server.c @@ -4624,14 +4624,19 @@ static int read_client_connection(turn_turnserver *server, } else { SOCKET_TYPE st = get_ioa_socket_type(ss->client_socket); if(is_stream_socket(st)) { - if(is_http((char*)ioa_network_buffer_data(in_buffer->nbh), ioa_network_buffer_get_size(in_buffer->nbh))) { + 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)) { const char *proto = "HTTP"; - ioa_network_buffer_data(in_buffer->nbh)[ioa_network_buffer_get_size(in_buffer->nbh)] = 0; - if (*server->web_admin_listen_on_workers) { + str[l] = 0; + if ((st == TCP_SOCKET) && (try_acme_redirect(str, l, 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), (char*)ioa_network_buffer_data(in_buffer->nbh)); + 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); 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); @@ -4644,7 +4649,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, (char*)ioa_network_buffer_data(in_buffer->nbh)); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s request: %s\n", __FUNCTION__, proto, str); } handle_http_echo(ss->client_socket); } @@ -4915,6 +4920,7 @@ void init_turn_server(turn_turnserver* server, allocate_bps_cb allocate_bps_func, int oauth, const char* oauth_server_name, + const char* acme_redirect, int keep_address_family) { if (!server) @@ -4944,6 +4950,7 @@ void init_turn_server(turn_turnserver* server, server->oauth_server_name = oauth_server_name; if(mobility) server->mobile_connections_map = ur_map_create(); + server->acme_redirect = acme_redirect; TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"turn server id=%d created\n",(int)id); diff --git a/src/server/ns_turn_server.h b/src/server/ns_turn_server.h index 924a507a..0df99716 100644 --- a/src/server/ns_turn_server.h +++ b/src/server/ns_turn_server.h @@ -171,6 +171,9 @@ struct _turn_turnserver { int oauth; const char* oauth_server_name; + /* ACME redirect URL */ + const char* acme_redirect; + /* Keep Address Family */ int keep_address_family; }; @@ -218,6 +221,7 @@ void init_turn_server(turn_turnserver* server, allocate_bps_cb allocate_bps_func, int oauth, const char* oauth_server_name, + const char* acme_redirect, int keep_address_family); ioa_engine_handle turn_server_get_engine(turn_turnserver *s); From 7e2c98bc1f28f12be9e47834da3a21d41da926f7 Mon Sep 17 00:00:00 2001 From: Jens Elkner Date: Mon, 18 May 2020 17:00:31 +0200 Subject: [PATCH 2/2] acme-redirect: add option to man page, fix help text --- man/man1/turnserver.1 | 7 +++++++ src/apps/relay/mainrelay.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/man/man1/turnserver.1 b/man/man1/turnserver.1 index df298c54..e6a377e1 100644 --- a/man/man1/turnserver.1 +++ b/man/man1/turnserver.1 @@ -770,6 +770,13 @@ 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/mainrelay.c b/src/apps/relay/mainrelay.c index 56b4c8af..854c080d 100644 --- a/src/apps/relay/mainrelay.c +++ b/src/apps/relay/mainrelay.c @@ -615,7 +615,7 @@ static char Usage[] = "Usage: turnserver [options]\n" " --pidfile <\"pid-file-name\"> File name to store the pid of the process.\n" " Default is /var/run/turnserver.pid (if superuser account is used) or\n" " /var/tmp/turnserver.pid .\n" -" --acme-redirect <\"URL\"> Redirect HTTP GET requests matching '^/.well-known/acme-challenge/(.*)' to '<\"URL\">$1'\n" +" --acme-redirect Redirect ACME, i.e. HTTP GET requests matching '^/.well-known/acme-challenge/(.*)' to '$1'.\n" " Default is '', i.e. no special handling for such requests.\n" " --secure-stun Require authentication of the STUN Binding request.\n" " By default, the clients are allowed anonymous access to the STUN Binding functionality.\n"