diff --git a/src/serv-args.def b/src/serv-args.def index 44b67f1ab..027737772 100644 --- a/src/serv-args.def +++ b/src/serv-args.def @@ -8,6 +8,19 @@ detail = "Server program that listens to incoming TLS connections."; #include args-std.def +flag = { + name = sni-hostname; + descrip = "Server's hostname for server name extension"; + arg-type = string; + doc = "Server name of type host_name that the server will recognise as its own. If the server receives client hello with different name, it will send a warning-level unrecognized_name alert."; +}; + +flag = { + name = sni-hostname-fatal; + descrip = "Send fatal alert on sni-hostname mismatch"; + doc = ""; +}; + flag = { name = noticket; descrip = "Don't accept session tickets"; diff --git a/src/serv.c b/src/serv.c index a1f9adfa8..f5ff48786 100644 --- a/src/serv.c +++ b/src/serv.c @@ -49,6 +49,8 @@ #include "sockets.h" #include "udp-serv.h" +#define _GNUTLS_E_UNRECOGNIZED_NAME -294 + /* konqueror cannot handle sending the page in multiple * pieces. */ @@ -81,6 +83,8 @@ const char *dh_params_file = NULL; const char *x509_crlfile = NULL; const char *priorities = NULL; const char *status_response_ocsp = NULL; +const char *sni_hostname = NULL; +int sni_hostname_fatal = 0; gnutls_datum_t session_ticket_key; static void tcp_server(const char *name, int port); @@ -312,6 +316,83 @@ int ret; return 0; } +/* callback used to verify if the host name advertised in client hello matches + * the one configured in server + */ +static int +post_client_hello(gnutls_session_t session) +{ + int ret; + /* DNS names (only type supported) may be at most 256 byte long */ + char *name; + size_t len = 256; + unsigned int type; + int i; + + name = malloc(len); + if (name == NULL) + return GNUTLS_E_MEMORY_ERROR; + + for (i=0; ; ) { + ret = gnutls_server_name_get(session, name, &len, &type, i); + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { + char *new_name; + new_name = realloc(name, len); + if (new_name == NULL) { + ret = GNUTLS_E_MEMORY_ERROR; + goto end; + } + name = new_name; + continue; /* retry call with same index */ + } + + /* check if it is the last entry in list */ + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + break; + i++; + if (ret != GNUTLS_E_SUCCESS) + goto end; + /* unknown types need to be ignored */ + if (type != GNUTLS_NAME_DNS) + continue; + + if (strlen(sni_hostname) != len) + continue; + /* API guarantees that the name of type DNS will be null terminated */ + if (!strncmp(name, sni_hostname, len)) { + ret = GNUTLS_E_SUCCESS; + goto end; + } + }; + /* when there is no extension, we can't send the extension specific alert */ + if (i == 0) { + fprintf(stderr, "Warning: client did not include SNI extension, using default host\n"); + ret = GNUTLS_E_SUCCESS; + goto end; + } + + if (sni_hostname_fatal == 1) { + /* abort the connection, propagate error up the stack */ + ret = _GNUTLS_E_UNRECOGNIZED_NAME; + goto end; + } + + fprintf(stderr, "Warning: client provided unrecognized host name\n"); + /* since we just want to send an alert, not abort the connection, we + * need to send it ourselves + */ + do { + ret = gnutls_alert_send(session, + GNUTLS_AL_WARNING, + GNUTLS_A_UNRECOGNIZED_NAME); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + /* continue handshake, fall through */ +end: + free(name); + return ret; +} + gnutls_session_t initialize_session(int dtls) { gnutls_session_t session; @@ -343,6 +424,10 @@ gnutls_session_t initialize_session(int dtls) &session_ticket_key); #endif + if (sni_hostname != NULL) + gnutls_handshake_set_post_client_hello_function(session, + &post_client_hello); + if (gnutls_priority_set_direct(session, priorities, &err) < 0) { fprintf(stderr, "Syntax error at: %s\n", err); exit(1); @@ -1629,6 +1714,12 @@ static void cmd_parser(int argc, char **argv) if (HAVE_OPT(OCSP_RESPONSE)) status_response_ocsp = OPT_ARG(OCSP_RESPONSE); + if (HAVE_OPT(SNI_HOSTNAME)) + sni_hostname = OPT_ARG(SNI_HOSTNAME); + + if (HAVE_OPT(SNI_HOSTNAME_FATAL)) + sni_hostname_fatal = 1; + } /* session resuming support */ -- 2.14.3