]> diplodocus.org Git - nmh/blobdiff - sbr/netsec.c
Put, rather than print, single characters.
[nmh] / sbr / netsec.c
index f6217c1d17a58b8cc482f53451a9e2af01008097..788b23bf0411036821e695d771a25ad3e3536f84 100644 (file)
@@ -71,6 +71,8 @@ struct _netsec_context {
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
     char *sasl_mech;           /* User-requested mechanism */
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
     char *sasl_mech;           /* User-requested mechanism */
+    char *sasl_chosen_mech;    /* Mechanism chosen by SASL */
+    netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
 #ifdef OAUTH_SUPPORT
     char *oauth_service;       /* OAuth2 service name */
 #endif /* OAUTH_SUPPORT */
 #ifdef OAUTH_SUPPORT
     char *oauth_service;       /* OAuth2 service name */
 #endif /* OAUTH_SUPPORT */
@@ -78,11 +80,9 @@ struct _netsec_context {
     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 */
     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 */
-    netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
     sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */
     nmh_creds_t sasl_creds;    /* Credentials (username/password) */
     sasl_secret_t *sasl_secret;        /* SASL password structure */
     sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */
     nmh_creds_t sasl_creds;    /* Credentials (username/password) */
     sasl_secret_t *sasl_secret;        /* SASL password structure */
-    char *sasl_chosen_mech;    /* Mechanism chosen by SASL */
     int sasl_seclayer;         /* If true, SASL security layer is enabled */
     char *sasl_tmpbuf;         /* Temporary read buffer for decodes */
     size_t sasl_maxbufsize;    /* Maximum negotiated SASL buffer size */
     int sasl_seclayer;         /* If true, SASL security layer is enabled */
     char *sasl_tmpbuf;         /* Temporary read buffer for decodes */
     size_t sasl_maxbufsize;    /* Maximum negotiated SASL buffer size */
@@ -99,20 +99,35 @@ struct _netsec_context {
 
 static int netsec_fillread(netsec_context *ns_context, char **errstr);
 
 
 static int netsec_fillread(netsec_context *ns_context, char **errstr);
 
+/*
+ * Code to check the ASCII content of a byte array.
+ */
+
+static int checkascii(const unsigned char *byte, size_t len);
+
 /*
  * How this code works, in general.
  *
 /*
  * How this code works, in general.
  *
- * _If_ we are using no encryption or SASL encryption, then we buffer the
- * network data through ns_inbuffer and ns_outbuffer.  That should be
- * relatively self-explanatory.
+ * _If_ we are using no encryption then we buffer the network data
+ * through ns_inbuffer and ns_outbuffer.  That should be relatively
+ * self-explanatory.
  *
  *
- * If we are using SSL for encryption, then use a buffering BIO for output
- * (that just easier).  Still do buffering for reads; when we need more
- * data we call the BIO_read() function to fill our local buffer.
+ * If we use encryption, then ns_inbuffer and ns_outbuffer contain the
+ * cleartext data.  When it comes time to send the encrypted data on the
+ * (either from a flush or the buffer is full) we either use BIO_write()
+ * for TLS or sasl_encode() (followed by a write() for Cyrus-SASL.  For
+ * reads we either use BIO_read() (TLS) or do a network read into a
+ * temporary buffer and use sasl_decode() (Cyrus-SASL).  Note that if
+ * negotiate TLS then we disable SASL encryption.
  *
  *
- * For SASL, we make use of (for now) the Cyrus-SASL library.  For some
- * mechanisms, we implement those mechanisms directly since the Cyrus SASL
- * library doesn't support them (like OAuth).
+ * We used to use a buffering BIO for the reads/writes for TLS, but it
+ * ended up being complicated to special-case the buffering for everything
+ * except TLS, so the buffering is now unified, no matter which encryption
+ * method is being used (even none).
+ *
+ * For SASL authentication, we make use of (for now) the Cyrus-SASL
+ * library.  For some mechanisms, we implement those mechanisms directly
+ * since the Cyrus SASL library doesn't support them (like OAuth).
  */
 
 /*
  */
 
 /*
@@ -122,8 +137,9 @@ static int netsec_fillread(netsec_context *ns_context, char **errstr);
 netsec_context *
 netsec_init(void)
 {
 netsec_context *
 netsec_init(void)
 {
-    netsec_context *nsc = mh_xmalloc(sizeof(*nsc));
+    netsec_context *nsc;
 
 
+    NEW(nsc);
     nsc->ns_readfd = -1;
     nsc->ns_writefd = -1;
     nsc->ns_snoop = 0;
     nsc->ns_readfd = -1;
     nsc->ns_writefd = -1;
     nsc->ns_snoop = 0;
@@ -141,6 +157,8 @@ netsec_init(void)
     nsc->ns_outptr = nsc->ns_outbuffer;
     nsc->ns_outbuflen = 0;
     nsc->sasl_mech = NULL;
     nsc->ns_outptr = nsc->ns_outbuffer;
     nsc->ns_outbuflen = 0;
     nsc->sasl_mech = NULL;
+    nsc->sasl_chosen_mech = NULL;
+    nsc->sasl_proto_cb = NULL;
 #ifdef OAUTH_SUPPORT
     nsc->oauth_service = NULL;
 #endif /* OAUTH_SUPPORT */
 #ifdef OAUTH_SUPPORT
     nsc->oauth_service = NULL;
 #endif /* OAUTH_SUPPORT */
@@ -150,7 +168,6 @@ netsec_init(void)
     nsc->sasl_cbs = NULL;
     nsc->sasl_creds = NULL;
     nsc->sasl_secret = NULL;
     nsc->sasl_cbs = NULL;
     nsc->sasl_creds = NULL;
     nsc->sasl_secret = NULL;
-    nsc->sasl_chosen_mech = NULL;
     nsc->sasl_ssf = 0;
     nsc->sasl_seclayer = 0;
     nsc->sasl_tmpbuf = NULL;
     nsc->sasl_ssf = 0;
     nsc->sasl_seclayer = 0;
     nsc->sasl_tmpbuf = NULL;
@@ -179,6 +196,8 @@ netsec_shutdown(netsec_context *nsc, int closeflag)
        free(nsc->ns_outbuffer);
     if (nsc->sasl_mech)
        free(nsc->sasl_mech);
        free(nsc->ns_outbuffer);
     if (nsc->sasl_mech)
        free(nsc->sasl_mech);
+    if (nsc->sasl_chosen_mech)
+       free(nsc->sasl_chosen_mech);
 #ifdef OAUTH_SERVICE
     if (nsc->oauth_service)
        free(nsc->oauth_service);
 #ifdef OAUTH_SERVICE
     if (nsc->oauth_service)
        free(nsc->oauth_service);
@@ -202,8 +221,6 @@ netsec_shutdown(netsec_context *nsc, int closeflag)
        }
        free(nsc->sasl_secret);
     }
        }
        free(nsc->sasl_secret);
     }
