]> diplodocus.org Git - nmh/blobdiff - sbr/netsec.c
Fix invalid pointer arithmetic.
[nmh] / sbr / netsec.c
index c31e7295bea976f75d062b546f4c54c7b4cf990d..ab49046745bbb4040e3ebb2053749950b323c040 100644 (file)
@@ -1,6 +1,4 @@
-
-/*
- * netsec.c -- Network security routines for handling protocols that
+/* netsec.c -- Network security routines for handling protocols that
  *            require SASL and/or TLS.
  *
  * This code is Copyright (c) 2016, by the authors of nmh.  See the
@@ -14,6 +12,7 @@
 #include <h/oauth.h>
 #include <stdarg.h>
 #include <sys/select.h>
+#include "base64.h"
 
 #ifdef CYRUS_SASL
 #include <sasl/sasl.h>
@@ -56,12 +55,14 @@ static SSL_CTX *sslctx = NULL;              /* SSL Context */
 struct _netsec_context {
     int ns_readfd;             /* Read descriptor for network connection */
     int ns_writefd;            /* Write descriptor for network connection */
+    int ns_noclose;            /* Do not close file descriptors if set */
     int ns_snoop;              /* If true, display network data */
-    int ns_snoop_noend;                /* If true, didn't get a CR/LF on last line */
     netsec_snoop_callback *ns_snoop_cb; /* Snoop output callback */
     void *ns_snoop_context;    /* Context data for snoop function */
+    char *ns_snoop_savebuf;    /* Save buffer for snoop data */
     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 */
@@ -73,11 +74,11 @@ struct _netsec_context {
     char *sasl_mech;           /* User-requested mechanism */
     char *sasl_chosen_mech;    /* Mechanism chosen by SASL */
     netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
+    void *sasl_proto_context;  /* Context to be used by SASL callback */
 #ifdef OAUTH_SUPPORT
     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 */
@@ -142,11 +143,13 @@ netsec_init(void)
     NEW(nsc);
     nsc->ns_readfd = -1;
     nsc->ns_writefd = -1;
+    nsc->ns_noclose = 0;
     nsc->ns_snoop = 0;
-    nsc->ns_snoop_noend = 0;
     nsc->ns_snoop_cb = NULL;
     nsc->ns_snoop_context = NULL;
+    nsc->ns_snoop_savebuf = 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);
@@ -159,12 +162,12 @@ netsec_init(void)
     nsc->sasl_mech = NULL;
     nsc->sasl_chosen_mech = NULL;
     nsc->sasl_proto_cb = NULL;
+    nsc->sasl_proto_context = NULL;
 #ifdef OAUTH_SUPPORT
     nsc->oauth_service = NULL;
 #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;
@@ -182,47 +185,34 @@ netsec_init(void)
 
 /*
  * Shutdown the connection completely and free all resources.
- * The connection is only closed if the flag is given.
  */
 
 void
-netsec_shutdown(netsec_context *nsc, int closeflag)
+netsec_shutdown(netsec_context *nsc)
 {
-    if (nsc->ns_userid)
-       free(nsc->ns_userid);
-    if (nsc->ns_inbuffer)
-       free(nsc->ns_inbuffer);
-    if (nsc->ns_outbuffer)
-       free(nsc->ns_outbuffer);
-    if (nsc->sasl_mech)
-       free(nsc->sasl_mech);
-    if (nsc->sasl_chosen_mech)
-       free(nsc->sasl_chosen_mech);
+    free(nsc->ns_userid);
+    free(nsc->ns_hostname);
+    free(nsc->ns_inbuffer);
+    free(nsc->ns_outbuffer);
+    free(nsc->sasl_mech);
+    free(nsc->sasl_chosen_mech);
+    free(nsc->ns_snoop_savebuf);
 #ifdef OAUTH_SERVICE
-    if (nsc->oauth_service)
-       free(nsc->oauth_service);
+    free(nsc->oauth_service);
 #endif /* OAUTH_SERVICE */
 #ifdef CYRUS_SASL
     if (nsc->sasl_conn)
        sasl_dispose(&nsc->sasl_conn);
-    if (nsc->sasl_hostname)
-       free(nsc->sasl_hostname);
-    if (nsc->sasl_cbs)
-       free(nsc->sasl_cbs);
-    if (nsc->sasl_creds) {
-       if (nsc->sasl_creds->password)
-           memset(nsc->sasl_creds->password, 0,
-                  strlen(nsc->sasl_creds->password));
-       free(nsc->sasl_creds);
-    }
+    free(nsc->sasl_cbs);
+    if (nsc->sasl_creds)
+       nmh_credentials_free(nsc->sasl_creds);
     if (nsc->sasl_secret) {
        if (nsc->sasl_secret->len > 0) {
            memset(nsc->sasl_secret->data, 0, nsc->sasl_secret->len);
        }
        free(nsc->sasl_secret);
     }
-    if (nsc->sasl_tmpbuf)
-       free(nsc->sasl_tmpbuf);
+    free(nsc->sasl_tmpbuf);
 #endif /* CYRUS_SASL */
 #ifdef TLS_SUPPORT
     if (nsc->ssl_io)
@@ -233,7 +223,7 @@ netsec_shutdown(netsec_context *nsc, int closeflag)
        BIO_free_all(nsc->ssl_io);
 #endif /* TLS_SUPPORT */
 
-    if (closeflag) {
+    if (! nsc->ns_noclose) {
        if (nsc->ns_readfd != -1)
            close(nsc->ns_readfd);
        if (nsc->ns_writefd != -1 && nsc->ns_writefd != nsc->ns_readfd)
@@ -264,6 +254,16 @@ netsec_set_userid(netsec_context *nsc, const char *userid)
     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
  */
@@ -396,7 +396,7 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
      * assume here that this has something in it.
      */
 
-    retlen = size > nsc->ns_inbuflen ? nsc->ns_inbuflen : size;
+    retlen = min(size, nsc->ns_inbuflen);
 
     memcpy(buffer, nsc->ns_inptr, retlen);
 
@@ -464,8 +464,10 @@ retry:
                if (nsc->ns_snoop_cb)
                    nsc->ns_snoop_cb(nsc, sptr, strlen(sptr),
                                     nsc->ns_snoop_context);
-               else
-                   fprintf(stderr, "%s\n", sptr);
+               else {
+                    fputs(sptr, stderr);
+                    putc('\n', stderr);
+                }
            }
            return sptr;
        }
@@ -477,7 +479,7 @@ retry:
      */
 
     if (count >= nsc->ns_inbufsize / 2) {
-       netsec_err(errstr, "Unable to find a line terminator after %d bytes",
+       netsec_err(errstr, "Unable to find a line terminator after %zu bytes",
                   count);
        return NULL;
     }
@@ -631,7 +633,8 @@ retry:
            if (nsc->ns_snoop)
                ERR_print_errors_fp(stderr);
            return NOTOK;
-       } else if (rc < 0) {
+       }
+        if (rc < 0) {
            /* Definitely an error */
            netsec_err(errstr, "Read on TLS connection failed: %s",
                       ERR_error_string(ERR_get_error(), NULL));
@@ -830,48 +833,18 @@ retry:
                       "%d bytes, but our buffer size was only %d bytes",
                       rc, nsc->ns_outbufsize);
            return NOTOK;
-       } else {
-           /*
-            * Generate a flush (which may be inefficient, but hopefully
-            * it isn't) and then try again.
-            */
-           if (netsec_flush(nsc, errstr) != OK)
-               return NOTOK;
-           /*
-            * After this, outbuffer should == outptr, so we shouldn't
-            * hit this next time around.
-            */
-           goto retry;
-       }
-    }
-
-    if (nsc->ns_snoop) {
-       int outlen = rc;
-       if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\n') {
-           outlen--;
-           if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\r')
-               outlen--;
-       } else {
-           nsc->ns_snoop_noend = 1;
-       }
-       if (outlen > 0 || nsc->ns_snoop_noend == 0) {
-#ifdef CYRUS_SASL
-           if (nsc->sasl_seclayer)
-               fprintf(stderr, "(sasl-encrypted) ");
-#endif /* CYRUS_SASL */
-#ifdef TLS_SUPPORT
-           if (nsc->tls_active)
-               fprintf(stderr, "(tls-encrypted) ");
-#endif /* TLS_SUPPORT */
-           fprintf(stderr, "=> ");
-           if (nsc->ns_snoop_cb)
-               nsc->ns_snoop_cb(nsc, (char *) nsc->ns_outptr, outlen,
-                                nsc->ns_snoop_context);
-           else
-                fprintf(stderr, "%.*s\n", outlen, nsc->ns_outptr); 
-       } else {
-           nsc->ns_snoop_noend = 0;
        }
