#include <stdio.h>
#include <errno.h>
#include <string.h>
+#include <ctype.h> /* isspace() */
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif /* HAVE_MEMORY_H */
#else
#include <net/socket.h>
#endif
+#include <sys/un.h>
#include <netinet/in.h>
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif /* NET_SECURITY */
#ifdef HAVE_SOCKETPAIR
+char *const *parse_plugin(const char *plugin, const char *host, const char *service)
+{ const char **argvec;
+ const char *c, *p;
+ char *cp, *plugin_copy;
+ unsigned int plugin_copy_len;
+ unsigned int plugin_offset = 0, plugin_copy_offset = 0;
+ unsigned int i, s = 2 * sizeof(char*), host_count = 0, service_count = 0;
+ unsigned int plugin_len = strlen(plugin);
+ unsigned int host_len = strlen(host);
+ unsigned int service_len = strlen(service);
+
+ for (c = p = plugin; *c; c++)
+ { if (isspace(*c) && !isspace(*p))
+ s += sizeof(char*);
+ if (*p == '%' && *c == 'h')
+ host_count++;
+ if (*p == '%' && *c == 'p')
+ service_count++;
+ p = c;
+ }
+
+ plugin_copy_len = plugin_len + host_len * host_count + service_len * service_count;
+ plugin_copy = malloc(plugin_copy_len + 1);
+ if (!plugin_copy)
+ {
+ report(stderr, GT_("fetchmail: malloc failed\n"));
+ return NULL;
+ }
+
+ while (plugin_copy_offset < plugin_copy_len)
+ { if ((plugin[plugin_offset] == '%') && (plugin[plugin_offset + 1] == 'h'))
+ { strcpy(plugin_copy + plugin_copy_offset, host);
+ plugin_offset += 2;
+ plugin_copy_offset += host_len;
+ }
+ else if ((plugin[plugin_offset] == '%') && (plugin[plugin_offset + 1] == 'p'))
+ { strcpy(plugin_copy + plugin_copy_offset, service);
+ plugin_offset += 2;
+ plugin_copy_offset += service_len;
+ }
+ else
+ { plugin_copy[plugin_copy_offset] = plugin[plugin_offset];
+ plugin_offset++;
+ plugin_copy_offset++;
+ }
+ }
+ plugin_copy[plugin_copy_len] = 0;
+
+ argvec = malloc(s);
+ if (!argvec)
+ {
+ report(stderr, GT_("fetchmail: malloc failed\n"));
+ return NULL;
+ }
+ memset(argvec, 0, s);
+ for (c = p = plugin_copy, i = 0; *c; c++)
+ { if ((!isspace(*c)) && (c == p ? 1 : isspace(*p))) {
+ argvec[i] = c;
+ i++;
+ }
+ p = c;
+ }
+ for (cp = plugin_copy; *cp; cp++)
+ { if (isspace(*cp))
+ *cp = 0;
+ }
+ return (char *const*)argvec;
+}
+
static int handle_plugin(const char *host,
const char *service, const char *plugin)
/* get a socket mediated through a given external command */
{
int fds[2];
+ char *const *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.
+ */
if (socketpair(AF_UNIX,SOCK_STREAM,0,fds))
{
- report(stderr, _("fetchmail: socketpair failed\n"));
+ report(stderr, GT_("fetchmail: socketpair failed\n"));
return -1;
}
switch (fork()) {
case -1:
/* error */
- report(stderr, _("fetchmail: fork failed\n"));
+ report(stderr, GT_("fetchmail: fork failed\n"));
return -1;
break;
case 0: /* child */
** detection */
(void) close(fds[1]);
if ( (dup2(fds[0],0) == -1) || (dup2(fds[0],1) == -1) ) {
- report(stderr, _("dup2 failed\n"));
+ report(stderr, GT_("dup2 failed\n"));
exit(1);
}
/* fds[0] is now connected to 0 and 1; close it */
(void) close(fds[0]);
if (outlevel >= O_VERBOSE)
- report(stderr, _("running %s %s %s\n"), plugin, host, service);
- execlp(plugin,plugin,host,service,0);
- report(stderr, _("execl(%s) failed\n"), plugin);
+ report(stderr, GT_("running %s (host %s service %s)\n"), plugin, host, service);
+ argvec = parse_plugin(plugin,host,service);
+ execvp(*argvec, argvec);
+ report(stderr, GT_("execvp(%s) failed\n"), *argvec);
exit(0);
break;
default: /* parent */
}
#endif /* __UNUSED__ */
+int UnixOpen(const char *path)
+{
+ int sock = -1;
+ struct sockaddr_un ad;
+ memset(&ad, 0, sizeof(ad));
+ ad.sun_family = AF_UNIX;
+ strncpy(ad.sun_path, path, sizeof(ad.sun_path)-1);
+
+ sock = socket( AF_UNIX, SOCK_STREAM, 0 );
+ if (sock < 0)
+ {
+ h_errno = 0;
+ return -1;
+ }
+ if (connect(sock, (struct sockaddr *) &ad, sizeof(ad)) < 0)
+ {
+ int olderr = errno;
+ fm_close(sock); /* don't use SockClose, no traffic yet */
+ h_errno = 0;
+ errno = olderr;
+ return -1;
+ }
+ return sock;
+}
+
#if INET6_ENABLE
int SockOpen(const char *host, const char *service, const char *options,
const char *plugin)
req.ai_socktype = SOCK_STREAM;
if (getaddrinfo(host, service, &req, &ai0)) {
- report(stderr, _("fetchmail: getaddrinfo(%s.%s)\n"), host,service);
+ report(stderr, GT_("fetchmail: getaddrinfo(%s.%s)\n"), host,service);
return -1;
}
#ifdef HAVE_SOCKETPAIR
if (plugin) {
char buf[10];
- sprintf(buf,"%d",clientPort);
+#ifdef HAVE_SNPRINTF
+ snprintf(buf, sizeof(buf), /* Yeah, paranoic. So what? :P */
+#else
+ sprintf(buf,
+#endif /* HAVE_SNPRINTF */
+ "%d",clientPort);
return handle_plugin(host,buf,plugin);
}
#endif /* HAVE_SOCKETPAIR */
/* we'll accept a quad address */
#ifndef HAVE_INET_ATON
- inaddr = inet_addr(host);
+ inaddr = inet_addr((char*)host);
if (inaddr != INADDR_NONE)
{
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
}
#endif /* HAVE_INET_ATON */
else {
- hp = gethostbyname(host);
+ hp = gethostbyname((char*)host);
if (hp == NULL)
{
{
h_errno = errno = 0;
report(stderr,
- _("fetchmail: illegal address length received for host %s\n"),host);
+ GT_("fetchmail: illegal address length received for host %s\n"),host);
return -1;
}
/*
}
#ifdef SSL_ENABLE
-#include "ssl.h"
-#include "err.h"
-#include "pem.h"
-#include "x509.h"
+#include "openssl/ssl.h"
+#include "openssl/err.h"
+#include "openssl/pem.h"
+#include "openssl/x509.h"
static SSL_CTX *_ctx = NULL;
static SSL *_ssl_context[FD_SETSIZE];
#ifdef SSL_ENABLE
static char *_ssl_server_cname = NULL;
+static int _check_fp;
+static char *_check_digest;
+static char *_server_label;
+static int _depth0ck;
SSL *SSLGetContext( int sock )
{
}
-int SSL_verify_callback( int ok_return, X509_STORE_CTX *ctx )
+int SSL_verify_callback( int ok_return, X509_STORE_CTX *ctx, int strict )
{
- char buf[260];
- char cbuf[260];
- char ibuf[260];
- char *str_ptr;
+ char buf[257];
X509 *x509_cert;
int err, depth;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ char text[EVP_MAX_MD_SIZE * 3 + 1], *tp, *te;
+ EVP_MD *digest_tp;
+ unsigned int dsz, i, esz;
+ X509_NAME *subj, *issuer;
x509_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
- X509_NAME_oneline(X509_get_subject_name(x509_cert), buf, 256);
- X509_NAME_oneline(X509_get_issuer_name(x509_cert), ibuf, 256);
-
- /* Just to be sure those buffers are terminated... I think the
- X509 libraries do, but... */
- buf[256] = ibuf[256] = '\0';
+ subj = X509_get_subject_name(x509_cert);
+ issuer = X509_get_issuer_name(x509_cert);
if (depth == 0) {
- if( ( str_ptr = strstr( ibuf, "/O=" ) ) ) {
- str_ptr += 3;
- strcpy( cbuf, str_ptr );
- if( ( str_ptr = strchr(cbuf, '/' ) ) ) {
- *str_ptr = '\0';
- }
- if (outlevel == O_VERBOSE)
- report(stdout, "Issuer Organization: %s\n", cbuf );
- } else {
- if (outlevel == O_VERBOSE)
- report(stdout, "Unknown Organization\n", cbuf );
+ _depth0ck = 1;
+
+ if (outlevel == O_VERBOSE) {
+ if ((i = X509_NAME_get_text_by_NID(issuer, NID_organizationName, buf, sizeof(buf))) != -1) {
+ report(stdout, GT_("Issuer Organization: %s\n"), buf);
+ if (i >= sizeof(buf) - 1)
+ report(stdout, GT_("Warning: Issuer Organization Name too long (possibly truncated).\n"));
+ } else
+ report(stdout, GT_("Unknown Organization\n"));
+ if ((i = X509_NAME_get_text_by_NID(issuer, NID_commonName, buf, sizeof(buf))) != -1) {
+ report(stdout, GT_("Issuer CommonName: %s\n"), buf);
+ if (i >= sizeof(buf) - 1)
+ report(stdout, GT_("Warning: Issuer CommonName too long (possibly truncated).\n"));
+ } else
+ report(stdout, GT_("Unknown Issuer CommonName\n"));
}
- if( ( str_ptr = strstr( ibuf, "/CN=" ) ) ) {
- str_ptr += 4;
- strcpy( cbuf, str_ptr );
- if( ( str_ptr = strchr(cbuf, '/' ) ) ) {
- *str_ptr = '\0';
- }
+ if ((i = X509_NAME_get_text_by_NID(subj, NID_commonName, buf, sizeof(buf))) != -1) {
if (outlevel == O_VERBOSE)
- report(stdout, "Issuer CommonName: %s\n", cbuf );
+ report(stdout, GT_("Server CommonName: %s\n"), buf);
+ if (i >= sizeof(buf) - 1) {
+ /* Possible truncation. In this case, this is a DNS name, so this
+ * is really bad. We do not tolerate this even in the non-strict case. */
+ report(stderr, GT_("Bad certificate: Subject CommonName too long!\n"));
+ return (0);
+ }
+ if (_ssl_server_cname != NULL) {
+ char *p1 = buf;
+ char *p2 = _ssl_server_cname;
+ int n;
+
+ if (*p1 == '*') {
+ ++p1;
+ n = strlen(p2) - strlen(p1);
+ if (n >= 0)
+ p2 += n;
+ }
+ if (0 != strcasecmp(p1, p2)) {
+ report(stderr,
+ GT_("Server CommonName mismatch: %s != %s\n"),
+ buf, _ssl_server_cname );
+ if (ok_return && strict)
+ return (0);
+ }
+ } else if (ok_return && strict) {
+ report(stderr, GT_("Server name not set, could not verify certificate!\n"));
+ return (0);
+ }
} else {
if (outlevel == O_VERBOSE)
- report(stdout, "Unknown Issuer CommonName\n", cbuf );
+ report(stdout, GT_("Unknown Server CommonName\n"));
+ if (ok_return && strict) {
+ report(stderr, GT_("Server name not specified in certificate!\n"));
+ return (0);
+ }
}
- if( ( str_ptr = strstr( buf, "/CN=" ) ) ) {
- str_ptr += 4;
- strcpy( cbuf, str_ptr );
- if( ( str_ptr = strchr(cbuf, '/' ) ) ) {
- *str_ptr = '\0';
+ /* Print the finger print. Note that on errors, we might print it more than once
+ * normally; we kluge around that by using a global variable. */
+ if (_check_fp) {
+ _check_fp = 0;
+ digest_tp = EVP_md5();
+ if (digest_tp == NULL) {
+ report(stderr, GT_("EVP_md5() failed!\n"));
+ return (0);
}
- if (outlevel == O_VERBOSE)
- report(stdout, "Server CommonName: %s\n", cbuf );
- /* Should we have some wildcarding here? */
- if ( NULL != _ssl_server_cname
- && 0 != strcmp( cbuf, _ssl_server_cname ) ) {
- report(stdout,
- "Server CommonName mismatch: %s != %s\n",
- cbuf, _ssl_server_cname );
+ if (!X509_digest(x509_cert, digest_tp, digest, &dsz)) {
+ report(stderr, GT_("Out of memory!\n"));
+ return (0);
+ }
+ tp = text;
+ te = text + sizeof(text);
+ for (i = 0; i < dsz; i++) {
+#ifdef HAVE_SNPRINTF
+ esz = snprintf(tp, te - tp, i > 0 ? ":%02X" : "%02X", digest[i]);
+#else
+ esz = sprintf(tp, i > 0 ? ":%02X" : "%02X", digest[i]);
+#endif
+ if (esz >= te - tp) {
+ report(stderr, GT_("Digest text buffer too small!\n"));
+ return (0);
+ }
+ tp += esz;
+ }
+ report(stdout, GT_("%s key fingerprint: %s\n"), _server_label, text);
+ if (_check_digest != NULL) {
+ if (strcmp(text, _check_digest) == 0)
+ report(stdout, GT_("%s fingerprints match.\n"), _server_label);
+ else {
+ report(stderr, GT_("%s fingerprints do not match!\n"), _server_label);
+ return (0);
+ }
}
- } else {
- if (outlevel == O_VERBOSE)
- report(stdout, "Unknown Server CommonName\n", cbuf );
}
}
- switch (ctx->error) {
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
- X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
- report(stdout, "unknown issuer= %s", buf);
- break;
- case X509_V_ERR_CERT_NOT_YET_VALID:
- case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
- report(stderr, "Server Certificate not yet valid");
- break;
- case X509_V_ERR_CERT_HAS_EXPIRED:
- case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
- report(stderr, "Server Certificate expired");
- break;
+ if (err != X509_V_OK && (strict || outlevel == O_VERBOSE)) {
+ report(strict ? stderr : stdout, GT_("Warning: server certificate verification: %s\n"), X509_verify_cert_error_string(err));
+ /* We gave the error code, but maybe we can add some more details for debugging */
+ switch (err) {
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ X509_NAME_oneline(issuer, buf, sizeof(buf));
+ buf[sizeof(buf) - 1] = '\0';
+ report(stdout, GT_("unknown issuer (first %d characters): %s\n"), sizeof(buf), buf);
+ break;
+ }
}
- /* We are not requiring or validating server or issuer id's as yet */
- /* Always return OK from here */
- ok_return = 1;
- return( ok_return );
+ if (!strict)
+ ok_return = 1;
+ return (ok_return);
+}
+
+int SSL_nock_verify_callback( int ok_return, X509_STORE_CTX *ctx )
+{
+ return SSL_verify_callback(ok_return, ctx, 0);
}
+int SSL_ck_verify_callback( int ok_return, X509_STORE_CTX *ctx )
+{
+ return SSL_verify_callback(ok_return, ctx, 1);
+}
/* performs initial SSL handshake over the connected socket
* uses SSL *ssl global variable, which is currently defined
* in this file
*/
-int SSLOpen(int sock, char *mycert, char *mykey, char *servercname )
+int SSLOpen(int sock, char *mycert, char *mykey, char *myproto, int certck, char *certpath,
+ char *fingerprint, char *servercname, char *label)
{
+ SSL *ssl;
+
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
if( sock < 0 || sock > FD_SETSIZE ) {
- report(stderr, "File descriptor out of range for SSL" );
+ report(stderr, GT_("File descriptor out of range for SSL") );
return( -1 );
}
if( ! _ctx ) {
/* Be picky and make sure the memory is cleared */
memset( _ssl_context, 0, sizeof( _ssl_context ) );
- _ctx = SSL_CTX_new(SSLv23_client_method());
+ if(myproto) {
+ if(!strcmp("ssl2",myproto)) {
+ _ctx = SSL_CTX_new(SSLv2_client_method());
+ } else if(!strcmp("ssl3",myproto)) {
+ _ctx = SSL_CTX_new(SSLv3_client_method());
+ } else if(!strcmp("tls1",myproto)) {
+ _ctx = SSL_CTX_new(TLSv1_client_method());
+ } else {
+ fprintf(stderr,GT_("Invalid SSL protocol '%s' specified, using default (SSLv23).\n"), myproto);
+ myproto = NULL;
+ }
+ }
+ if(!myproto) {
+ _ctx = SSL_CTX_new(SSLv23_client_method());
+ }
if(_ctx == NULL) {
ERR_print_errors_fp(stderr);
return(-1);
}
}
+
+ if (certck) {
+ SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, SSL_ck_verify_callback);
+ if (certpath)
+ SSL_CTX_load_verify_locations(_ctx, NULL, certpath);
+ } else {
+ /* In this case, we do not fail if verification fails. However,
+ * we provide the callback for output and possible fingerprint checks. */
+ SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, SSL_nock_verify_callback);
+ }
_ssl_context[sock] = SSL_new(_ctx);
/* This static is for the verify callback */
_ssl_server_cname = servercname;
-
- SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, SSL_verify_callback);
+ _server_label = label;
+ _check_fp = 1;
+ _check_digest = fingerprint;
+ _depth0ck = 0;
if( mycert || mykey ) {
ERR_print_errors_fp(stderr);
return(-1);
}
-
+
+ /* Paranoia: was the callback not called as we expected? */
+ if ((fingerprint != NULL || certck) && !_depth0ck) {
+ report(stderr, GT_("Certificate/fingerprint verification was somehow skipped!\n"));
+
+ if( NULL != ( ssl = SSLGetContext( sock ) ) ) {
+ /* Clean up the SSL stack */
+ SSL_free( _ssl_context[sock] );
+ _ssl_context[sock] = NULL;
+ }
+ return(-1);
+ }
+
return(0);
}
#endif
int SockClose(int sock)
/* close a socket gracefully */
{
- char ch;
#ifdef SSL_ENABLE
SSL *ssl;
* This stops sends but allows receives (effectively, it sends a
* TCP <FIN>). */
if (shutdown(sock, 1) == 0) {
+ char ch;
/* If there is any data still waiting in the queue, discard it.
* Call recv() until either it returns 0 (meaning we received a FIN)
* or any error occurs. This makes sure all data sent by the other