-    if (nsc->sasl_chosen_mech)
-       free(nsc->sasl_chosen_mech);
     if (nsc->sasl_tmpbuf)
        free(nsc->sasl_tmpbuf);
 #endif /* CYRUS_SASL */
     if (nsc->sasl_tmpbuf)
        free(nsc->sasl_tmpbuf);
 #endif /* CYRUS_SASL */
@@ -286,7 +303,7 @@ void
 netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len,
                         void *context)
 {
 netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len,
                         void *context)
 {
-    const char *decoded;
+    unsigned char *decoded;
     size_t decodedlen;
     int offset;
     NMH_UNUSED(nsc);
     size_t decodedlen;
     int offset;
     NMH_UNUSED(nsc);
@@ -303,16 +320,48 @@ netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len,
     }
 
     if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) {
     }
 
     if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) {
-       char *hexified;
-       hexify((const unsigned char *) decoded, decodedlen, &hexified);
-       fprintf(stderr, "b64<%s>\n", hexified);
-       free(hexified);
-       free((char *) decoded);
+       /*
+        * Some mechanisms produce large binary tokens, which aren't really
+        * readable.  So let's do a simple heuristic.  If the token is greater
+        * than 100 characters _and_ the first 100 bytes are more than 50%
+        * non-ASCII, then don't print the decoded buffer, just the
+        * base64 text.
+        */
+       if (decodedlen > 100 && !checkascii(decoded, 100)) {
+           fprintf(stderr, "%.*s\n", (int) len, string);
+       } else {
+           char *hexified;
+           hexify(decoded, decodedlen, &hexified);
+           fprintf(stderr, "b64<%s>\n", hexified);
+           free(hexified);
+       }
+       free(decoded);
     } else {
        fprintf(stderr, "%.*s\n", (int) len, string);
     }
 }
 
     } else {
        fprintf(stderr, "%.*s\n", (int) len, string);
     }
 }
 