+        /*
+         * Generate a flush (which may be inefficient, but hopefully
+         * it isn't) and then try again.
+         */
+        if (netsec_flush(nsc, errstr) != OK)
+            return NOTOK;
+        /*
+         * After this, outbuffer should == outptr, so we shouldn't
+         * hit this next time around.
+         */
+        goto retry;
     }
 
     nsc->ns_outptr += rc;
@@ -899,6 +872,95 @@ netsec_flush(netsec_context *nsc, char **errstr)
     if (netoutlen == 0)
        return OK;
 
+    /*
+     * If we have snoop turned on, output the data.
+     *
+     * Note here; if we don't have a CR or LF at the end, save the data
+     * in ns_snoop_savebuf for later and print it next time.
+     */
+
+    if (nsc->ns_snoop) {
+       unsigned int snoopoutlen = netoutlen;
+       const char *snoopoutbuf = (const char *) nsc->ns_outbuffer;
+
+       while (snoopoutlen > 0) {
+           const char *end = strpbrk(snoopoutbuf, "\r\n");
+           unsigned int outlen;
+
+           if (! end) {
+               if (nsc->ns_snoop_savebuf) {
+                   nsc->ns_snoop_savebuf = mh_xrealloc(nsc->ns_snoop_savebuf,
+                                               strlen(nsc->ns_snoop_savebuf) +
+                                               snoopoutlen + 1);
+                   strncat(nsc->ns_snoop_savebuf, snoopoutbuf, snoopoutlen);
+               } else {
+                   nsc->ns_snoop_savebuf = mh_xmalloc(snoopoutlen + 1);
+                   strncpy(nsc->ns_snoop_savebuf, snoopoutbuf, snoopoutlen);
+                   nsc->ns_snoop_savebuf[snoopoutlen] = '\0';
+               }
+               break;
+           }
+
+           outlen = end - snoopoutbuf;
+
+#ifdef CYRUS_SASL
+           if (nsc->sasl_seclayer)
+               fprintf(stderr, "(sasl-encrypted) ");
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+           if (nsc->tls_active)
+               fprintf(stderr, "(tls-encrypted) ");
+#endif /* TLS_SUPPORT */
+           fprintf(stderr, "=> ");
+           if (nsc->ns_snoop_cb) {
+               const char *ptr;
+               unsigned int cb_len = outlen;
+
+               if (nsc->ns_snoop_savebuf) {
+                   cb_len += strlen(nsc->ns_snoop_savebuf);
+                   nsc->ns_snoop_savebuf = mh_xrealloc(nsc->ns_snoop_savebuf,
+                                               outlen);
+                   ptr = nsc->ns_snoop_savebuf;
+               } else {
+                   ptr = snoopoutbuf;
+               }
+
+               nsc->ns_snoop_cb(nsc, ptr, cb_len, nsc->ns_snoop_context);
+
+               if (nsc->ns_snoop_savebuf) {
+                   free(nsc->ns_snoop_savebuf);
+                   nsc->ns_snoop_savebuf = NULL;
+               }
+           } else {
+               if (nsc->ns_snoop_savebuf) {
+                   fprintf(stderr, "%s", nsc->ns_snoop_savebuf);
+                   free(nsc->ns_snoop_savebuf);
+                   nsc->ns_snoop_savebuf = NULL;
+               }
+               fprintf(stderr, "%.*s\n", outlen, snoopoutbuf);
+           }
+
+           /*
+            * Alright, hopefully any previous leftover data is done,
+            * and we have the current line output.  Move things past the
+            * next CR/LF.
+            */
+
+           snoopoutlen -= outlen;
+           snoopoutbuf += outlen;
+
+           if (snoopoutlen > 0 && *snoopoutbuf == '\r') {
+               snoopoutlen--;
+               snoopoutbuf++;
+           }
+
+           if (snoopoutlen > 0 && *snoopoutbuf == '\n') {
+               snoopoutlen--;
+               snoopoutbuf++;
+           }
+       }
+    }
+
     /*
      * If SASL security layers are in effect, run the data through
      * sasl_encode() first.
@@ -950,14 +1012,19 @@ netsec_flush(netsec_context *nsc, char **errstr)
  */
 
 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,
