]> diplodocus.org Git - nmh/commitdiff
More work, but not there just yet.
authorKen Hornstein <kenh@pobox.com>
Mon, 19 Sep 2016 23:59:40 +0000 (19:59 -0400)
committerKen Hornstein <kenh@pobox.com>
Mon, 19 Sep 2016 23:59:40 +0000 (19:59 -0400)
h/netsec.h
h/prototypes.h
sbr/netsec.c
uip/popsbr.c

index 65b867bde760e376c0638031cefe4484c285ce3d..ef0d521d2c491fe4c6fc199c54b3fad7c32700cf 100644 (file)
@@ -37,7 +37,7 @@ void netsec_shutdown(netsec_context *ns_context, int closeflag);
  * fd          - File descriptor of network connection.
  */
 
-void netset_set_fd(netsec_context *ns_context, int fd);
+void netsec_set_fd(netsec_context *ns_context, int fd);
 
 /*
  * Set the userid used to authenticate to this connection.
@@ -89,18 +89,19 @@ void netsec_set_timeout(netsec_context *ns_context, int timeout);
  * Read a "line" from the network.  This reads one CR/LF terminated line.
  * Returns a pointer to a NUL-terminated string.  This memory is valid
  * until the next call to any read function.  Will return an error if
- * the line does not terminated with CR/LF.  Note that this will not work
- * if the data might have embedded NULs.
+ * the line does not terminate with a CR/LF.
  *
  * Arguments:
  *
  * ns_context  - Network security context
+ * length      - Returned length of string
  * errstr      - Error string
  *
  * Returns pointer to string, or NULL on error.
  */
 
-char *netsec_readline(netsec_context *ns_context, char **errstr);
+char *netsec_readline(netsec_context *ns_context, size_t *lenght,
+                     char **errstr);
 
 /*
  * Read bytes from the network.
@@ -217,7 +218,8 @@ enum sasl_message_type {
  *                       Otherwise they will be set to NULL and 0.
  *                       The complete protocol message should be
  *                       stored in outdata/outdatasize, to be free()d
- *                       by the caller.
+ *                       by the caller.  Alternatively, the plugin
+ *                       can choose to send the data on their own.
  * NETSEC_SASL_READ    - Parse and decode a protocol message and extract
  *                       out the SASL payload data.  indata will be set
  *                       to NULL; the callback must read in the necessary
@@ -227,7 +229,9 @@ enum sasl_message_type {
  * NETSEC_SASL_WRITE   - Generate a protocol message to send over the
  *                       network.  indata/indatasize will contain the
  *                       SASL payload data.  outdata/outdatasize should
- *                       contain the complete protocol message.
+ *                       contain the complete protocol message.  Alternatively
+ *                       the plugin can write the data to the network
+ *                       directly.
  * NETSEC_SASL_FINISH  - Process the final SASL message exchange; at
  *                       this point SASL exchange should have completed
  *                       and we should get a message back from the server
@@ -240,6 +244,11 @@ enum sasl_message_type {
  * The callback should return OK on success, NOTOK on failure.  Depending
  * at the point of the authentication exchange, the callback may be asked
  * to generate a cancel message.
+ *
+ * Some higher-level notes in terms of protocol management:
+ *
+ * Any data returned in outdata should consist of allocated data that
+ * the sasl routines is expected to free.
  */
 
 typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype,
@@ -348,3 +357,17 @@ int netsec_set_tls(netsec_context *context, int tls, char **errstr);
  */
 
 int netsec_negotiate_tls(netsec_context *ns_context, char **errstr);
+
+/*
+ * Allocate and format an error string; should be used by plugins
+ * to report errors.
+ *
+ * Arguments:
+ *
+ * errstr      - Error string to be returned
+ * format      - printf(3) format string
+ * ...         - Arguments to printf(3)
+ *
+ */
+
+void netsec_err(char **errstr, const char *format, ...);
index 5c1b5cf157e6f59161b0c10e249fd097bfbcab5f..cb47ca6a8f7e42d116fad78e98e6758fb20d33ae 100644 (file)
@@ -457,6 +457,10 @@ int what_now (char *, int, int, char *, char *,
        int, struct msgs *, char *, int, char *, int);
 int WhatNow(int, char **);
 
+/* Includes trailing NUL */
+
+#define BASE64SIZE(x) ((((x + 2) / 3) * 4) + 1)
+
 /*
  * Copy data from one file to another, converting to base64-encoding.
  *
index 372732c3540ce986f9d215aa7ec5eb78fc9636c6..5bfbea60ccc7b4d0f9f303b025eb2c8a1b2c951c 100644 (file)
@@ -89,12 +89,6 @@ struct _netsec_context {
 #endif /* TLS_SUPPORT */
 };
 