+/*
+ * If the ASCII content is > 50%, return 1
+ */
+
+static int
+checkascii(const unsigned char *bytes, size_t len)
+{
+    size_t count = 0, half = len / 2;
+
+    while (len-- > 0) {
+       if (isascii(*bytes) && isprint(*bytes) && ++count > half)
+           return 1;
+       bytes++;
+       /* No chance by this point */
+       if (count + len < half)
+           return 0;
+    }
+
+    return 0;
+}
+
 /*
  * Set the read timeout for this connection
  */
 /*
  * Set the read timeout for this connection
  */
@@ -372,11 +421,12 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
  *
  * - Unlike every other function, we return a pointer to the
  *   existing buffer.  This pointer is valid until you call another
  *
  * - Unlike every other function, we return a pointer to the
  *   existing buffer.  This pointer is valid until you call another
- *   read functiona again.
- * - We NUL-terminated the buffer right at the end, before the terminator.
+ *   read function again.
+ * - We NUL-terminate the buffer right at the end, before the CR-LF terminator.
  * - Technically we look for a LF; if we find a CR right before it, then
  *   we back up one.
  * - Technically we look for a LF; if we find a CR right before it, then
  *   we back up one.
- * - If your data may contain embedded NULs, this won't work.
+ * - If your data may contain embedded NULs, this won't work.  You should
+ *   be using netsec_read() in that case.
  */
 
 char *
  */
 
 char *
@@ -470,7 +520,9 @@ netsec_fillread(netsec_context *nsc, char **errstr)
        nsc->ns_inptr = nsc->ns_inbuffer;
     }
 
        nsc->ns_inptr = nsc->ns_inbuffer;
     }
 
+#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
 retry:
 retry:
+#endif /* CYRUS_SASL || TLS_SUPPORT */
     /*
      * If we are using TLS and there's anything pending, then skip the
      * select call
     /*
      * If we are using TLS and there's anything pending, then skip the
      * select call
@@ -508,10 +560,38 @@ retry:
         */
     }
 
         */
     }
 
+    /*
+     * Some explanation:
+     *
+     * startoffset is the offset from the beginning of the input
+     * buffer to data that is in our input buffer, but has not yet
+     * been consumed.  This can be non-zero if functions like
+     * netsec_readline() leave leftover data.
+     *
+     * remaining is the remaining amount of unconsumed data in the input
+     * buffer.
+     *
+     * end is a pointer to the end of the valid data + 1; it's where
+     * the next read should go.
+     */
+
     startoffset = nsc->ns_inptr - nsc->ns_inbuffer;
     remaining = nsc->ns_inbufsize - (startoffset + nsc->ns_inbuflen);
     end = nsc->ns_inptr + nsc->ns_inbuflen;
 
     startoffset = nsc->ns_inptr - nsc->ns_inbuffer;
     remaining = nsc->ns_inbufsize - (startoffset + nsc->ns_inbuflen);
     end = nsc->ns_inptr + nsc->ns_inbuflen;
 
+    /*
+     * If we're past the halfway point in our read buffers, shuffle everything
+     * back to the beginning.
+     */
+
+    if (startoffset > nsc->ns_inbufsize / 2) {
+       memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen);
+       nsc->ns_inptr = nsc->ns_inbuffer;
+       startoffset = 0;
+       remaining = nsc->ns_inbufsize - nsc->ns_inbuflen;
+       end = nsc->ns_inptr + nsc->ns_inbuflen;
+    }
+
     /*
      * If we are using TLS, then just read via the BIO.  But we still
      * use our local buffer.
     /*
      * If we are using TLS, then just read via the BIO.  But we still
      * use our local buffer.
@@ -520,11 +600,36 @@ retry:
     if (nsc->tls_active) {
        rc = BIO_read(nsc->ssl_io, end, remaining);
        if (rc == 0) {
     if (nsc->tls_active) {
        rc = BIO_read(nsc->ssl_io, end, remaining);
        if (rc == 0) {
+           SSL *ssl;
+           int errcode;
+
+           /*
+            * Check to see if we're supposed to retry; if so,
+            * then go back and read again.
+            */
+
+           if (BIO_should_retry(nsc->ssl_io))
+               goto retry;
+
            /*
            /*
-            * Either EOF, or possibly an error.  Either way, it was probably
-            * unexpected, so treat as error.
+            * Okay, fine.  Get the real error out of the SSL context.
             */
             */