+                      void *context, 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) {
@@ -993,8 +1060,8 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
 
     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",
@@ -1002,29 +1069,30 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
        return NOTOK;
     }
 
-    nsc->sasl_hostname = getcpy(hostname);
+    /*
+     * Set up our credentials
+     */
+
+    nsc->sasl_creds = nmh_get_credentials(nsc->ns_hostname, nsc->ns_userid);
+
 #else /* CYRUS_SASL */
-    NMH_UNUSED(hostname);
     NMH_UNUSED(service);
     NMH_UNUSED(errstr);
 #endif /* CYRUS_SASL */
 
     /*
      * According to the RFC, mechanisms can only be uppercase letter, numbers,
-     * and a hypen or underscore.  So make sure we uppercase any letters
+     * and a hyphen or underscore.  So make sure we uppercase any letters
      * in case the user passed in lowercase.
      */
 
     if (mechanism) {
-       char *p;
-       nsc->sasl_mech = getcpy(mechanism);
-
-       for (p = nsc->sasl_mech; *p; p++)
-           if (isascii((unsigned char) *p))    /* Just in case */
-               *p = toupper((unsigned char) *p);
+       nsc->sasl_mech = mh_xstrdup(mechanism);
+       to_upper(nsc->sasl_mech);
     }
 
     nsc->sasl_proto_cb = callback;
