From abad64b9cf46b0f9d6b85bf8a59f513856ea75fb Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Mon, 12 Jun 2023 14:44:04 -0700 Subject: [PATCH 1/7] [CONC-648] Do not trust error packets received prior to TLS handshake completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MariaDB Connector/C does not distinguish [application-layer error packets](https://mariadb.com/kb/en/err_packet) that it receives prior to TLS handshake completion from those that it receives immediately after. (A trivially modified server built from https://github.com/dlenski/mariadb-server/commit/demonstration_of_CONC-648_vulnerability can easily be used to demonstrate this.) Pre-TLS error packet received from this trivially modified server. This packet should NOT be trusted to actually originate from the server: $ mariadb --ssl --ssl-verify-server-cert -uUsername -pVerySecretPassword -h CONC-648.vuln.demo.server.com ERROR 1815 (HY000): Internal error: Client will accept this error as genuine even if running with --ssl --ssl-verify-server-cert, and even though this error is sent in plaintext PRIOR TO TLS HANDSHAKE. Post-(TLS handshake) error packet received from a normal MariaDB server upon an attempt to connect with incorrect credentials. This error packet CAN be trusted to actually originate from the server, assuming transitive trust in the TLS protocol implementation and PKI-based certificate validation: $ mariadb --ssl --ssl-verify-server-cert -uUsername -pWrongPassword -h $NORMAL_MARIADB10.6.14_SERVER ERROR 1045 (28000): Access denied for user 'Username'@'A.B.C.D' (using password: YES) This client behavior opens up MariaDB Connector/C clients to an extremely straightforward [downgrade attack](https://en.wikipedia.org/wiki/Downgrade_attack). An on-path or pervasive attacker can inject errors into MariaDB client→server connections that are intended to be protected by TLS, and the client has no clear mechanism to distinguish such errors from errors that actually come from the server. An attacker could easily use this to DOS a client, or even influence its behavior. For example, consider a client application which is configured… 1. To use TLS with server certificate validation (`--ssl --ssl-verify-server-cert`), and 2. To wait for a back-off period and then *retry* connection attempts if the server responds with `ER_CON_COUNT_ERROR` ("Too many connections") from the server, and 3. To give up and shut down if its connection attempts fail with `ER_ACCESS_DENIED_ERROR` ("Access denied for user"), on the assumption that this is due to an incorrect or expired password, and cannot be resolved without human intervention. An attacker could completely disable the retry mechanism of this application by intercepting connection attempts and replying with `ER_ACCESS_DENIED_ERROR` packets. This patch modifies MariaDB Connector/C so that if the client is configured to use TLS, error packets received prior to the completion of the TLS handshake are untrusted, and are changed to a generic `CR_CONNECTION_ERROR`. $ mariadb --ssl --ssl-verify-server-cert -uUsername -pVerySecretPassword -h CONC-648.vuln.demo.server.com ERROR 2002 (HY000): Received error packet before completion of TLS handshake. Suppressing its details so that the client cannot vary its behavior based on this UNTRUSTED input. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- libmariadb/mariadb_lib.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 8907f23ce..5f922efe4 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1787,7 +1787,12 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, */ if ((pkt_length=ma_net_safe_read(mysql)) == packet_error) { - if (mysql->net.last_errno == CR_SERVER_LOST) + if (mysql->options.use_ssl) + my_set_error(mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + "Received error packet before completion of TLS handshake. " + "Suppressing its details so that the client cannot vary its behavior " + "based on this UNTRUSTED input."); + else if (mysql->net.last_errno == CR_SERVER_LOST) my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "handshake: reading initial communication packet", From 165110d6fd40d1fa1d434936b087e64325f2ac0d Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 14 Jun 2023 11:38:19 -0700 Subject: [PATCH 2/7] [MDEV-28634] Do not allow silent downgrade of connections where SSL is requested The 2015 commit https://github.com/mariadb-corporation/mariadb-connector-c/commit/23895fbd4#diff-4339ae6506ef1fb201f6f836085257e72c191d2b4498df507d499fc30d891005 was described as "Fixed gnutls support", which is an extremely incomplete description of what it actually does. In that commit, the Connector/C client authentication plugin was modified to abort following the initial server greeting packet if the client has requested a secure transport (`mariadb --ssl`), but the server does not advertise support for SSL/TLS. However, there's a crucial caveat here: If the client has requested secure transport (`mariadb --ssl`) but has not requested verification of the server's TLS certificate (`mariadb --ssl --ssl-verify-server-cert`), then the client will silently accept an unencrypted connection, without even printing a diagnostic message. Thus, any such client is susceptible to a trivial downgrade to an unencrypted connection by an on-path attacker who simply flips the TLS/SSL capability bit in the advertised server capabilities in the greeting packet. The entire design of MariaDB's TLS support is severely flawed in terms of user expectations surrounding secure-by-default connections: the `--ssl` option SHOULD imply `--ssl-verify-server-cert` BY DEFAULT; if a client actually wants a TLS connection that's susceptible to a trivial MITM'ed by any pervasive or on-path attacker, that should be an exceptional case (e.g. `--insecure-ssl-without-server-cert-verification`) rather than the default. Even without resolving the issue of no default verification of server certificates, the issue of silent downgrade to unencrypted connections can and should be resolved. Backwards compatibility: This is an INTENTIONAL backwards-incompatible change to prevent clients from being silently downgraded to unencrypted connections, when they've specified `--ssl` and thus clearly indicated that they do not want unencrypted connections. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- plugins/auth/my_auth.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/plugins/auth/my_auth.c b/plugins/auth/my_auth.c index f5d46ced3..daeb3074a 100644 --- a/plugins/auth/my_auth.c +++ b/plugins/auth/my_auth.c @@ -245,22 +245,15 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, mysql->server_capabilities &= ~(CLIENT_SSL); } - /* if server doesn't support SSL and verification of server certificate - was set to mandatory, we need to return an error */ + /* if server doesn't support SSL, we need to return an error */ if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) { - if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) || - (mysql->options.extension && (mysql->options.extension->tls_fp || - mysql->options.extension->tls_fp_list))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "SSL is required, but the server does not support it"); - goto error; - } + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "SSL is required, but the server does not support it"); + goto error; } - /* Remove options that server doesn't support */ mysql->client_flag= mysql->client_flag & (~(CLIENT_COMPRESS | CLIENT_ZSTD_COMPRESSION | CLIENT_SSL | CLIENT_PROTOCOL_41) From 2ebbeb9139815757c0017e4a5a14615c33880ad4 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 14 Jun 2023 12:25:39 -0700 Subject: [PATCH 3/7] [MDEV-28634] Move check for server TLS/SSL capability to mthd_my_real_connect Two reasons: 1. Reduction of attack surface As soon as the client receives the server's capability flags, it knows whether the server supports TLS/SSL. If the server does not support TLS/SSL, but the client expects and requires it, the client should immediately abort at this point in order to truncate any code paths by which it could inadvertently continue to communicate without TLS/SSL. 2. Separation of concerns Whether or not the server supports TLS/SSL encryption at the transport layer (TLS stands for TRANSPORT-layer security) is a logically separate issue from what APPLICATION-layer authentication modes the client and server support or should use. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- libmariadb/mariadb_lib.c | 10 ++++++++++ plugins/auth/my_auth.c | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 5f922efe4..e0f33c4ba 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1923,6 +1923,16 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, } } + /* We now know the server's capabilities. If the client wants TLS/SSL, + * but the server doesn't support it, we should immediately abort. + */ + if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) + { + SET_CLIENT_ERROR(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + "Client requires TLS/SSL, but the server does not support it"); + goto error; + } + /* Set character set */ if (mysql->options.charset_name) mysql->charset= mysql_find_charset_name(mysql->options.charset_name); diff --git a/plugins/auth/my_auth.c b/plugins/auth/my_auth.c index daeb3074a..991306d97 100644 --- a/plugins/auth/my_auth.c +++ b/plugins/auth/my_auth.c @@ -245,15 +245,6 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, mysql->server_capabilities &= ~(CLIENT_SSL); } - /* if server doesn't support SSL, we need to return an error */ - if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "SSL is required, but the server does not support it"); - goto error; - } - /* Remove options that server doesn't support */ mysql->client_flag= mysql->client_flag & (~(CLIENT_COMPRESS | CLIENT_ZSTD_COMPRESSION | CLIENT_SSL | CLIENT_PROTOCOL_41) From b0909945f9ec18186cadd9b217fe784a5be8c332 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 14 Jun 2023 12:25:39 -0700 Subject: [PATCH 4/7] Add comments about usage of TLS/SSL in connection establishment All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- libmariadb/mariadb_lib.c | 11 +++++++++++ plugins/auth/my_auth.c | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index e0f33c4ba..fde3c7409 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1951,6 +1951,17 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, mysql->client_flag= client_flag; + /* Until run_plugin_auth has completed, the connection + * cannot have been secured with TLS/SSL. + * + * This means that any client which expects to use a + * TLS/SSL-secured connection SHOULD NOT trust any + * communication received from the server prior to this + * point as being genuine; nor should either the client + * or the server send any confidential information up + * to this point. + */ + if (run_plugin_auth(mysql, scramble_data, scramble_len, scramble_plugin, db)) goto error; diff --git a/plugins/auth/my_auth.c b/plugins/auth/my_auth.c index 991306d97..c65729a16 100644 --- a/plugins/auth/my_auth.c +++ b/plugins/auth/my_auth.c @@ -321,8 +321,19 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, (mysql->client_flag & CLIENT_SSL)) { /* - Send mysql->client_flag, max_packet_size - unencrypted otherwise - the server does not know we want to do SSL + Send UNENCRYPTED "Login Request" packet with mysql->client_flag + and max_packet_size, but no username; without this, the server + does not know we want to switch to SSL/TLS + + FIXME: Sending this packet is a very very VERY bad idea. It + contains the client's preferred charset and flags in plaintext; + this can be used for fingerprinting the client software version, + and probable geographic location. + + This offers a glaring opportunity for pervasive attackers to + easily target, intercept, and exploit the client-server + connection (e.g. "MITM all connections from known-vulnerable + client versions originating from countries X, Y, and Z"). */ if (ma_net_write(net, (unsigned char *)buff, (size_t) (end-buff)) || ma_net_flush(net)) { @@ -332,6 +343,9 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, errno); goto error; } + /* This is where the socket is actually converted from a plain + * TCP/IP socket to a TLS/SSL-wrapped socket. + */ if (ma_pvio_start_ssl(mysql->net.pvio)) goto error; } From c73f9bdc81f5969861d1e2efa75561c8ab19808e Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Mon, 19 Jun 2023 17:09:54 -0700 Subject: [PATCH 5/7] [MDEV-15935] Follow server redirection error packets If the server responds to our initial connection with an error packet having error number 4196 (`ER_SERVER_REDIRECT`), and an error string formatted as `|Human-readable message|host[:port]`, then redirect accordingly. This redirection is performed in `mthd_my_real_connect`, which appears to be the most appropriate location given that it already contains code for retrying/repeating a new server connection. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- include/mysqld_error.h | 3 ++- libmariadb/mariadb_lib.c | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/mysqld_error.h b/include/mysqld_error.h index 133654374..913dbe841 100644 --- a/include/mysqld_error.h +++ b/include/mysqld_error.h @@ -1236,5 +1236,6 @@ #define ER_JSON_HISTOGRAM_PARSE_FAILED 4186 #define ER_SF_OUT_INOUT_ARG_NOT_ALLOWED 4187 #define ER_INCONSISTENT_SLAVE_TEMP_TABLE 4188 -#define ER_ERROR_LAST 4188 +#define ER_SERVER_REDIRECT 4196 +#define ER_ERROR_LAST 4196 #endif /* ER_ERROR_FIRST */ diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index fde3c7409..26657e004 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1708,6 +1708,7 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, else #endif { + tcp_redirect: cinfo.unix_socket=0; /* This is not used */ if (!port) port=mysql_port; @@ -1964,7 +1965,30 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, if (run_plugin_auth(mysql, scramble_data, scramble_len, scramble_plugin, db)) + { + if (mysql->net.last_errno == ER_SERVER_REDIRECT) + { + char *p= mysql->net.last_error; /* Should look like '|message|host[:port]' */ + if (p && p[0] == '|') + p= strchr(p + 1, '|') ? : NULL; + if (p && *++p) { + host= p; + p= strchr(p, ':') ? : NULL; + if (p) { + *p++ = '\0'; + port= atoi(p); + } + else + { + /* Restore to the default port, rather than reusing our current one */ + port= 0; + } + fprintf(stderr, "Got server redirect to '%s' (port %d)\n", host, port); + goto tcp_redirect; + } + } goto error; + } if (mysql->client_flag & CLIENT_COMPRESS || mysql->client_flag & CLIENT_ZSTD_COMPRESSION) From 54886ac29d045cb1021cf82e4e20947c59ed5f23 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 5 Jul 2023 12:24:12 -0700 Subject: [PATCH 6/7] Allow clients to disable following server redirects This adds a new boolean option, `follow-server-redirects` (ENABLED by default), to allow the client to disable server redirection and simply return an error message. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- include/mysql.h | 9 ++++++++ libmariadb/mariadb_lib.c | 45 ++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/include/mysql.h b/include/mysql.h index 76b168303..06068ce7e 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -227,6 +227,8 @@ extern const char *SQLSTATE_UNKNOWN; /* MariaDB specific */ MYSQL_PROGRESS_CALLBACK=5999, MYSQL_OPT_NONBLOCK, + MARIADB_OPT_FOLLOW_SERVER_REDIRECTS, + /* MariaDB Connector/C specific */ MYSQL_DATABASE_DRIVER=7000, MARIADB_OPT_SSL_FP, /* deprecated, use MARIADB_OPT_TLS_PEER_FP instead */ @@ -337,12 +339,19 @@ struct st_mysql_options { char *bind_address; my_bool secure_auth; my_bool report_data_truncation; + my_bool follow_server_redirects; + /* function pointers for local infile support */ int (*local_infile_init)(void **, const char *, void *); int (*local_infile_read)(void *, char *, unsigned int); void (*local_infile_end)(void *); int (*local_infile_error)(void *, char *, unsigned int); void *local_infile_userdata; + + /* WHY are some options relegated to this "extension" structure? + * What is the logic for distinguishing the "main" options from + * the extension options? + */ struct st_mysql_options_extension *extension; }; diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 26657e004..21a439136 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -657,6 +657,7 @@ struct st_default_options mariadb_defaults[] = {{MYSQL_SET_CHARSET_NAME}, MARIADB_OPTION_STR, "default-character-set"}, {{MARIADB_OPT_INTERACTIVE}, MARIADB_OPTION_NONE, "interactive-timeout"}, {{MYSQL_OPT_CONNECT_TIMEOUT}, MARIADB_OPTION_INT, "connect-timeout"}, + {{MARIADB_OPT_FOLLOW_SERVER_REDIRECTS}, MARIADB_OPTION_BOOL, "follow-server-redirects"}, {{MYSQL_OPT_LOCAL_INFILE}, MARIADB_OPTION_BOOL, "local-infile"}, {{0}, 0 ,"disable-local-infile",}, {{MYSQL_OPT_SSL_CIPHER}, MARIADB_OPTION_STR, "ssl-cipher"}, @@ -1283,6 +1284,7 @@ mysql_init(MYSQL *mysql) goto error; mysql->options.report_data_truncation= 1; mysql->options.connect_timeout=CONNECT_TIMEOUT; + mysql->options.follow_server_redirects= TRUE; mysql->charset= mysql_find_charset_name(MARIADB_DEFAULT_CHARSET); mysql->methods= &MARIADB_DEFAULT_METHODS; strcpy(mysql->net.sqlstate, "00000"); @@ -1968,23 +1970,31 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, { if (mysql->net.last_errno == ER_SERVER_REDIRECT) { - char *p= mysql->net.last_error; /* Should look like '|message|host[:port]' */ - if (p && p[0] == '|') - p= strchr(p + 1, '|') ? : NULL; - if (p && *++p) { - host= p; - p= strchr(p, ':') ? : NULL; - if (p) { - *p++ = '\0'; - port= atoi(p); - } - else - { - /* Restore to the default port, rather than reusing our current one */ - port= 0; + if (!mysql->options.follow_server_redirects) + { + /* Client has disabled server redirection. Fall through and treat this + * as a "normal" error. */ + } + else + { + char *p= mysql->net.last_error; /* Should look like '|message|host[:port]' */ + if (p && p[0] == '|') + p= strchr(p + 1, '|') ? : NULL; + if (p && *++p) { + host= p; + p= strchr(p, ':') ? : NULL; + if (p) { + *p++ = '\0'; + port= atoi(p); + } + else + { + /* Restore to the default port, rather than reusing our current one */ + port= 0; + } + fprintf(stderr, "Got server redirect to '%s' (port %d)\n", host, port); + goto tcp_redirect; } - fprintf(stderr, "Got server redirect to '%s' (port %d)\n", host, port); - goto tcp_redirect; } } goto error; @@ -3495,6 +3505,9 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) case MYSQL_OPT_RECONNECT: mysql->options.reconnect= *(my_bool *)arg1; break; + case MARIADB_OPT_FOLLOW_SERVER_REDIRECTS: + mysql->options.follow_server_redirects= *(my_bool *)arg1; + break; case MYSQL_OPT_PROTOCOL: mysql->options.protocol= *((uint *)arg1); break; From 9768c6e9519de629908ed9f4b0cd7b54b55688a7 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Wed, 5 Jul 2023 12:24:12 -0700 Subject: [PATCH 7/7] Limit number of server redirects followed by client Set the limit to 8 for now. This will prevent the client from getting stuck in redirect loops. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- libmariadb/mariadb_lib.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 21a439136..382f7d016 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -85,6 +85,7 @@ #define MA_RPL_VERSION_HACK "5.5.5-" #define CHARSET_NAME_LEN 64 +#define SERVER_REDIRECT_LIMIT 8 #undef max_allowed_packet #undef net_buffer_length @@ -1570,7 +1571,7 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, my_bool is_multi= 0; char *host_copy= NULL; struct st_host *host_list= NULL; - int connect_attempts= 0; + int connect_attempts= 0, server_redirects=0; if (!mysql->methods) mysql->methods= &MARIADB_DEFAULT_METHODS; @@ -1975,6 +1976,13 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, /* Client has disabled server redirection. Fall through and treat this * as a "normal" error. */ } + else if (server_redirects >= SERVER_REDIRECT_LIMIT) + { + /* Too many server redirects */ + my_set_error(mysql, ER_SERVER_REDIRECT, SQLSTATE_UNKNOWN, + "Too many server redirects (>= %d)", + SERVER_REDIRECT_LIMIT); + } else { char *p= mysql->net.last_error; /* Should look like '|message|host[:port]' */ @@ -1993,6 +2001,7 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, port= 0; } fprintf(stderr, "Got server redirect to '%s' (port %d)\n", host, port); + ++server_redirects; goto tcp_redirect; } }