-           netsec_err(errstr, "TLS peer aborted connection");
+
+           if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+               netsec_err(errstr, "SSL_read() returned 0, but cannot "
+                          "retrieve SSL context");
+               return NOTOK;
+           }
+
+           errcode = SSL_get_error(ssl, rc);
+           if (errcode == SSL_ERROR_ZERO_RETURN) {
+               netsec_err(errstr, "TLS peer closed remote connection");
+           } else {
+               netsec_err(errstr, "TLS network read failed: %s",
+                          ERR_error_string(ERR_peek_last_error(), NULL));
+           }
+           if (nsc->ns_snoop)
+               ERR_print_errors_fp(stderr);
            return NOTOK;
        } else if (rc < 0) {
            /* Definitely an error */
            return NOTOK;
        } else if (rc < 0) {
            /* Definitely an error */
@@ -612,16 +717,6 @@ retry:
 #endif /* CYRUS_SASL */
        nsc->ns_inbuflen += rc;
 
 #endif /* CYRUS_SASL */
        nsc->ns_inbuflen += rc;
 
-    /*
-     * If we're past the halfway point in our read buffers, shuffle everything
-     * back to the beginning.
-     */
-
-    if (startoffset > nsc->ns_inbufsize / 2) {
-       memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen);
-       nsc->ns_inptr = nsc->ns_inbuffer;
-    }
-
     return OK;
 }
 
     return OK;
 }
 
@@ -907,6 +1002,13 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
        return NOTOK;
     }
 
        return NOTOK;
     }
 
+    nsc->sasl_hostname = mh_xstrdup(hostname);
+#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
     /*
      * According to the RFC, mechanisms can only be uppercase letter, numbers,
      * and a hypen or underscore.  So make sure we uppercase any letters
@@ -915,7 +1017,7 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
 
     if (mechanism) {
        char *p;
 
     if (mechanism) {
        char *p;
-       nsc->sasl_mech = getcpy(mechanism);
+       nsc->sasl_mech = mh_xstrdup(mechanism);
 
        for (p = nsc->sasl_mech; *p; p++)
            if (isascii((unsigned char) *p))    /* Just in case */
 
        for (p = nsc->sasl_mech; *p; p++)
            if (isascii((unsigned char) *p))    /* Just in case */
@@ -923,14 +1025,8 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
     }
 
     nsc->sasl_proto_cb = callback;
     }
 
     nsc->sasl_proto_cb = callback;
-    nsc->sasl_hostname = getcpy(hostname);
 
     return OK;
 
     return OK;
-#else /* CYRUS_SASL */
-    netsec_err(errstr, "SASL is not supported");
-
-    return NOTOK;
-#endif /* CYRUS_SASL */
 }
 
 #ifdef CYRUS_SASL
 }
 
 #ifdef CYRUS_SASL