-/*
- * Function to allocate error message strings
- */
-
-static void netsec_err(char **errstr, const char *format, ...);
-
 /*
  * Function to read data from the actual network socket
  */
@@ -327,7 +321,7 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
  */
 
 char *
-netsec_readline(netsec_context *nsc, char **errstr)
+netsec_readline(netsec_context *nsc, size_t *len, char **errstr)
 {
     unsigned char *ptr = nsc->ns_inptr;
     size_t count = 0, offset;
@@ -344,6 +338,8 @@ retry:
            if (count > 1 && *(ptr - 2) == '\r')
                ptr--;
            *--ptr = '\0';
+           if (len)
+               *len = ptr - nsc->ns_inptr;
            nsc->ns_inptr += count;
            nsc->ns_inbuflen -= count;
            return sptr;
@@ -567,6 +563,11 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size,
     const unsigned char *bufptr = buffer;
     int rc, remaining;
 
+    /* Just in case */
+
+    if (size == 0)
+       return OK;
+
     /*
      * If TLS is active, then bypass all of our buffering logic; just
      * write it directly to our BIO.  We have a buffering BIO first in
@@ -624,6 +625,7 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size,
 
 /*
  * Our network printf() routine, which really just calls netsec_vprintf().
+ */
 
 int
 netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
@@ -631,7 +633,7 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
     va_list ap;
     int rc;
 
-    va_start(format, ap);
+    va_start(ap, format);
     rc = netsec_vprintf(nsc, errstr, format, ap);
     va_end(ap);
 
@@ -745,6 +747,13 @@ netsec_flush(netsec_context *nsc, char **errstr)
     }
 #endif /* TLS_SUPPORT */
 
+    /*
+     * Small optimization
+     */
+
+    if (netoutlen == 0)
+       return OK;
+
     /*
      * If SASL security layers are in effect, run the data through
      * sasl_encode() first and then write it.
@@ -1082,15 +1091,16 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
                           &outbuflen, errstr) != OK)
        return NOTOK;
 
-    if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
-       free(outbuf);
-       return NOTOK;
+    if (outbuflen > 0) {
+       if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
+           free(outbuf);
+           return NOTOK;
+       }
+       free(outbuf);
+       if (netsec_flush(nsc, errstr) != OK)
+           return NOTOK;
     }
 
-    free(outbuf);
-
-    if (netsec_flush(nsc, errstr) != OK)
-       return NOTOK;
 
     /*
      * We've written out our first message; enter in the step loop
@@ -1106,9 +1116,11 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
                               errstr) != OK) {
            if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
                                   &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
+               if (outbuflen > 0) {
+                   netsec_write(nsc, outbuf, outbuflen, NULL);
+                   netsec_flush(nsc, NULL);
+                   free(outbuf);
+               }
            }
            return NOTOK;
        }
@@ -1116,16 +1128,19 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
        rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
                              (const char **) &saslbuf, &saslbuflen);
 
-       free(outbuf);
+       if (outbuf)
+           free(outbuf);
 
        if (rc != SASL_OK && rc != SASL_CONTINUE) {
            netsec_err(errstr, "SASL client negotiation failed: %s",
                       sasl_errdetail(nsc->sasl_conn));
            if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
                                   &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
+               if (outbuflen > 0) {
+                   netsec_write(nsc, outbuf, outbuflen, NULL);
+                   netsec_flush(nsc, NULL);
+                   free(outbuf);
+               }
            }
            return NOTOK;
        }
@@ -1134,22 +1149,24 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
                               &outbuf, &outbuflen, errstr) != OK) {
            if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
                                   &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
+               if (outbuflen > 0) {
+                   netsec_write(nsc, outbuf, outbuflen, NULL);
+                   netsec_flush(nsc, NULL);
+                   free(outbuf);
+               }
            }
            return NOTOK;
        }
 
-       if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
+       if (outbuflen > 0) {
+           if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
+               free(outbuf);
+               return NOTOK;
+           }
            free(outbuf);
-           return NOTOK;
+           if (netsec_flush(nsc, errstr) != OK)
+               return NOTOK;
        }
-
-       free(outbuf);
-
-       if (netsec_flush(nsc, errstr) != OK)
-           return NOTOK;
     }
 
     /*
@@ -1455,7 +1472,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr)
  * Generate an (allocated) error string
  */
 