+    nsc->sasl_proto_context = context;
 
     return OK;
 }
@@ -1043,35 +1111,10 @@ int netsec_get_user(void *context, int id, const char **result,
     if (! result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
        return SASL_BADPARAM;
 
-    if (nsc->ns_userid == NULL) {
-       /*
-        * Pass the 1 third argument to nmh_get_credentials() so that
-        * a default user if the -user switch wasn't supplied, and so
-        * that a default password will be supplied.  That's used when
-        * those values really don't matter, and only with legacy/.netrc,
-        * i.e., with a credentials profile entry.
-        */
-
-       if (nsc->sasl_creds == NULL) {
-           NEW(nsc->sasl_creds);
-           nsc->sasl_creds->user = NULL;
-           nsc->sasl_creds->password = NULL;
-       }
-
-       if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 1,
-                               nsc->sasl_creds) != OK)
-           return SASL_BADPARAM;
+    *result = nmh_cred_get_user(nsc->sasl_creds);
 
-       if (nsc->ns_userid != nsc->sasl_creds->user) {
-           if (nsc->ns_userid)
-               free(nsc->ns_userid);
-           nsc->ns_userid = getcpy(nsc->sasl_creds->user);
-       }
-    }
-
-    *result = nsc->ns_userid;
     if (len)
-       *len = strlen(nsc->ns_userid);
+       *len = strlen(*result);
 
     return SASL_OK;
 }
@@ -1085,6 +1128,7 @@ netsec_get_password(sasl_conn_t *conn, void *context, int id,
                    sasl_secret_t **psecret)
 {
     netsec_context *nsc = (netsec_context *) context;
+    const char *password;
     int len;
 
     NMH_UNUSED(conn);
@@ -1092,40 +1136,22 @@ netsec_get_password(sasl_conn_t *conn, void *context, int id,
     if (! psecret || id != SASL_CB_PASS)
        return SASL_BADPARAM;
 
-    if (nsc->sasl_creds == NULL) {
-       NEW(nsc->sasl_creds);
-       nsc->sasl_creds->user = NULL;
-       nsc->sasl_creds->password = NULL;
-    }
-
-    if (nsc->sasl_creds->password == NULL) {
-       /*
-        * Pass the 0 third argument to nmh_get_credentials() so
-        * that the default password isn't used.  With legacy/.netrc
-        * credentials support, we'll only get here if the -user
-        * switch to send(1)/post(8) wasn't used.
-        */
-
-       if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 0,
-                               nsc->sasl_creds) != OK) {
-           return SASL_BADPARAM;
-       }
-    }
+    password = nmh_cred_get_password(nsc->sasl_creds);
 
