diff options
Diffstat (limited to 'socket.c')
-rw-r--r-- | socket.c | 232 |
1 files changed, 174 insertions, 58 deletions
@@ -2,7 +2,7 @@ * socket.c -- socket library functions * * Copyright 1998 - 2004 by Eric S. Raymond. - * Copyright 2004 - 2020 by Matthias Andree. + * Copyright 2004 - 2023 by Matthias Andree. * Contributions by Alexander Bluhm, Earl Chew, John Beck. * For license terms, see the file COPYING in this directory. @@ -82,7 +82,20 @@ extern int h_errno; #endif /* ndef h_errno */ #ifdef HAVE_SOCKETPAIR -static char *const *parse_plugin(const char *plugin, const char *host, const char *service) +static void free_plugindata(char **argvec) +{ + if (argvec) { + xfree(*argvec); + xfree(argvec); + } +} + +/** parse plugin and interpolate %h and %p with single-quoted host and service. + * Returns a malloc()ed pointer to a NULL-terminated vector of pointers, of + * which the first is also malloc()ed and the 2nd and later ones (if present) + * are pointers into the same memory region - these serve as input for the + * argument vector of execvp() in handle_plugin. */ +static char **parse_plugin(const char *plugin, const char *host, const char *service) { char **argvec; const char *c, *p; @@ -106,12 +119,7 @@ static char *const *parse_plugin(const char *plugin, const char *host, const cha /* we need to discount 2 bytes for each placeholder */ plugin_copy_len = plugin_len + (host_len - 2) * host_count + (service_len - 2) * service_count; - plugin_copy = (char *)malloc(plugin_copy_len + 1); - if (!plugin_copy) - { - report(stderr, GT_("fetchmail: malloc failed\n")); - return NULL; - } + plugin_copy = (char *)xmalloc(plugin_copy_len + 1); while (plugin_offset < plugin_len && plugin_copy_offset < plugin_copy_len) { if ((plugin[plugin_offset] == '%') && (plugin[plugin_offset + 1] == 'h')) @@ -142,6 +150,7 @@ static char *const *parse_plugin(const char *plugin, const char *host, const cha return NULL; } memset(argvec, 0, vecsiz); + argvec[0] = plugin_copy; /* make sure we can free() it in every case */ for (p = cp = plugin_copy, i = 0; *cp; cp++) { if ((!isspace((unsigned char)*cp)) && (cp == p ? 1 : isspace((unsigned char)*p))) { argvec[i] = cp; @@ -161,21 +170,29 @@ static int handle_plugin(const char *host, /* get a socket mediated through a given external command */ { int fds[2]; - char *const *argvec; + char **argvec; /* * The author of this code, Felix von Leitner <felix@convergence.de>, says: * he chose socketpair() instead of pipe() because socketpair creates * bidirectional sockets while allegedly some pipe() implementations don't. */ + argvec = parse_plugin(plugin,host,service); + if (!argvec || !*argvec[0]) { + free_plugindata(argvec); + report(stderr, GT_("fetchmail: plugin for host %s service %s is empty, cannot run!\n"), host, service); + return -1; + } if (socketpair(AF_UNIX,SOCK_STREAM,0,fds)) { report(stderr, GT_("fetchmail: socketpair failed\n")); + free_plugindata(argvec); return -1; } switch (fork()) { case -1: /* error */ + free_plugindata(argvec); report(stderr, GT_("fetchmail: fork failed\n")); return -1; case 0: /* child */ @@ -190,15 +207,12 @@ static int handle_plugin(const char *host, (void) close(fds[0]); if (outlevel >= O_VERBOSE) report(stderr, GT_("running %s (host %s service %s)\n"), plugin, host, service); - argvec = parse_plugin(plugin,host,service); - if (argvec == NULL) - _exit(EXIT_FAILURE); execvp(*argvec, argvec); report(stderr, GT_("execvp(%s) failed\n"), *argvec); _exit(EXIT_FAILURE); break; default: /* parent */ - /* NOP */ + free_plugindata(argvec); break; } /* fds[0] is the child's end; close it for proper EOF detection */ @@ -386,31 +400,55 @@ va_dcl { #define OPENSSL_NO_DEPRECATED 23 #endif #include "tls-aux.h" -#include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/pem.h> #include <openssl/x509v3.h> #include <openssl/rand.h> -#define fm_MIN_OPENSSL_VER 0x1000200fL - -#ifdef LIBRESSL_VERSION_NUMBER -#pragma message "WARNING - LibreSSL is unsupported. Use at your own risk." -#endif - -#if OPENSSL_VERSION_NUMBER < 0x1010100fL -#pragma message "WARNING - OpenSSL SHOULD be at least version 1.1.1." +#ifdef LIBRESSL_VERSION_NUMBER +# ifdef __OpenBSD__ +# pragma message "WARNING - Linking against LibreSSL, which is not a supported configuration." +# else +# error "FAILED - LibreSSL cannot be used legally, for lack of GPL clause 2b exception, see COPYING." +# endif #endif -#if OPENSSL_VERSION_NUMBER < fm_MIN_OPENSSL_VER -#error Your OpenSSL version must be at least 1.0.2 release. Older OpenSSL versions are unsupported. -#else +#ifdef USING_WOLFSSL +# if LIBWOLFSSL_VERSION_HEX < 0x05006002L +# error "FAILED - wolfSSL MUST be at least version 5.6.2. You have " LIBWOLFSSL_VERSION_STRING "." +# endif +# if LIBWOLFSSL_VERSION_HEX < 0x05006006L +# pragma message "WARNING - wolfSSL SHOULD be at least version 5.6.6. You have " LIBWOLFSSL_VERSION_STRING "." +# endif +#else /* !USING_WOLFSSL */ +#define fm_MIN_OPENSSL_VER 0x1000215fL /* 1.0.2u */ +# if OPENSSL_VERSION_NUMBER < 0x1010115fL +# pragma message "WARNING - OpenSSL 1.m.nx SHOULD be at least release version 1.1.1u, using " OPENSSL_VERSION_TEXT "." +# endif /* 0xMNN00PPSL */ +/* do not warn about OpenSSL 3.2.0, the 3.2.1 fix is of low priority */ +# if OPENSSL_VERSION_NUMBER >= 0x30100000L && OPENSSL_VERSION_NUMBER < 0x30200000L +# if OPENSSL_VERSION_NUMBER < 0x30100040L +# pragma message "WARNING - OpenSSL 3.1.n SHOULD be at least release version 3.1.4, using " OPENSSL_VERSION_TEXT "." +# endif +# endif /* 0xMNN00PPSL */ +# if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_VERSION_NUMBER < 0x30100000L +# if OPENSSL_VERSION_NUMBER < 0x30000090L +# pragma message "WARNING - OpenSSL 3.0.n SHOULD be at least release version 3.0.9, using " OPENSSL_VERSION_TEXT "." +# endif +# endif /* 0xMNN00PPSL */ +# if OPENSSL_VERSION_NUMBER < 0x30000000L +# pragma message "WARNING - OpenSSL before 3.0.0 is end-of-life and unsupported, using " OPENSSL_VERSION_TEXT "." +# endif +# if OPENSSL_VERSION_NUMBER < fm_MIN_OPENSSL_VER +# error Your OpenSSL version MUST be at least 1.0.2u release. Older OpenSSL versions are unsupported. +# else /* OpenSSL too old */ /* #define __fm_ossl_ver(x) #x #define _fm_ossl_ver(x) __fm_ossl_ver(x) #pragma message "Building with OpenSSL headers version " _fm_ossl_ver(OPENSSL_VERSION_NUMBER) ", " OPENSSL_VERSION_TEXT */ -#endif +# endif /* OpenSSL too old */ +#endif /* USING_WOLFSSL */ static void report_SSL_errors(FILE *stream) { @@ -483,7 +521,11 @@ int SockRead(int sock, char *buf, int len) */ #ifdef SSL_ENABLE if( NULL != ( ssl = SSLGetContext( sock ) ) ) { + int e; /* Hack alert! */ + /* XXX FIXME: once we deprecate OpenSSL before 1.1.1, we can + * use SSL_peek_ex() and SSL_read_ex() and simplify this code + * quite a bit */ /* OK... SSL_peek works a little different from MSG_PEEK Problem is that SSL_peek can return 0 if there is no data currently available. If, on the other @@ -495,15 +537,21 @@ int SockRead(int sock, char *buf, int len) loop. This should continue to work even if they later change the behavior of SSL_peek to "fix" this problem... :-( */ - if ((n = SSL_peek(ssl, bp, len)) < 0) { - (void)SSL_get_error(ssl, n); - return(-1); - } - if( 0 == n ) { + if ((n = SSL_peek(ssl, bp, len)) <= 0) { /* SSL_peek says no data... Does he mean no data or did the connection blow up? If we got an error then bail! */ - if (0 != SSL_get_error(ssl, n)) { + e = SSL_get_error(ssl, n); + if (SSL_ERROR_NONE != e +#ifdef USING_WOLFSSL + /* wolfSSL 5.0.0 may return SSL_ERROR_WANT_READ when + * receiving HANDSHAKE instead of app data on SSL_peek + * https://github.com/wolfSSL/wolfssl/issues/4593 */ + && SSL_ERROR_WANT_READ != e +#endif + ) + { + ERR_print_errors_fp(stderr); return -1; } /* We didn't get an error so read at least one @@ -519,8 +567,10 @@ int SockRead(int sock, char *buf, int len) * we must call SSL_get_error to figure if there was * an error or just a "no data" condition */ if ((n = SSL_read(ssl, bp, n)) <= 0) { - if ((n = SSL_get_error(ssl, n))) { - return(-1); + e = SSL_get_error(ssl, n); + if (SSL_ERROR_NONE != e) { + ERR_print_errors_fp(stderr); + return -1; } } /* Check for case where our single character turned out to @@ -570,20 +620,13 @@ int SockPeek(int sock) #ifdef SSL_ENABLE if( NULL != ( ssl = SSLGetContext( sock ) ) ) { n = SSL_peek(ssl, &ch, 1); - if (n < 0) { - (void)SSL_get_error(ssl, n); - return -1; - } - if( 0 == n ) { - /* This code really needs to implement a "hold back" - * to simulate a functioning SSL_peek()... sigh... - * Has to be coordinated with the read code above. - * Next on the list todo... */ - + if (n <= 0) { /* SSL_peek says 0... Does that mean no data or did the connection blow up? If we got an error then bail! */ - if(0 != SSL_get_error(ssl, n)) { + int e = SSL_get_error(ssl, n); + if (SSL_ERROR_NONE != e) { + ERR_print_errors_fp(stderr); return -1; } @@ -628,10 +671,10 @@ SSL *SSLGetContext( int sock ) return _ssl_context[sock]; } -/* ok_return (preverify_ok) is 1 if this stage of certificate verification +/* ok_return is 1 if this stage of certificate verification passed, or 0 if it failed. This callback lets us display informative errors, and perform additional validation (e.g. CN matches) */ -static int SSL_verify_callback(int ok_return, X509_STORE_CTX *ctx, int strict) +static int SSL_verify_callback(int ok_return, X509_STORE_CTX *ctx, const int strict) { #define SSLverbose (((outlevel) >= O_DEBUG) || ((outlevel) >= O_VERBOSE && (depth) == 0)) char buf[257]; @@ -653,7 +696,7 @@ static int SSL_verify_callback(int ok_return, X509_STORE_CTX *ctx, int strict) if (outlevel >= O_DEBUG) { if (SSLverbose) - report(stdout, GT_("SSL verify callback depth %d: preverify_ok == %d, err = %d, %s\n"), + report(stdout, GT_("SSL verify callback depth %d: verify_ok == %d, err = %d, %s\n"), depth, ok_return, err, X509_verify_cert_error_string(err)); } @@ -908,7 +951,7 @@ static const char *SSLCertGetCN(const char *mycert, } #if !defined(OSSL110_API) -/* ===== implementation for OpenSSL 1.0.X and LibreSSL ===== */ +/* ===== implementation for OpenSSL 1.0.X ===== */ static int OSSL10X_proto_version_logic(int sock, const char **myproto, int *avoid_ssl_versions) { if (!*myproto) { @@ -1048,8 +1091,6 @@ static int OSSL110_proto_version_logic(int sock, const char **myproto, report(stderr, GT_("Invalid SSL protocol '%s' specified, using default autoselect (auto).\n"), *myproto); - report(stderr, "fetchmail internal error in OSSL110_proto_version_logic\n"); - abort(); } return 0; } @@ -1067,7 +1108,7 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck struct stat randstat; int i; int avoid_ssl_versions = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - long sslopts = SSL_OP_ALL | SSL_OP_SINGLE_DH_USE; + long sslopts = SSL_OP_ALL; int ssle_connect = 0; long ver; @@ -1078,6 +1119,20 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck #endif ver = OpenSSL_version_num(); /* version switch through tls-aux.h */ +#ifdef USING_WOLFSSL + { char *tmp; + if (NULL != (tmp = getenv("FETCHMAIL_WOLFSSL_DEBUG"))) { + if (*tmp) wolfSSL_Debugging_ON(); + } + } + { + int wver = wolfSSL_lib_version_hex(); + if (wver < LIBWOLFSSL_VERSION_HEX) { + report(stderr, GT_("Loaded wolfSSL library %#lx older than headers %#lx, refusing to work.\n"), (long)wver, (long)(LIBWOLFSSL_VERSION_HEX)); + } + } +#endif + if (ver < OPENSSL_VERSION_NUMBER) { report(stderr, GT_("Loaded OpenSSL library %#lx older than headers %#lx, refusing to work.\n"), (long)ver, (long)(OPENSSL_VERSION_NUMBER)); return -1; @@ -1124,9 +1179,11 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck if(_ctx[sock] == NULL) { unsigned long ec = ERR_peek_last_error(); ERR_print_errors_fp(stderr); +#ifdef SSL_R_NULL_SSL_METHOD_PASSED /* wolfSSL does not define this error */ if (ERR_GET_REASON(ec) == SSL_R_NULL_SSL_METHOD_PASSED) { report(stderr, GT_("Note that some distributions disable older protocol versions in weird non-standard ways. Try a newer protocol version.\n")); } +#endif return(-1); } @@ -1136,7 +1193,9 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck sslopts &= ~ SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; } - SSL_CTX_set_options(_ctx[sock], sslopts | avoid_ssl_versions); + (void)SSL_CTX_set_options(_ctx[sock], sslopts | avoid_ssl_versions); + + (void)SSL_CTX_set_mode(_ctx[sock], SSL_MODE_AUTO_RETRY); if (certck) { SSL_CTX_set_verify(_ctx[sock], SSL_VERIFY_PEER, SSL_ck_verify_callback); @@ -1151,17 +1210,43 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck { char *tmp; int want_default_cacerts = 0; + int r = 1; + const char *l1 = 0, *l2 = 0; /* Load user locations if any is given */ - if (certpath || cacertfile) - SSL_CTX_load_verify_locations(_ctx[sock], + if (certpath || cacertfile) { + l1 = cacertfile; + l2 = certpath; + r = SSL_CTX_load_verify_locations(_ctx[sock], cacertfile, certpath); - else + if (1 != r) goto no_verify_load; + } else { want_default_cacerts = 1; + } tmp = getenv("FETCHMAIL_INCLUDE_DEFAULT_X509_CA_CERTS"); if (want_default_cacerts || (tmp && tmp[0])) { - SSL_CTX_set_default_verify_paths(_ctx[sock]); +#ifdef USING_WOLFSSL + /* wolfSSL 5.0.0 does not implement + * SSL_CTX_set_default_verify_paths(). Use something + * else: */ + const char *tmp = WOLFSSL_TRUST_FILE; + l1 = tmp; l2=NULL; + if (*tmp) + r = SSL_CTX_load_verify_locations(_ctx[sock], + tmp, NULL); +#else + r = SSL_CTX_set_default_verify_paths(_ctx[sock]); + if (1 != r) goto no_verify_load; +#endif + } + + if (1 != r) { +no_verify_load: + report(stderr, GT_("Cannot load verify locations (file=\"%s\", dir=\"%s\"), error %d:\n"), + l1?l1:"(null)", l2?l2:"(null)", r); + ERR_print_errors_fp(stderr); + return -1; } } @@ -1198,7 +1283,22 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck } } - /* OpenSSL >= 1.0.2: set host name for verification */ +#ifdef USING_WOLFSSL + { + /* workaround for WolfSSL 5.0.0 compatibility issue, + * which leaves errors in the X509 ctx passed to the + * SSL_verify_callback() in a preverify_ok==1 case, + * where OpenSSL will not return an error. + * https://github.com/wolfSSL/wolfssl/issues/4592 */ + int r = wolfSSL_check_domain_name(_ssl_context[sock], servercname); + if (WOLFSSL_SUCCESS != r) { + report(stderr, GT_("fetchmail: sock %d: wolfSSL_check_domain_name(%#p, \"%s\") returned %d, trying to continue\n"), + sock, _ssl_context[sock], servercname, r); + } + } +#else + /* set host name for verification, only available since OpenSSL 1.0.2 + * */ /* XXX FIXME: do we need to change the function's signature and pass the akalist to * permit the other hostnames through SSL? */ /* https://wiki.openssl.org/index.php/Hostname_validation */ @@ -1212,7 +1312,23 @@ int SSLOpen(int sock, char *mycert, char *mykey, const char *myproto, int certck (void *)_ssl_context[sock], servercname, r); ERR_print_errors_fp(stderr); } + + /* OpenSSL 1.x.y: 0xMNNFFPPSL: major minor fix patch status + * OpenSSL 3.0.z: 0xMNN00PPSL: synthesized */ + /* 0xMNNFFPPsL 0xMNNFFPPsL */ +#if (OPENSSL_VERSION_NUMBER & 0xfffff000L) == 0x10002000L +#pragma message "enabling OpenSSL 1.0.2 X509_V_FLAG_TRUSTED_FIRST flag setter" + /* OpenSSL 1.0.2 and 1.0.2 only: + * work around Let's Encrypt Cross-Signing Certificate Expiry, + * https://www.openssl.org/blog/blog/2021/09/13/LetsEncryptRootCertExpire/ + * Workaround #2 */ + X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_TRUSTED_FIRST); +#endif + + /* param is a pointer to internal OpenSSL data, must not be freed, + * and just goes out of scope */ } +#endif if( mycert || mykey ) { |