-static void
+void
 netsec_err(char **errstr, const char *fmt, ...)
 {
     va_list ap;
index 0153dcb15d6fc9bdd0e5bdab480907c6f1c53dd3..7126b95002f59ee2b3c87287e03b7a17091de430 100644 (file)
@@ -32,13 +32,16 @@ static int multiline(void);
 
 static int traverse (int (*)(char *), const char *, ...);
 static int vcommand(const char *, va_list);
-static int getline (char *, int, FILE *);
+static int pop_getline (char *, int, netsec_context *);
 static int sasl_fgetc(FILE *);
 static int putline (char *, FILE *);
+static int pop_sasl_callback(enum sasl_message_type, unsigned const char *,
+                            unsigned int, unsigned char **, unsigned int *,
+                            char **);
 
 
-int
-check_mech(char *server_mechs, size_t server_mechs_size, char *mech)
+static int
+check_mech(char *server_mechs, size_t server_mechs_size)
 {
   int status, sasl_capability = 0;
 
@@ -81,17 +84,6 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech)
        return NOTOK;
     }
 
-    /*
-     * If we received a preferred mechanism, see if the server supports it.
-     */
-
-    if (mech && stringdex(mech, server_mechs) == -1) {
-       snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is "
-                "not in list of supported mechanisms:\n%s",
-                mech, server_mechs);
-       return NOTOK;
-    }
-
     return OK;
 }
 
@@ -111,6 +103,7 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech)
       } \
     }
 
+#if 0
 int
 pop_auth_sasl(char *user, char *host, char *mech)
 {
@@ -389,7 +382,6 @@ sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret)
 
     return SASL_OK;
 }
-#endif /* CYRUS_SASL */
 
 int
 pop_auth_xoauth(const char *client_res)
@@ -411,7 +403,7 @@ pop_auth_xoauth(const char *client_res)
     /* Then we're supposed to send an empty response ("\r\n"). */
     return command("");
 }
-
+#endif /* CYRUS_SASL */
 /*
  * Split string containing proxy command into an array of arguments
  * suitable for passing to exec. Returned array must be freed. Shouldn't
@@ -465,23 +457,9 @@ int
 pop_init (char *host, char *port, char *user, char *pass, char *proxy,
          int snoop, int sasl, char *mech, int tls, const char *oauth_svc)
 {
-    int fd1;
+    int fd1, fd2;
     char buffer[BUFSIZ];
     char *errstr;
-#ifndef CYRUS_SASL
-    NMH_UNUSED (sasl);
-    NMH_UNUSED (mech);
-#endif /* ! CYRUS_SASL */
-
-#ifdef OAUTH_SUPPORT
-    if (oauth_svc != NULL) {
-       xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc,
-                                              snoop ? stderr : NULL);
-    }
-#else
-    NMH_UNUSED (oauth_svc);
-    NMH_UNUSED (xoauth_client_res);
-#endif /* OAUTH_SUPPORT */
 
     nsc = netsec_init();  
 
@@ -579,23 +557,24 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy,
        }
     }
 
