diff options
| author | Sam Varshavchik | 2021-02-20 21:07:50 -0500 |
|---|---|---|
| committer | Sam Varshavchik | 2021-02-20 21:07:50 -0500 |
| commit | 7e9140305a0652e86d18e8dcf71fc07748ce8e4d (patch) | |
| tree | 6b31f077fc1f3dc8a557c841cedffc6c917e509f | |
| parent | 4f8680c108d2ca32611dd60345cb0553a2ebd82e (diff) | |
| download | courier-libs-7e9140305a0652e86d18e8dcf71fc07748ce8e4d.tar.bz2 | |
Add support for ALPN.
| -rw-r--r-- | imap/imapd-ssl.dist.in.git | 9 | ||||
| -rw-r--r-- | imap/pop3d-ssl.dist.in.git | 7 | ||||
| -rw-r--r-- | tcpd/configure.ac | 10 | ||||
| -rw-r--r-- | tcpd/libcouriergnutls.c | 184 | ||||
| -rw-r--r-- | tcpd/libcouriertls.c | 228 |
5 files changed, 436 insertions, 2 deletions
diff --git a/imap/imapd-ssl.dist.in.git b/imap/imapd-ssl.dist.in.git index 35880c8..f6b1ae8 100644 --- a/imap/imapd-ssl.dist.in.git +++ b/imap/imapd-ssl.dist.in.git @@ -5,7 +5,7 @@ # Do not alter lines that begin with ##, they are used when upgrading # this configuration. # -# Copyright 2000 - 2019 Double Precision, Inc. See COPYING for +# Copyright 2000 - 2021 Double Precision, Inc. See COPYING for # distribution information. # # This configuration file sets various options for the Courier-IMAP server @@ -316,6 +316,13 @@ TLS_VERIFYPEER=NONE TLS_CACHEFILE=@localstatedir@/couriersslpop3cache TLS_CACHESIZE=524288 +##TLS_ALPN:0 +# +# Application level protocol negotiation should be enabled by default, and +# should be commented out only in case of compatibility issues. + +TLS_ALPN=imap + ##NAME: MAILDIRPATH:0 # # MAILDIRPATH - directory name of the maildir directory. diff --git a/imap/pop3d-ssl.dist.in.git b/imap/pop3d-ssl.dist.in.git index dd352cd..40508e1 100644 --- a/imap/pop3d-ssl.dist.in.git +++ b/imap/pop3d-ssl.dist.in.git @@ -313,6 +313,13 @@ TLS_VERIFYPEER=NONE TLS_CACHEFILE=@localstatedir@/couriersslimapcache TLS_CACHESIZE=524288 +##TLS_ALPN:0 +# +# Application level protocol negotiation should be enabled by default, and +# should be commented out only in case of compatibility issues. + +TLS_ALPN=pop3 + ##NAME: MAILDIRPATH:0 # # MAILDIRPATH - directory name of the maildir directory. diff --git a/tcpd/configure.ac b/tcpd/configure.ac index 0aa6f8a..c3bb9cb 100644 --- a/tcpd/configure.ac +++ b/tcpd/configure.ac @@ -318,6 +318,11 @@ if test "$have_ssl" = "yes" then LIBCOURIERTLSOPENSSL=libcouriertlsopenssl.la + AC_CHECK_LIB(ssl, SSL_CTX_set_alpn_protos, [ + AC_DEFINE_UNQUOTED(HAVE_OPENSSL_ALPN, 1, + [ Whether OpenSSL supports ALPN ]) + ]) + if test "$KRB5CONFIG" != "krb5-config" then AC_MSG_CHECKING(whether OpenSSL requires Kerberos) @@ -394,6 +399,11 @@ then gnutlsdep="`$PKGCONFIG --libs gnutls`" AC_DEFINE([HAVE_GNUTLS3], [1], [Use GnuTLS3]) fi + + AC_CHECK_LIB(gnutls, gnutls_alpn_set_protocols, [ + AC_DEFINE_UNQUOTED(HAVE_GNUTLS_ALPN, 1, + [ Whether OpenSSL supports ALPN ]) + ]) fi AC_CHECK_SIZEOF(gnutls_transport_ptr_t,0, [ diff --git a/tcpd/libcouriergnutls.c b/tcpd/libcouriergnutls.c index 1b4bfe2..18ea01c 100644 --- a/tcpd/libcouriergnutls.c +++ b/tcpd/libcouriergnutls.c @@ -161,6 +161,23 @@ static void nonsslerror(struct tls_info *info, const char *pfix) (*info->tls_err_msg)(errmsg, info->app_data); } +#if HAVE_GNUTLS_ALPN + +static void sslerror(ssl_context ctx, int errcode, const char *pfix) +{ + char errmsg[512]; + const char *err; + + strcat(strcpy(errmsg, pfix), ": "); + + err=gnutls_strerror(errcode); + + strncat(errmsg, err, sizeof(errmsg)-1-strlen(errmsg)); + errmsg[sizeof(errmsg)-1]=0; + (*ctx->info_cpy.tls_err_msg)(errmsg, ctx->info_cpy.app_data); +} +#endif + static const char *safe_getenv(ssl_context context, const char *n, const char *def) { @@ -722,6 +739,154 @@ static int verify_client(ssl_handle ssl, int fd) return 0; } +#if HAVE_GNUTLS_ALPN +/* +** Parse the TLS_ALPN setting, repeatedly invoking a callback with: +** 1) size of the protocol string +** 2) the protocol string +** 3) pass-through parameter. +*/ + +static gnutls_alpn_flags_t alpn_parse(const char *alpn, + void (*cb)(unsigned char l, + const char *p, + void *arg), + void *arg) +{ + gnutls_alpn_flags_t flags=GNUTLS_ALPN_MANDATORY; + + while (alpn && *alpn) + { + const char *p; + unsigned char l; + + /* Commas and spaces are delimiters here */ + + if (*alpn==',' || isspace(*alpn)) + { + ++alpn; + continue; + } + + /* Look for the next comma, spaces, or end of string */ + p=alpn; + + while (*alpn && *alpn != ',' && !isspace(*alpn)) + ++alpn; + + /* + ** We now have the character count and the string. + ** Defend against a bad setting by checking for overflow. + */ + l=alpn - p; + + if (l != alpn - p) + continue; + + if (*p != '@') + (*cb)(l, p, arg); + else + { + if (l > 1) + switch (p[1]) { + case 'o': + case 'O': + flags &= GNUTLS_ALPN_MANDATORY; + break; + case 's': + case 'S': + flags |= GNUTLS_ALPN_SERVER_PRECEDENCE; + break; + } + } + } + + return flags; +} + +static void alpn_count(unsigned char l, + const char *p, + void *arg) +{ + ++*(unsigned *)arg; +} + +static void alpn_save(unsigned char l, + const char *name, + void *arg) +{ + gnutls_datum_t **p=(gnutls_datum_t **)arg; + + (*p)->data=(unsigned char *)name; + (*p)->size=l; + ++*p; +} + +static int alpn_set(ssl_context ctx, + gnutls_session_t session) +{ + const char *p=safe_getenv(ctx, "TLS_ALPN", ""); + unsigned n=0; + gnutls_datum_t *datums; + gnutls_datum_t *datum_ptr; + gnutls_alpn_flags_t flags; + int ret; + + alpn_parse(p, alpn_count, &n); + + if (n == 0) + return GNUTLS_E_SUCCESS; + + if ((datums=(gnutls_datum_t *)malloc(n * sizeof(gnutls_datum_t))) + == NULL) + { + return GNUTLS_E_SUCCESS; + } + + datum_ptr=datums; + + flags=alpn_parse(p, alpn_save, &datum_ptr); + + ret=gnutls_alpn_set_protocols(session, datums, n, flags); + free((char *)datums); + + return ret; +} + +#if 0 +static int verify_alpn(ssl_handle ssl) +{ + const char *p=safe_getenv(ssl->ctx, "TLS_ALPN", ""); + unsigned n=0; + + gnutls_datum_t protocol; + int rc; + + alpn_parse(p, alpn_count, &n); + + rc=gnutls_alpn_get_selected_protocol(ssl->session, &protocol); + if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + { + fprintf(stderr, "Not negotiated\n"); + + return 0; + } + if (rc != GNUTLS_E_SUCCESS) + { + fprintf(stderr, "ERROR: %d %s\n", + rc, gnutls_strerror(rc)); + return 0; + } + + fprintf(stderr, "Negotiated "); + fwrite(protocol.data, protocol.size, 1, stderr); + fprintf(stderr, "\n"); + + return 0; +} +#endif +#endif + static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w) { int rc; @@ -734,10 +899,13 @@ static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w) { ssl->info_cpy.connect_interrupted=0; - if (verify_client(ssl, fd)) return -1; +#if 0 + if (verify_alpn(ssl)) + return -1; +#endif if (ssl->info_cpy.connect_callback != NULL && !(*ssl->info_cpy.connect_callback)(ssl, ssl->info_cpy.app_data)) @@ -1537,6 +1705,20 @@ RT | return NULL; } +#if HAVE_GNUTLS_ALPN + { + int result_rc; + + result_rc=alpn_set(ctx, ssl->session); + + if (result_rc != GNUTLS_E_SUCCESS) + { + sslerror(ctx, result_rc, "gnutls_alpn_set_protocols"); + tls_free_session(ssl); + return (NULL); + } + } +#endif if (ssl->dhparams_initialized) { gnutls_certificate_set_dh_params(ssl->xcred, ssl->dhparams); diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c index 874a0d1..39c7d49 100644 --- a/tcpd/libcouriertls.c +++ b/tcpd/libcouriertls.c @@ -732,6 +732,203 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg) return SSL_TLSEXT_ERR_OK; } +#if HAVE_OPENSSL_ALPN + +/* +** Parse the TLS_ALPN setting, repeatedly invoking a callback with: +** 1) size of the protocol string +** 2) the protocol string +** 3) pass-through parameter. +*/ + +static void alpn_parse(const char *alpn, + void (*cb)(unsigned char l, + const char *p, + void *arg), + void *arg) +{ + while (alpn && *alpn) + { + const char *p; + unsigned char l; + + /* Commas and spaces are delimiters here */ + + if (*alpn==',' || isspace(*alpn)) + { + ++alpn; + continue; + } + + /* Look for the next comma, spaces, or end of string */ + p=alpn; + + while (*alpn && *alpn != ',' && !isspace(*alpn)) + ++alpn; + + /* + ** We now have the character count and the string. + ** Defend against a bad setting by checking for overflow. + */ + l=alpn - p; + + if (l != alpn - p) + continue; + + if (*p != '@') /* Used in the gnutls version */ + (*cb)(l, p, arg); + } +} + +struct tls_avpn_to_protocol_list_info { + void (*cb)(const char *, unsigned int, void *); + void *arg; +}; + +/* +** Take the result of alpn_parse and construct a character stream, in ALPN +** protocol-list format. +*/ + +static void tls_avpn_to_protocol_list_cb(unsigned char l, + const char *p, + void *arg) +{ + struct tls_avpn_to_protocol_list_info *ptr= + (struct tls_avpn_to_protocol_list_info *)arg; + + (*ptr->cb)((char *)&l, 1, ptr->arg); + (*ptr->cb)(p, l, ptr->arg); +} + +static void tls_avpn_to_protocol_list(const char *alpn, + void (*cb)(const char *, unsigned int, + void *), + void *arg) +{ + struct tls_avpn_to_protocol_list_info info; + + info.cb=cb; + info.arg=arg; + + alpn_parse(alpn, tls_avpn_to_protocol_list_cb, &info); +} + +/* +** Create a discrete unsigned char buffer with the ALPN protocol-list +** string. +** +** First we use tls_avpn_to_protocol_list to count the size of the string, +** then malloc it, then create it for real-sies. +*/ + +struct alpn_info { + unsigned char *p; + unsigned int l; +}; + +static void alpn_proto_count(const char *ptr, unsigned int n, + void *arg) +{ + struct alpn_info *info=(struct alpn_info *)arg; + + info->l += n; +} + +static void alpn_proto_save(const char *ptr, unsigned int n, + void *arg) +{ + struct alpn_info *info=(struct alpn_info *)arg; + + memcpy(info->p, ptr, n); + info->p+=n; +} + +static void parse_tls_alpn(const struct tls_info *info, + struct alpn_info *alpn) +{ + const char *s=safe_getenv(info, "TLS_ALPN"); + unsigned char *buffer; + + alpn->p=NULL; + alpn->l=0; + + tls_avpn_to_protocol_list(s, alpn_proto_count, alpn); + + if (alpn->l==0) + return; + + buffer=(unsigned char *)malloc(alpn->l); + + if (!buffer) + { + nonsslerror(info, "malloc"); + exit(1); + } + alpn->p=buffer; + tls_avpn_to_protocol_list(s, alpn_proto_save, alpn); + alpn->p=buffer; +} + +struct tls_alpn_server_info { + const unsigned char *in; + unsigned char inl; + int found; +}; + +static void alpn_proto_search(unsigned char l, + const char *p, + void *arg) +{ + struct tls_alpn_server_info *info= + (struct tls_alpn_server_info *)arg; + + if (info->inl == l && + memcmp(info->in, p, l) == 0) + { + info->found=1; + } +} + +static int tls_alpn_server_cb(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); + const char *s=safe_getenv(info, "TLS_ALPN"); + + while (inlen) + { + struct tls_alpn_server_info search_info; + + if (*in >= inlen || *in == 0) /* Won't assume */ + return SSL_TLSEXT_ERR_ALERT_FATAL; + + search_info.in=in+1; + search_info.inl=*in; + search_info.found=0; + + alpn_parse(s, alpn_proto_search, &search_info); + if (search_info.found) + { + *outlen=search_info.inl; + *out=search_info.in; + return SSL_TLSEXT_ERR_OK; + } + + inlen -= *in; + --inlen; + in += *in; + ++in; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; +} + +#endif + SSL_CTX *tls_create(int isserver, const struct tls_info *info) { return tls_create_int(isserver, info, 0); @@ -966,10 +1163,41 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info, if (isserver) { +#if HAVE_OPENSSL_ALPN + struct alpn_info alpn; + + parse_tls_alpn(info, &alpn); + + if (alpn.p) + { + free((char *)alpn.p); + + SSL_CTX_set_alpn_select_cb(ctx, tls_alpn_server_cb, + NULL); + } +#endif SSL_CTX_set_tlsext_servername_callback(ctx, server_cert_cb); } else { +#if HAVE_OPENSSL_ALPN + struct alpn_info alpn; + + parse_tls_alpn(info, &alpn); + + if (alpn.p) + { + int ret=SSL_CTX_set_alpn_protos(ctx, alpn.p, alpn.l); + free((char *)alpn.p); + + if (ret) + { + sslerror(info, "SSL_CTX_set_alpn_protos", -1); + tls_destroy(ctx); + return (NULL); + } + } +#endif SSL_CTX_set_client_cert_cb(ctx, client_cert_cb); } return (ctx); |
