aboutsummaryrefslogtreecommitdiffstats
path: root/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'socket.c')
-rw-r--r--socket.c232
1 files changed, 174 insertions, 58 deletions
diff --git a/socket.c b/socket.c
index 326dc9cb..c98e1552 100644
--- a/socket.c
+++ b/socket.c
@@ -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 ) {