-    switch (getline (response, sizeof response, input)) {
+    switch (pop_getline (response, sizeof response, nsc)) {
        case OK: 
            if (poprint)
                fprintf (stderr, "<--- %s\n", response);
            if (*response == '+') {
-#  ifdef CYRUS_SASL
                if (sasl) {
-                   if (pop_auth_sasl(user, host, mech) != NOTOK)
-                       return OK;
-               } else
-#  endif /* CYRUS_SASL */
-#  if OAUTH_SUPPORT
-               if (xoauth_client_res != NULL) {
-                   if (pop_auth_xoauth(xoauth_client_res) != NOTOK)
-                       return OK;
+                   char server_mechs[256];
+                   if (check_mech(server_mechs, sizeof(server_mechs) != OK))
+                       return NOTOK;
+                   if (netsec_negotiate_sasl(nsc, server_mechs,
+                                             &errstr) != OK) {
+                       strncpy(response, errstr, sizeof(response));
+                       response[sizeof(response) - 1] = '\0';
+                       free(errstr);
+                       return NOTOK;
+                   }
+                   return OK;
                } else
-#  endif /* OAUTH_SUPPORT */
                if (command ("USER %s", user) != NOTOK
                    && command ("%s %s", (pophack++, "PASS"),
                                        pass) != NOTOK)
@@ -610,8 +589,8 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy,
        case DONE: 
            if (poprint)            
                fprintf (stderr, "%s\n", response);
-           fclose (input);
-           fclose (output);
+           netsec_shutdown(nsc, 1);
+           nsc = NULL;
            return NOTOK;
     }
 
@@ -619,6 +598,112 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy,
 }
 
 
+/*
+ * Our SASL callback; we are given SASL tokens and then have to format
+ * them according to the protocol requirements, and then process incoming
+ * messages and feed them back into the SASL library.
+ */
+
+static int
+pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
+                 unsigned int indatasize, unsigned char **outdata,
+                 unsigned int *outdatasize, char **errstr)
+{
+    int rc;
+    char *mech, *line;
+    size_t len, b64len;
+
+    switch (mtype) {
+    case NETSEC_SASL_START:
+       /*
+        * Generate our AUTH message, but there is a wrinkle.
+        *
+        * Technically, according to RFC 5034, if your command INCLUDING
+        * an initial response exceeds 255 octets (including CRLF), you
+        * can't issue this all in one go, but have to just issue the
+        * AUTH command, wait for a blank initial response, and then
+        * send your data.
+        */
+
+       mech = netsec_get_sasl_mechanism(nsc);
+
+       if (indatasize) {
+           char *b64data;
+           b64data = mh_xmalloc(BASE64SIZE(indatasize));
+           writeBase64raw(indata, indatasize, line);
+           b64len = strlen(b64data);
+
+           /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
+           len = b64len + 8 + strlen(mech);
+           if (len > 255) {
+               rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
+               if (rc)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+               line = netsec_readline(nsc, &len, errstr);
+               if (! line)
+                   return NOTOK;
+               /*
+                * If the protocol is being followed correctly, should just
+                * be a "+ ", nothing else.
+                */
+               if (len != 2 || strcmp(line, "+ ") != 0) {
+                   netsec_err(errstr, "Did not get expected blank response "
+                              "for initial challenge response");
+                   return NOTOK;
+               }
+               rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
+               free(b64data);
+               if (rc != OK)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+           } else {
+               rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
+                                  b64data);
+               free(b64data);
+               if (rc != OK)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+           }
+       } else {
+           if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
+               return NOTOK;
+           if (netsec_flush(nsc, errstr) != OK)
+               return NOTOK;
+       }
+
+       break;
+
+       /*
+        * We should get one line back, with our base64 data.  Decode that
+        * and feed it back in.
+        */
+    case NETSEC_SASL_READ:
+       line = netsec_readline(nsc, &len, errstr);
+
+       if (line == NULL)
+           return NOTOK;
+       if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0) {
+           netsec_err(errstr, "Invalid format for SASL response");
+           return NOTOK;
+       }
+
+       if (len == 2) {
+           *outdata = NULL;
+           *outdatalen = 0;
+       } else {
+           rc = decodeBase64(line + 2, &outdata, &outdatasize, 0, NULL);
+           if (rc != OK)
+               return NOTOK;
+       }
+       break;
+
+    return OK;
+}
+
 /*
  * Find out number of messages available
  * and their total size.
@@ -820,7 +905,7 @@ vcommand (const char *fmt, va_list ap)
        fprintf(stderr, "(decrypted) ");
 #endif /* CYRUS_SASL */
 
-    switch (sasl_getline (response, sizeof response, input)) {
+    switch (pop_getline (response, sizeof response, nsc)) {
        case OK: 
            if (poprint)
                fprintf (stderr, "<--- %s\n", response);
@@ -842,7 +927,7 @@ multiline (void)
 {
     char buffer[BUFSIZ + TRMLEN];
 
-    if (sasl_getline (buffer, sizeof buffer, input) != OK)
+    if (pop_getline (buffer, sizeof buffer, nsc) != OK)
        return NOTOK;
 #ifdef DEBUG
     if (poprint) {
@@ -866,36 +951,43 @@ multiline (void)
 }
 
 /*
- * Note that these functions have been modified to deal with layer encryption
- * in the SASL case
+ * This is now just a thin wrapper around netsec_readline().
  */
 
 static int
-sasl_getline (char *s, int n, FILE *iop)
+pop_getline (char *s, int n, netsec_context *ns)
 {
     int c = -2;
     char *p;
+    size_t len, destlen;
+    int rc;
+    char *errstr;
 
-    p = s;
-    while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2) 
-       if ((*p++ = c) == '\n')
-           break;
-    if (c == -2)
-       return NOTOK;
-    if (ferror (iop) && c != EOF) {
-       strncpy (response, "error on connection", sizeof(response));
+    p = netsec_readline(ns, &len, &errstr);
+
+    if (p == NULL) {
+       strncpy(response, errstr, sizeof(response));
+       response[sizeof(response) - 1] = '\0';
+       free(errstr);
        return NOTOK;
     }
-    if (c == EOF && p == s) {
-       strncpy (response, "connection closed by foreign host", sizeof(response));
-       return DONE;
-    }
-    *p = 0;
-    if (*--p == '\n')
-       *p = 0;
-    if (p > s  &&  *--p == '\r')
-       *p = 0;
 
+    /*
+     * If we had an error, it should have been returned already.  Since
+     * netsec_readline() strips off the CR-LF ending, just copy the existing
+     * buffer into response now.
+     *
+     * We get a length back from netsec_readline, but the rest of the POP
+     * code doesn't handle it; the assumptions are that everything from
+     * the network can be respresented as C strings.  That should get fixed
+     * someday.
+     */
+
+    destlen = len > n - 1 ? len : n - 1;
+
+    memcpy(s, p, destlen);
+    s[destlen] = '\0';
+    
     return OK;
 }