void netsec_set_userid(netsec_context *ns_context, const char *userid);
+/*
+ * Set the hostname of the server we're connecting to. This is used
+ * by the Cyrus-SASL library and by the TLS code. This must be called
+ * before netsec_negotiate_tls() or netsec_set_sasl_params().
+ *
+ * Arguments:
+ *
+ * ns_context - Network security context
+ * hostname - FQDN of remote host. Cannot be NULL.
+ */
+
+void netsec_set_hostname(netsec_context *ns_context, const char *hostname);
+
/*
* Returns "snoop" status on current connection.
*
* Arguments:
*
* ns_context - Network security context
- * hostname - Fully qualified hostname of remote host.
* service - Service name (set to NULL to disable SASL).
* mechanism - The mechanism desired by the user. If NULL, the SASL
* library will attempt to negotiate the best mechanism.
* Returns NOTOK if SASL is not supported.
*/
-int netsec_set_sasl_params(netsec_context *ns_context, const char *hostname,
- const char *service, const char *mechanism,
+int netsec_set_sasl_params(netsec_context *ns_context, const char *service,
+ const char *mechanism,
netsec_sasl_callback callback, char **errstr);
/*
* Controls whether or not TLS will be negotiated for this connection.
*
* Note: callers still have to call netsec_tls_negotiate() to start
- * TLS negotiation at the appropriate point in the protocol.
+ * TLS negotiation at the appropriate point in the protocol. The
+ * remote hostname (controlled by netsec_set_hostname()) should have
+ * already been set before this function is called unless certificate
+ * verification is disabled.
*
* Arguments
*
* tls - If nonzero, enable TLS. Otherwise disable TLS
* negotiation.
+ * noverify - If nonzero, disable server certificate and hostname
+ * validation.
*
* Returns NOTOK if TLS is not supported or was unable to initialize.
*/
-int netsec_set_tls(netsec_context *context, int tls, char **errstr);
+int netsec_set_tls(netsec_context *context, int tls, int noverify,
+ char **errstr);
/*
* Start TLS negotiation on this protocol. This connection should have
* popsbr.h -- header for POP client subroutines
*/
+/* TLS flags */
+#define P_INITTLS 0x01
+#define P_NOVERIFY 0x02
int pop_init (char *, char *, char *, char *, int, int, char *, int,
const char *);
int pop_fd (char *, int, char *, int);
.IR service ]
.RB [ \-initialtls ]
.RB [ \-notls ]
+.RB [ \-certverify " | " \-nocertverify ]
.RB [ \-snoop ]
.ad
.SH DESCRIPTION
switch. The
.B \-notls
switch will disable all attempts to negotiate TLS.
+.PP
+When using TLS the default is to verify the remote certificate and SubjectName
+against the local trusted certificate store. This can be controlled by
+the
+.B \-certverify
+and
+.B \-nocertverify
+switches. See your OpenSSL documentation for more information on certificate
+verification.
.SH FILES
.PD 0
.TP 20
.IR hostname ]
.RB [ \-user
.IR username ]
-.RB [ \-sasl ]
+.RB [ \-sasl " | " \-nosasl ]
.RB [ \-saslmech
.IR mechanism ]
.RB [ \-initialtls ]
.RB [ \-notls ]
+.RB [ \-certverify " | " \-nocertverify ]
.RB [ \-authservice
.IR service ]
.RB [ \-snoop ]
switch. The
.B \-notls
switch will disable all attempts to negotiate TLS.
+.PP
+When using TLS the default is to verify the remote certificate and SubjectName
+against the local trusted certificate store. This can be controlled by
+the
+.B \-certverify
+and
+.B \-nocertverify
+switches. See your OpenSSL documentation for more information on certificate
+verification.
.SH FILES
.fc ^ ~
.nf
.RB [ \-tls ]
.RB [ \-initialtls ]
.RB [ \-notls ]
+.RB [ \-certverify ]
+.RB [ \-nocertverify ]
.RB [ \-width
.IR columns ]
.RB [ file
SMTPS (SMTP over SSL) was deregistered by IANA in 1998, it is still
used for that service.
.PP
+When using TLS the default is to verify the remote certificate and SubjectName
+against the local trusted certificate store. This can be controlled by
+the
+.B \-certverify
+and
+.B \-nocertverify
+switches. See your OpenSSL documentation for more information on certificate
+verification.
+.PP
The files specified by the profile entry \*(lqAliasfile:\*(rq and any
additional alias files given by the
.B \-alias
.RB ` \-noverbose '
.RB ` \-nowatch '
.RB ` "\-width\ 72" '
+.RB ` \-certverify '
.fi
.SH CONTEXT
None
if (user)
netsec_set_userid(nsc, user);
+ netsec_set_hostname(nsc, server);
+
if (sm_debug)
netsec_set_snoop(nsc, 1);
if (sasl) {
- if (netsec_set_sasl_params(nsc, server, "smtp", saslmech,
- sm_sasl_callback, &errstr) != OK)
+ if (netsec_set_sasl_params(nsc, "smtp", saslmech, sm_sasl_callback,
+ &errstr) != OK)
return sm_nerror(errstr);
}
netsec_set_fd(nsc, sd1, sd1);
- if (tls) {
- if (netsec_set_tls(nsc, 1, &errstr) != OK)
+ if (tls & S_TLSENABLEMASK) {
+ if (netsec_set_tls(nsc, 1, tls & S_NOVERIFY, &errstr) != OK)
return sm_nerror(errstr);
}
/*
- * If tls == 2, that means that the user requested "initial" TLS,
- * which happens right after the connection has opened. Do that
- * negotiation now
+ * If tls == S_INITTLS, that means that the user requested
+ * "initial" TLS, which happens right after the connection has
+ * opened. Do that negotiation now
*/
- if (tls == 2) {
+ if (tls & S_INITTLS) {
if (netsec_negotiate_tls(nsc, &errstr) != OK) {
sm_end(NOTOK);
return sm_nerror(errstr);
* restart the EHLO dialog after TLS negotiation is complete.
*/
- if (tls == 1) {
+ if (tls & S_STARTTLS) {
if (! EHLOset("STARTTLS")) {
sm_end(NOTOK);
return sm_ierror("SMTP server does not support TLS");
if (user)
netsec_set_userid(nsc, user);
+ netsec_set_hostname(nsc, client);
+
if (sm_debug)
netsec_set_snoop(nsc, 1);
if (sasl) {
- if (netsec_set_sasl_params(nsc, client, "smtp", saslmech,
- sm_sasl_callback, &errstr) != OK)
+ if (netsec_set_sasl_params(nsc, "smtp", saslmech, sm_sasl_callback,
+ &errstr) != OK)
return sm_nerror(errstr);
}
* prototypes
*/
/* int client (); */
+/* TLS flags */
+#define S_STARTTLS 0x01
+#define S_INITTLS 0x02
+#define S_TLSENABLEMASK 0x03
+#define S_NOVERIFY 0x04
int sm_init (char *, char *, char *, int, int, int, int, const char *,
const char *, const char *, int);
int sm_winit (char *, int, int);
void *ns_snoop_context; /* Context data for snoop function */
int ns_timeout; /* Network read timeout, in seconds */
char *ns_userid; /* Userid for authentication */
+ char *ns_hostname; /* Hostname we've connected to */
unsigned char *ns_inbuffer; /* Our read input buffer */
unsigned char *ns_inptr; /* Our read buffer input pointer */
unsigned int ns_inbuflen; /* Length of data in input buffer */
char *oauth_service; /* OAuth2 service name */
#endif /* OAUTH_SUPPORT */
#ifdef CYRUS_SASL
- char *sasl_hostname; /* Hostname we've connected to */
sasl_conn_t *sasl_conn; /* SASL connection context */
sasl_ssf_t sasl_ssf; /* SASL Security Strength Factor */
sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */
nsc->ns_snoop_cb = NULL;
nsc->ns_snoop_context = NULL;
nsc->ns_userid = NULL;
+ nsc->ns_hostname = NULL;
nsc->ns_timeout = 60; /* Our default */
nsc->ns_inbufsize = NETSEC_BUFSIZE;
nsc->ns_inbuffer = mh_xmalloc(nsc->ns_inbufsize);
#endif /* OAUTH_SUPPORT */
#ifdef CYRUS_SASL
nsc->sasl_conn = NULL;
- nsc->sasl_hostname = NULL;
nsc->sasl_cbs = NULL;
nsc->sasl_creds = NULL;
nsc->sasl_secret = NULL;
netsec_shutdown(netsec_context *nsc, int closeflag)
{
mh_xfree(nsc->ns_userid);
+ mh_xfree(nsc->ns_hostname);
mh_xfree(nsc->ns_inbuffer);
mh_xfree(nsc->ns_outbuffer);
mh_xfree(nsc->sasl_mech);
#ifdef CYRUS_SASL
if (nsc->sasl_conn)
sasl_dispose(&nsc->sasl_conn);
- mh_xfree(nsc->sasl_hostname);
mh_xfree(nsc->sasl_cbs);
if (nsc->sasl_creds)
nmh_credentials_free(nsc->sasl_creds);
nsc->ns_userid = getcpy(userid);
}
+/*
+ * Set the hostname of the remote host we're connecting to.
+ */
+
+void
+netsec_set_hostname(netsec_context *nsc, const char *hostname)
+{
+ nsc->ns_hostname = mh_xstrdup(hostname);
+}
+
/*
* Get the snoop flag for this connection
*/
*/
int
-netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
- const char *service, const char *mechanism,
- netsec_sasl_callback callback, char **errstr)
+netsec_set_sasl_params(netsec_context *nsc, const char *service,
+ const char *mechanism, netsec_sasl_callback callback,
+ char **errstr)
{
#ifdef CYRUS_SASL
sasl_callback_t *sasl_cbs;
int retval;
+ if (!nsc->ns_hostname) {
+ netsec_err(errstr, "Internal error: ns_hostname is NULL");
+ return NOTOK;
+ }
+
if (! sasl_initialized) {
retval = sasl_client_init(NULL);
if (retval != SASL_OK) {
nsc->sasl_cbs = sasl_cbs;
- retval = sasl_client_new(service, hostname, NULL, NULL, nsc->sasl_cbs, 0,
- &nsc->sasl_conn);
+ retval = sasl_client_new(service, nsc->ns_hostname, NULL, NULL,
+ nsc->sasl_cbs, 0, &nsc->sasl_conn);
if (retval) {
netsec_err(errstr, "SASL new client allocation failed: %s",
return NOTOK;
}
- nsc->sasl_hostname = mh_xstrdup(hostname);
-
/*
* Set up our credentials
*/
- nsc->sasl_creds = nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid);
+ nsc->sasl_creds = nmh_get_credentials(nsc->ns_hostname, nsc->ns_userid);
#else /* CYRUS_SASL */
NMH_UNUSED(hostname);
*/
int
-netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
+netsec_set_tls(netsec_context *nsc, int tls, int noverify, char **errstr)
{
#ifdef TLS_SUPPORT
if (tls) {
SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_TLSv1);
+ if (!SSL_CTX_set_default_verify_paths(sslctx)) {
+ netsec_err(errstr, "Unable to set default certificate "
+ "verification paths: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
tls_initialized++;
}
SSL_set_bio(ssl, rbio, wbio);
SSL_set_connect_state(ssl);
+ /*
+ * If noverify is NOT set, then do certificate validation.
+ * Turning on SSL_VERIFY_PEER will verify the certificate chain
+ * against locally stored root certificates (the locations are
+ * set using SSL_CTX_set_default_verify_paths()), and we put
+ * the hostname in the X509 verification parameters so the OpenSSL
+ * code will verify that the hostname appears in the server
+ * certificate.
+ */
+
+ if (! noverify) {
+ X509_VERIFY_PARAM *param;
+
+ SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
+ if (! nsc->ns_hostname) {
+ netsec_err(errstr, "Internal error: hostname not set and "
+ "certification verification enabled");
+ SSL_free(ssl);
+ return NOTOK;
+ }
+
+ param = SSL_get0_param(ssl);
+
+ if (! X509_VERIFY_PARAM_set1_host(param, nsc->ns_hostname, 0)) {
+ netsec_err(errstr, "Unable to add hostname %s to cert "
+ "verification parameters: %s", nsc->ns_hostname,
+ ERR_error_string(ERR_get_error(), NULL));
+ SSL_free(ssl);
+ return NOTOK;
+ }
+ }
+
ssl_bio = BIO_new(BIO_f_ssl());
if (! ssl_bio) {
}
if (BIO_do_handshake(nsc->ssl_io) < 1) {
- netsec_err(errstr, "TLS negotiation failed: %s",
- ERR_error_string(ERR_get_error(), NULL));
+ unsigned long errcode = ERR_get_error();
+
+ /*
+ * Print a more detailed message if it was certificate verification
+ * failure.
+ */
+
+ if (ERR_GET_LIB(errcode) == ERR_LIB_SSL &&
+ ERR_GET_REASON(errcode) == SSL_R_CERTIFICATE_VERIFY_FAILED) {
+ SSL *ssl;
+
+ if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+ netsec_err(errstr, "Certificate verification failed, but "
+ "cannot retrieve SSL handle: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ } else {
+ netsec_err(errstr, "Server certificate verification failed: %s",
+ X509_verify_cert_error_string(
+ SSL_get_verify_result(ssl)));
+ }
+ } else {
+ netsec_err(errstr, "TLS negotiation failed: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+
+ /*
+ * Because negotiation failed, shut down TLS so we don't get any
+ * garbage on the connection. Because of weirdness with SSL_shutdown,
+ * we end up calling it twice: once explicitly, once as part of
+ * BIO_free_all().
+ */
+
+ BIO_ssl_shutdown(nsc->ssl_io);
+ BIO_free_all(nsc->ssl_io);
+ nsc->ssl_io = NULL;
+
return NOTOK;
}
X("saslmech", 0, SASLMECHSW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NOTLSSW) \
+ X("certverify", TLSminc(-10), CERTVERSW) \
+ X("nocertverify", TLSminc(-12), NOCERTVERSW) \
X("authservice", 0, AUTHSERVICESW) \
X("proxy command", 0, PROXYSW) \
int chgflag = 1, trnflag = 1;
int noisy = 1, width = -1;
int hghnum = 0, msgnum = 0;
- int sasl = 0, tls = 0;
+ int sasl = 0, tls = 0, noverify = 1;
int incerr = 0; /* <0 if inc hits an error which means it should not truncate mailspool */
char *cp, *maildir = NULL, *folder = NULL;
char *format = NULL, *form = NULL;
tls = 0;
continue;
+ case CERTVERSW:
+ noverify = 0;
+ continue;
+
+ case NOCERTVERSW:
+ noverify++;
+ continue;
+
case AUTHSERVICESW:
#ifdef OAUTH_SUPPORT
if (!(auth_svc = *argp++) || *auth_svc == '-')
* a POP server?
*/
if (inc_type == INC_POP) {
+ int tlsflag = 0;
+
if (auth_svc == NULL) {
if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
adios (NULL, "must specify -authservice with -saslmech xoauth2");
}
}
+ if (tls)
+ tlsflag |= P_INITTLS;
+
+ if (noverify)
+ tlsflag |= P_NOVERIFY;
+
/*
* initialize POP connection
*/
if (pop_init (host, port, user, proxy, snoop, sasl, saslmech,
- tls, auth_svc) == NOTOK)
+ tlsflag, auth_svc) == NOTOK)
adios (NULL, "%s", response);
/* Check if there are any messages */
X("help", 0, HELPSW) \
X("snoop", 0, SNOOPSW) \
X("sasl", SASLminc(4), SASLSW) \
+ X("nosasl", SASLminc(6), NOSASLSW) \
X("saslmech", SASLminc(5), SASLMECHSW) \
X("authservice", SASLminc(0), AUTHSERVICESW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NOTLSSW) \
+ X("certverify", TLSminc(-10), CERTVERSW) \
+ X("nocertverify", TLSminc(-12), NOCERTVERSW) \
X("proxy command", 0, PROXYSW) \
#define X(sw, minchars, id) id,
main (int argc, char **argv)
{
int datesw = 1, notifysw = NT_ALL;
- int status = 0, sasl = 0, tls = 0;
+ int status = 0, sasl = 0, tls = 0, noverify = 0;
int snoop = 0, vecp = 0;
char *cp, *host = NULL, *port = NULL, *user = NULL, *proxy = NULL;
char buf[BUFSIZ], *saslmech = NULL, *auth_svc = NULL;
sasl++;
continue;
+ case NOSASLSW:
+ sasl = 0;
+ continue;
+
case SASLMECHSW:
if (!(saslmech = *argp++) || *saslmech == '-')
adios (NULL, "missing argument to %s", argp[-2]);
tls = 0;
continue;
+ case CERTVERSW:
+ noverify = 0;
+ continue;
+
+ case NOCERTVERSW:
+ noverify++;
+ continue;
+
case AUTHSERVICESW:
#ifdef OAUTH_SUPPORT
if (!(auth_svc = *argp++) || *auth_svc == '-')
vec[vecp] = NULL;
if (host) {
+ int tlsflag = 0;
+
+ if (tls)
+ tlsflag |= P_INITTLS;
+
+ if (noverify)
+ tlsflag |= P_NOVERIFY;
+
if (vecp == 0) {
status = remotemail (host, port, user, proxy, notifysw, 1,
- snoop, sasl, saslmech, tls, auth_svc);
+ snoop, sasl, saslmech, tlsflag, auth_svc);
} else {
for (vecp = 0; vec[vecp]; vecp++)
status += remotemail (host, port, vec[vecp], proxy, notifysw, 0,
- snoop, sasl, saslmech, tls, auth_svc);
+ snoop, sasl, saslmech, tlsflag, auth_svc);
}
} else {
if (user == NULL) user = getusername ();
if (user)
netsec_set_userid(nsc, user);
+ netsec_set_hostname(nsc, host);
+
if (oauth_svc != NULL) {
if (netsec_set_oauth_service(nsc, oauth_svc) != OK) {
snprintf(response, sizeof(response), "OAuth2 not supported");
netsec_set_fd(nsc, fd1, fd2);
netsec_set_snoop(nsc, snoop);
- if (tls) {
- if (netsec_set_tls(nsc, 1, &errstr) != OK) {
+ if (tls & P_INITTLS) {
+ if (netsec_set_tls(nsc, 1, tls & P_NOVERIFY, &errstr) != OK) {
snprintf(response, sizeof(response), "%s", errstr);
free(errstr);
return NOTOK;
}
if (sasl) {
- if (netsec_set_sasl_params(nsc, host, "pop", mech,
- pop_sasl_callback, &errstr) != OK) {
+ if (netsec_set_sasl_params(nsc, "pop", mech, pop_sasl_callback,
+ &errstr) != OK) {
snprintf(response, sizeof(response), "%s", errstr);
free(errstr);
return NOTOK;
X("tls", TLSminc(-3), TLSSW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NTLSSW) \
+ X("certverify", TLSminc(-10), CERTVERSW) \
+ X("nocertverify", TLSminc(-12), NOCERTVERSW) \
X("fileproc", -4, FILEPROCSW) \
X("mhlproc", -3, MHLPROCSW) \
X("sendmail program", 0, MTSSM) \
static char *saslmech=NULL; /* Force use of particular SASL mech */
static char *user=NULL; /* Authenticate as this user */
static char *port="submission"; /* Name of server port for SMTP submission */
-static int tls=-1; /* Use TLS for encryption */
+static int tlsflag=0; /* Flags to control TLS settings */
static int fromcount=0; /* Count of addresses on From: header */
static int seensender=0; /* Have we seen a Sender: header? */
int
main (int argc, char **argv)
{
- int state, compnum, dashstuff = 0, swnum, oauth_flag = 0;
+ int state, compnum, dashstuff = 0, swnum, oauth_flag = 0, tls = -1;
+ int noverify = 0;
int eai = 0; /* use Email Address Internationalization (EAI) (SMTPUTF8) */
char *cp, *msg = NULL, **argp, **arguments, *envelope;
char buf[BUFSIZ], name[NAMESZ], *auth_svc = NULL;
tls = 0;
continue;
+ case CERTVERSW:
+ noverify = 0;
+ continue;
+
+ case NOCERTVERSW:
+ noverify++;
+ continue;
+
case FILEPROCSW:
if (!(cp = *argp++) || *cp == '-')
adios (NULL, "missing argument to %s", argp[-2]);
#endif /* ! TLS_SUPPORT */
}
+ if (tls == 1)
+ tlsflag = S_STARTTLS;
+ else if (tls == 2)
+ tlsflag = S_INITTLS;
+ else
+ tlsflag = 0;
+
+ if (noverify)
+ tlsflag |= S_NOVERIFY;
+
/*
* If we were given any oauth flags, store the appropriate profile
* entries and make sure an authservice was given (we have to do this
if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
verbose, snoop, sasl, saslmech, user,
- oauth_flag ? auth_svc : NULL, tls))
+ oauth_flag ? auth_svc : NULL, tlsflag))
|| rp_isbad (retval = sm_winit (envelope, eai, eightbit))) {
close (fd);
die (NULL, "problem initializing server; %s", rp_string (retval));
if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
verbose, snoop, sasl, saslmech, user,
- oauth_flag ? auth_svc : NULL, tls))
+ oauth_flag ? auth_svc : NULL, tlsflag))
|| rp_isbad (retval = sm_winit (envelope, eai, eightbit))) {
die (NULL, "problem initializing server; %s", rp_string (retval));
}
X("tls", TLSminc(-3), TLSSW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NTLSSW) \
+ X("certverify", TLSminc(-10), CERTVERSW) \
+ X("nocertverify", TLSminc(-12), NOCERTVERSW) \
X("sendmail program", 0, MTSSM) \
X("mts smtp|sendmail/smtp|sendmail/pipe", 2, MTSSW) \
X("messageid localname|random", 2, MESSAGEIDSW) \
case TLSSW:
case INITTLSSW:
case NTLSSW:
+ case CERTVERSW:
+ case NOCERTVERSW:
vec[vecp++] = --cp;
continue;
X("tls", TLSminc(-3), TLSSW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NTLSSW) \
+ X("certverify", TLSminc(-10), CERTVERSW) \
+ X("nocertverify", TLSminc(-12), NOCERTVERSW) \
X("sendmail program", 0, MTSSM) \
X("mts smtp|sendmail/smtp|sendmail/pipe", 2, MTSSW) \
X("messageid localname|random", 2, MESSAGEIDSW) \
case TLSSW:
case INITTLSSW:
case NTLSSW:
+ case CERTVERSW:
+ case NOCERTVERSW:
vec[vecp++] = --cp;
continue;