@@ -950,14 +1046,14 @@ int netsec_get_user(void *context, int id, const char **result,
     if (nsc->ns_userid == NULL) {
        /*
         * Pass the 1 third argument to nmh_get_credentials() so that
     if (nsc->ns_userid == NULL) {
        /*
         * Pass the 1 third argument to nmh_get_credentials() so that
-        * a defauly user if the -user switch wasn't supplied, and so
+        * 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) {
         * 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) {
-           nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
+           NEW(nsc->sasl_creds);
            nsc->sasl_creds->user = NULL;
            nsc->sasl_creds->password = NULL;
        }
            nsc->sasl_creds->user = NULL;
            nsc->sasl_creds->password = NULL;
        }
@@ -997,7 +1093,7 @@ netsec_get_password(sasl_conn_t *conn, void *context, int id,
        return SASL_BADPARAM;
 
     if (nsc->sasl_creds == NULL) {
        return SASL_BADPARAM;
 
     if (nsc->sasl_creds == NULL) {
-       nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
+       NEW(nsc->sasl_creds);
        nsc->sasl_creds->user = NULL;
        nsc->sasl_creds->password = NULL;
     }
        nsc->sasl_creds->user = NULL;
        nsc->sasl_creds->password = NULL;
     }
@@ -1057,7 +1153,9 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
     unsigned char *xoauth_client_res;
     size_t xoauth_client_res_len;
 #endif /* OAUTH_SUPPORT */
     unsigned char *xoauth_client_res;
     size_t xoauth_client_res_len;
 #endif /* OAUTH_SUPPORT */
+#if defined CYRUS_SASL || defined OAUTH_SUPPORT
     int rc;
     int rc;
+#endif /* CYRUS_SASL || OAUTH_SUPPORT */
 
     /*
      * If we've been passed a requested mechanism, check our mechanism
 
     /*
      * If we've been passed a requested mechanism, check our mechanism
@@ -1102,7 +1200,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
            return NOTOK;
        }
 
            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,
 
        if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service,
                               &xoauth_client_res, &xoauth_client_res_len,
@@ -1132,7 +1230,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
            /*
             * We're going to assume the error here is a JSON response;
             * we ignore it and send a blank message in response.  We should
            /*
             * We're going to assume the error here is a JSON response;
             * we ignore it and send a blank message in response.  We should
-            * then get either an +OK or -ERR
+            * then get a failure messages with a useful error.  We should
+            * NOT get a success message at this point.
             */
            free(*errstr);
            nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
             */
            free(*errstr);
            nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
@@ -1321,7 +1420,18 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
 
     return OK;
 #else
 
     return OK;
 #else
-    netsec_err(errstr, "SASL not supported");
+    /*
+     * If we're at this point, then either we have NEITHER OAuth2 or
+     * Cyrus-SASL compiled in, or have OAuth2 but didn't give the XOAUTH2
+     * mechanism on the command line.
+     */
+
+    if (! nsc->sasl_mech)
+       netsec_err(errstr, "SASL library support not available; please "
+                  "specify a SASL mechanism to use");
+    else
+       netsec_err(errstr, "No support for the %s SASL mechanism",
+                  nsc->sasl_mech);
 
     return NOTOK;
 #endif /* CYRUS_SASL */
 
     return NOTOK;
 #endif /* CYRUS_SASL */
@@ -1334,11 +1444,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
 char *
 netsec_get_sasl_mechanism(netsec_context *nsc)
 {
 char *
 netsec_get_sasl_mechanism(netsec_context *nsc)
 {
-#ifdef CYRUS_SASL
     return nsc->sasl_chosen_mech;
     return nsc->sasl_chosen_mech;
-#else /* CYRUS_SASL */
-    return NULL;
-#endif /* CYRUS_SASL */
 }
 
 /*
 }
 
 /*
@@ -1352,6 +1458,8 @@ netsec_set_oauth_service(netsec_context *nsc, const char *service)
     nsc->oauth_service = getcpy(service);
     return OK;
 #else /* OAUTH_SUPPORT */
     nsc->oauth_service = getcpy(service);
     return OK;
 #else /* OAUTH_SUPPORT */
+    NMH_UNUSED(nsc);
+    NMH_UNUSED(service);
     return NOTOK;
 #endif /* OAUTH_SUPPORT */
 }
     return NOTOK;
 #endif /* OAUTH_SUPPORT */
 }
@@ -1424,12 +1532,11 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
         * supplied socket.
         *
         * Then we create an SSL BIO, and assign our current SSL connection
         * supplied socket.
         *
         * Then we create an SSL BIO, and assign our current SSL connection
-        * to it.  We then create a buffer BIO and push it in front of our
-        * SSL BIO.  So the chain looks like:
-        *
-        * buffer BIO -> SSL BIO -> socket BIO.
+        * to it.  This is done so our code stays simple if we want to use
+        * any buffering BIOs (right now we do our own buffering).
+        * So the chain looks like:
         *
         *
-        * So writes and reads are buffered (we mostly care about writes).
+        * SSL BIO -> socket BIO.
         */
 
        rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);
         */
 
        rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);