-    len = strlen(nsc->sasl_creds->password);
+    len = strlen(password);
 
     /*
      * sasl_secret_t includes 1 bytes for "data" already, so that leaves
      * us room for a terminating NUL
      */
 
-    *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
+    *psecret = malloc(sizeof(sasl_secret_t) + len);
 
     if (! *psecret)
        return SASL_NOMEM;
 
     (*psecret)->len = len;
-    strcpy((char *) (*psecret)->data, nsc->sasl_creds->password);
+    strcpy((char *) (*psecret)->data, password);
 
     nsc->sasl_secret = *psecret;
 
@@ -1200,7 +1226,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
            return NOTOK;
        }
 
-       nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech);
+       nsc->sasl_chosen_mech = mh_xstrdup(nsc->sasl_mech);
 
        if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service,
                               &xoauth_client_res, &xoauth_client_res_len,
@@ -1211,7 +1237,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
        }
 
        rc = nsc->sasl_proto_cb(NETSEC_SASL_START, xoauth_client_res,
-                               xoauth_client_res_len, NULL, 0, errstr);
+                               xoauth_client_res_len, NULL, 0,
+                               nsc->sasl_proto_context, errstr);
        free(xoauth_client_res);
 
        if (rc != OK)
@@ -1224,7 +1251,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
         * error.
         */
 
-       rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0, errstr);
+       rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
+                               nsc->sasl_proto_context, errstr);
 
        if (rc != OK) {
            /*
@@ -1234,9 +1262,10 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
             * NOT get a success message at this point.
             */
            free(*errstr);
-           nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
+           nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0,
+                              nsc->sasl_proto_context, NULL);
            rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
-                                   errstr);
+                                   nsc->sasl_proto_context, errstr);
            if (rc == 0) {
                netsec_err(errstr, "Unexpected success after OAuth failure!");
            }
@@ -1254,7 +1283,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
      * messages.
      */
 
-    memset(&secprops, 0, sizeof(secprops));
+    ZERO(&secprops);
     secprops.maxbufsize = SASL_MAXRECVBUF;
 
     /*
@@ -1294,7 +1323,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
     nsc->sasl_chosen_mech = getcpy(chosen_mech);
 
     if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, NULL, 0,
-                          errstr) != OK)
+                          nsc->sasl_proto_context, errstr) != OK)
        return NOTOK;
 
     /*
@@ -1308,27 +1337,30 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
         */
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_READ, NULL, 0, &outbuf, &outbuflen,
-                              errstr) != OK) {
-           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+                              nsc->sasl_proto_context, errstr) != OK) {
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0,
+                              nsc->sasl_proto_context, NULL);
            return NOTOK;
        }
 
        rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
                              (const char **) &saslbuf, &saslbuflen);
 
-       if (outbuf)
-           free(outbuf);
+        free(outbuf);
 
        if (rc != SASL_OK && rc != SASL_CONTINUE) {
            netsec_err(errstr, "SASL client negotiation failed: %s",
                       sasl_errdetail(nsc->sasl_conn));
-           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0,
+                              nsc->sasl_proto_context, NULL);
            return NOTOK;
        }
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_WRITE, saslbuf, saslbuflen,
-                              NULL, 0, errstr) != OK) {
-           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+                              NULL, 0, nsc->sasl_proto_context,
+                              errstr) != OK) {
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0,
+                              nsc->sasl_proto_context, NULL);
            return NOTOK;
        }
     }
@@ -1339,7 +1371,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
      */
 
     if (nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
-                          errstr) != OK) {
+                          nsc->sasl_proto_context, errstr) != OK) {
        /*
         * At this point we can't really send an abort since the SASL dialog
         * has completed, so just bubble back up the error message.
@@ -1447,6 +1479,20 @@ netsec_get_sasl_mechanism(netsec_context *nsc)
     return nsc->sasl_chosen_mech;
 }
 
+/*
+ * Return the negotiated SASL strength security factor (SSF)
+ */
+
+int
+netsec_get_sasl_ssf(netsec_context *nsc)
+{
+#ifdef CYRUS_SASL
+    return nsc->sasl_ssf;
+#else /* CYRUS_SASL */
+    return 0;
+#endif /* CYRUS_SASL */
+}
+
 /*
  * Set an OAuth2 service name, if we support it.
  */
@@ -1469,12 +1515,12 @@ netsec_set_oauth_service(netsec_context *nsc, const char *service)
  */
 
 int
-netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
+netsec_set_tls(netsec_context *nsc, int tls, int noverify, char **errstr)
 {
-    if (tls) {
 #ifdef TLS_SUPPORT
+    if (tls) {
        SSL *ssl;
-       BIO *rbio, *wbio, *ssl_bio;;
+        BIO *rbio, *wbio, *ssl_bio;
 
        if (! tls_initialized) {
            SSL_library_init();
@@ -1497,6 +1543,13 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
            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++;
        }
 
@@ -1539,7 +1592,7 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
         * SSL BIO -> socket BIO.
         */
 
-       rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);
+       rbio = BIO_new_socket(nsc->ns_readfd, BIO_CLOSE);
 
        if (! rbio) {
            netsec_err(errstr, "Unable to create a read socket BIO: %s",
@@ -1548,7 +1601,7 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
            return NOTOK;
        }
 
-       wbio = BIO_new_socket(nsc->ns_writefd, BIO_NOCLOSE);
+       wbio = BIO_new_socket(nsc->ns_writefd, BIO_CLOSE);
 
        if (! wbio) {
            netsec_err(errstr, "Unable to create a write socket BIO: %s",
@@ -1561,6 +1614,42 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
        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) {
+#ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST
+           X509_VERIFY_PARAM *param;
+#endif /* HAVE_X509_VERIFY_PARAM_SET1_HOST */
+
+           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;
+           }
+
+#ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST
+           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;
+           }
+#endif /* HAVE_X509_VERIFY_PARAM_SET1_HOST */
+       }
+
        ssl_bio = BIO_new(BIO_f_ssl());
 
        if (! ssl_bio) {
@@ -1573,19 +1662,29 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
        BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
        nsc->ssl_io = ssl_bio;
 
-       return OK;
-    } else {
-       BIO_free_all(nsc->ssl_io);
-       nsc->ssl_io = NULL;
+       /*
+        * Since SSL now owns these file descriptors, have it handle the
+        * closing of them instead of netsec_shutdown().
+        */
+
+       nsc->ns_noclose = 1;
 
        return OK;
     }
+    BIO_free_all(nsc->ssl_io);
+    nsc->ssl_io = NULL;
+
 #else /* TLS_SUPPORT */
-       netsec_err(errstr, "TLS is not supported");
+    NMH_UNUSED(nsc);
+    NMH_UNUSED(noverify);
 
+    if (tls) {
+       netsec_err(errstr, "TLS is not supported");
        return NOTOK;
     }
 #endif /* TLS_SUPPORT */
+
+    return OK;
 }
 
 /*
@@ -1602,8 +1701,42 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr)
     }
 
     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(errcode, 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;
     }
 
@@ -1618,6 +1751,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr)
                    SSL_CIPHER_get_name(cipher),
                    SSL_CIPHER_get_bits(cipher, NULL),
                    SSL_CIPHER_get_version(cipher));
+           SSL_SESSION_print_fp(stderr, SSL_get_session(ssl));
        }
     }
 
@@ -1625,6 +1759,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr)
 
     return OK;
 #else /* TLS_SUPPORT */
+    NMH_UNUSED(nsc);
     netsec_err(errstr, "TLS not supported");
 
     return NOTOK;