]> diplodocus.org Git - nmh/commitdiff
Fleshed out some more, but we're not there just yet.
authorKen Hornstein <kenh@pobox.com>
Thu, 15 Sep 2016 20:00:07 +0000 (16:00 -0400)
committerKen Hornstein <kenh@pobox.com>
Thu, 15 Sep 2016 20:00:07 +0000 (16:00 -0400)
h/netsec.h
sbr/netsec.c

index f5abbcb5c80af27c22125a9360304c30e8faa531..98cfe256bef12a1d790921924d57ab4fe5496acd 100644 (file)
@@ -38,6 +38,27 @@ void netsec_shutdown(netsec_context *ns_context);
 
 void netset_set_fd(netsec_context *ns_context, int fd);
 
 
 void netset_set_fd(netsec_context *ns_context, int fd);
 
+/*
+ * Set the hostname of the server we're connecting to.
+ *
+ * Arguments:
+ * ns_context  - Network security context
+ * hostname    - Server hostname
+ */
+
+void netsec_set_hostname(netsec_context *ns_context, const char *hostname);
+
+/*
+ * Set the userid used to authenticate to this connection.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * userid      - Userid to be used for authentication.  Cannot be NULL.
+ */
+
+void netsec_set_userid(netsec_context *ns_context, const char *userid);
+
 /*
  * Sets "snoop" status; if snoop is set to a nonzero value, network traffic
  * will be logged on standard error.
 /*
  * Sets "snoop" status; if snoop is set to a nonzero value, network traffic
  * will be logged on standard error.
@@ -99,6 +120,19 @@ ssize_t netsec_read(netsec_context *ns_context, void *buffer, size_t size,
 int netsec_printf(netsec_context *ns_context, char **errstr,
                  const char *format, ...);
 
 int netsec_printf(netsec_context *ns_context, char **errstr,
                  const char *format, ...);
 
+/*
+ * Flush any buffered bytes to the network.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * errstr      - Error string
+ *
+ * Returns OK on success, NOTOK on error.
+ */
+
+int netsec_flush(netsec_context *ns_context, char **errstr);
+
 /*
  * Enumerated types for the type of message we are sending/receiving.
  */
 /*
  * Enumerated types for the type of message we are sending/receiving.
  */
@@ -173,18 +207,50 @@ typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype,
  * Arguments:
  *
  * ns_context  - Network security context
  * Arguments:
  *
  * ns_context  - Network security context
+ * hostname    - Fully qualified hostname of remote host.
  * service     - Service name (set to NULL to disable SASL).
  * mechlist    - A space-separated list of mechanisms supported by the server.
  * mechanism   - The mechanism desired by the user.  If NULL, the SASL
  *               library will attempt to negotiate the best mechanism.
  * callback    - SASL protocol callbacks 
  * service     - Service name (set to NULL to disable SASL).
  * mechlist    - A space-separated list of mechanisms supported by the server.
  * mechanism   - The mechanism desired by the user.  If NULL, the SASL
  *               library will attempt to negotiate the best mechanism.
  * callback    - SASL protocol callbacks 
+ * errstr      - Error string.
  *
  * Returns NOTOK if SASL is not supported.
  */
 
  *
  * Returns NOTOK if SASL is not supported.
  */
 
-int netsec_set_sasl_params(netsec_context *ns_context, const char *service,
-                          const char *mechanism,
-                          netsec_sasl_callback callback);
+int netsec_set_sasl_params(netsec_context *ns_context, const char *hostname,
+                          const char *service, const char *mechanism,
+                          netsec_sasl_callback callback, char **errstr);
+
+/*
+ * Start SASL negotiation.  The Netsec library will use the callbacks
+ * supplied in netsec_set_sasl_params() to format and parse the protocol
+ * messages.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * mechlist    - Space-separated list of supported SASL mechanisms
+ * errstr      - An error string to be returned upon error.
+ *
+ * Returns OK on success, NOTOK on failure.
+ */
+
+int netsec_negotiate_sasl(netsec_context *ns_context, const char *mechlist,
+                         char **errstr);
+
+/*
+ * Returns the chosen SASL mechanism by the SASL library or netsec.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ *
+ * Returns a string containing the chosen mech, or NULL if SASL is not
+ * supported or in use.
+ */
+
+char *netsec_get_sasl_mechanism(netsec_context *ns_context);
 
 /*
  * Set the OAuth service name used to retrieve the OAuth parameters from
 
 /*
  * Set the OAuth service name used to retrieve the OAuth parameters from
@@ -224,9 +290,10 @@ int netsec_set_tls(netsec_context *context, int tls, char **errstr);
  *
  * Arguments:
  *
  *
  * Arguments:
  *
+ * ns_context  - Network security context
  * errstr      - Error string upon failure.
  *
  * Returns OK on success, NOTOK on failure.
  */
 
  * errstr      - Error string upon failure.
  *
  * Returns OK on success, NOTOK on failure.
  */
 
-int netsec_negotiate_tls(char **errstr);
+int netsec_negotiate_tls(netsec_context *ns_context, char **errstr);
index cd33df9d902f2cae52528b9bbc2197d688c3b4f1..7cc9e32b82e308c4818a5b6fc53f90cc6398b591 100644 (file)
      Provide this prototype for earlier versions. */
   typedef int (*sasl_callback_ft)();
 # endif /* SASL_VERSION_FULL < 0x020125 */
      Provide this prototype for earlier versions. */
   typedef int (*sasl_callback_ft)();
 # endif /* SASL_VERSION_FULL < 0x020125 */
+
+static int netsec_get_user(void *context, int id, const char **result,
+                          unsigned int *len);
+static int netsec_get_password(sasl_conn_t *conn, void *context, int id,
+                              sasl_secret_t **psecret);
+
+static int sasl_initialized = 0;
 #endif /* CYRUS_SASL */
 
 #ifdef TLS_SUPPORT
 #endif /* CYRUS_SASL */
 
 #ifdef TLS_SUPPORT
@@ -42,6 +49,8 @@ static SSL_CTX *sslctx = NULL;                /* SSL Context */
 struct _netsec_context {
     int ns_fd;                 /* Descriptor for network connection */
     int ns_snoop;              /* If true, display network data */
 struct _netsec_context {
     int ns_fd;                 /* Descriptor for network connection */
     int ns_snoop;              /* If true, display network data */
+    char *ns_hostname;         /* Hostname we've connected to */
+    char *ns_userid;           /* Userid for authentication */
     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 */
     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 */
@@ -51,11 +60,15 @@ struct _netsec_context {
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
 #ifdef CYRUS_SASL
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
 #ifdef CYRUS_SASL
-    char *sasl_service;                /* SASL service name */
     char *sasl_mech;           /* User-requested mechanism */
     sasl_conn_t *sasl_conn;    /* SASL connection context */
     sasl_ssf_t sasl_ssf;       /* SASL Security Strength Factor */
     char *sasl_mech;           /* User-requested mechanism */
     sasl_conn_t *sasl_conn;    /* SASL connection context */
     sasl_ssf_t sasl_ssf;       /* SASL Security Strength Factor */
-    netsec_sasl_callback sasl_cb; /* SASL callback */
+    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 */
+    char *sasl_chosen_mech;    /* Mechanism chosen by SASL */
+    int sasl_enabled;          /* If true, SASL is enabled */
 #endif /* CYRUS_SASL */
 #ifdef TLS_SUPPORT
     BIO *ssl_io;               /* BIO used for connection I/O */
 #endif /* CYRUS_SASL */
 #ifdef TLS_SUPPORT
     BIO *ssl_io;               /* BIO used for connection I/O */
@@ -96,13 +109,18 @@ netsec_init(void)
 
     nsc->ns_fd = -1;
     nsc->ns_snoop = 0;
 
     nsc->ns_fd = -1;
     nsc->ns_snoop = 0;
+    nsc->ns_userid = nsc->ns_hostname = NULL;
     nsc->ns_inbuffer = nsc->ns_inptr = NULL;
     nsc->ns_inbuflen = nsc->ns_inbufsize = 0;
     nsc->ns_outbuffer = nsc->ns_outptr = NULL;
     nsc->ns_outbuflen = nsc->ns_outbufsize = 0;
 #ifdef CYRUS_SASL
     nsc->sasl_conn = NULL;
     nsc->ns_inbuffer = nsc->ns_inptr = NULL;
     nsc->ns_inbuflen = nsc->ns_inbufsize = 0;
     nsc->ns_outbuffer = nsc->ns_outptr = NULL;
     nsc->ns_outbuflen = nsc->ns_outbufsize = 0;
 #ifdef CYRUS_SASL
     nsc->sasl_conn = NULL;
-    nsc->sasl_service = nsc->sasl_mech = NULL;
+    nsc->sasl_mech = NULL;
+    nsc->sasl_cbs = NULL;
+    nsc->sasl_creds = NULL;
+    nsc->sasl_secret = NULL;
+    nsc->sasl_chosen_mech = NULL;
 #endif /* CYRUS_SASL */
 #ifdef TLS_SUPPORT
     nsc->ssl_io = NULL;
 #endif /* CYRUS_SASL */
 #ifdef TLS_SUPPORT
     nsc->ssl_io = NULL;
@@ -111,6 +129,56 @@ netsec_init(void)
     return nsc;
 }
 
     return nsc;
 }
 
+/*
+ * Shutdown the connection completely and free all resources.
+ * The connection is not closed, however.
+ */
+
+void
+netsec_shutdown(netsec_context *nsc)
+{
+    if (nsc->ns_hostname)
+       free(nsc->ns_hostname);
+    if (nsc->ns_userid)
+       free(nsc->ns_userid);
+    if (nsc->ns_inbuffer)
+       free(nsc->ns_inbuffer);
+    if (nsc->ns_outbuffer)
+       free(nsc->ns_outbuffer);
+#ifdef CYRUS_SASL
+    if (nsc->sasl_conn)
+       sasl_dispose(&nsc->sasl_conn);
+    if (nsc->sasl_mech)
+       free(nsc->sasl_mech);
+    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);
+    }
+    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_chosen_mech)
+       free(nsc->sasl_chosen_mech);
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+    if (nsc->ssl_io)
+       /*
+        * I checked; BIO_free_all() will cause SSL_shutdown to be called
+        * on the SSL object in the chain.
+        */
+       BIO_free_all(nsc->ssl_io);
+#endif /* TLS_SUPPORT */
+
+    free(nsc);
+}
+
 /*
  * Set the file descriptor for our context
  */
 /*
  * Set the file descriptor for our context
  */
@@ -121,6 +189,26 @@ netsec_set_fd(netsec_context *nsc, int fd)
     nsc->ns_fd = fd;
 }
 
     nsc->ns_fd = fd;
 }
 
+/*
+ * Set the remote hostname we've connected to.
+ */
+
+void
+netsec_set_hostname(netsec_context *nsc, const char *hostname)
+{
+    nsc->ns_hostname = getcpy(hostname);
+}
+
+/*
+ * Set the userid used for authentication for this context
+ */
+
+void
+netsec_set_userid(netsec_context *nsc, const char *userid)
+{
+    nsc->ns_userid = getcpy(userid);
+}
+
 /*
  * Set the snoop flag for this connection
  */
 /*
  * Set the snoop flag for this connection
  */
@@ -131,6 +219,213 @@ netsec_set_snoop(netsec_context *nsc, int snoop)
     nsc->ns_snoop = snoop;
 }
 
     nsc->ns_snoop = snoop;
 }
 
+/*
+ * Set various SASL protocol parameters
+ */
+
+int
+netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
+                      const char *service, const char *mechanism,
+                      netsec_sasl_callback callback, char **errstr)
+{
+#ifdef CYRUS_SASL
+    sasl_callback_t *sasl_cbs;
+    int retval;
+
+    if (! sasl_initialized) {
+       retval = sasl_client_init(NULL);
+       if (retval != SASL_OK) {
+           *errstr = netsec_errstr("SASL client initialization failed: %s",
+                                   sasl_errstring(retval, NULL, NULL));
+           return NOTOK;
+       }
+       sasl_initialized++;
+    }
+
+    /*
+     * Allocate an array of SASL callbacks for this connection.
+     * Right now we just allocate an array of four callbacks.
+     */
+
+    sasl_cbs = mh_xmalloc(sizeof(*sasl_cbs) * 4);
+
+    sasl_cbs[0].id = SASL_CB_USER;
+    sasl_cbs[0].proc = (sasl_callback_ft) netsec_get_user;
+    sasl_cbs[0].context = nsc;
+
+    sasl_cbs[1].id = SASL_CB_AUTHNAME;
+    sasl_cbs[1].proc = (sasl_callback_ft) netsec_get_user;
+    sasl_cbs[1].context = nsc;
+
+    sasl_cbs[2].id = SASL_CB_PASS;
+    sasl_cbs[2].proc = (sasl_callback_ft) netsec_get_password;
+    sasl_cbs[2].context = nsc;
+
+    sasl_cbs[3].id = SASL_CB_LIST_END;
+    sasl_cbs[3].proc = NULL;
+    sasl_cbs[3].context = NULL;
+
+    nsc->sasl_cbs = sasl_cbs;
+
+    retval = sasl_client_new(service, hostname, NULL, NULL, nsc->sasl_cbs, 0,
+                            &nsc->sasl_conn);
+
+    if (retval) {
+       *errstr = netsec_errstr("SASL new client allocation failed: %s",
+                               sasl_errstring(retval, NULL, NULL));
+       return NOTOK;
+    }
+
+    nsc->sasl_mech = mechanism ? getcpy(mechanism) : NULL;
+    nsc->sasl_proto_cb = callback;
+
+    return OK;
+#else /* CYRUS_SASL */
+    *errstr = netsec_errstr("SASL is not supported");
+
+    return NOTOK;
+#endif /* CYRUS_SASL */
+}
+
+#ifdef CYRUS_SASL
+/*
+ * Our userid callback; return the specified username to the SASL
+ * library when asked.
+ */
+
+int netsec_get_user(void *context, int id, const char **result,
+                   unsigned int *len)
+{
+    netsec_context *nsc = (netsec_context *) context;
+
+    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 defauly 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) {
+           nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
+           nsc->sasl_creds->user = NULL;
+           nsc->sasl_creds->password = NULL;
+       }
+
+       if (nmh_get_credentials(nsc->ns_hostname, nsc->ns_userid, 1,
+                               nsc->sasl_creds) != OK)
+           return SASL_BADPARAM;
+
+       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);
+
+    return SASL_OK;
+}
+
+/*
+ * Retrieve a password and return it to SASL
+ */
+
+static int
+netsec_get_password(sasl_conn_t *conn, void *context, int id,
+                   sasl_secret_t **psecret)
+{
+    netsec_context *nsc = (netsec_context *) context;
+    int len;
+
+    NMH_UNUSED(conn);
+
+    if (! psecret || id != SASL_CB_PASS)
+       return SASL_BADPARAM;
+
+    if (nsc->sasl_creds == NULL) {
+       nsc->sasl_creds = mh_xmalloc(sizeof(*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->ns_hostname, nsc->ns_userid, 0,
+                               nsc->sasl_creds) != OK) {
+           return SASL_BADPARAM;
+       }
+    }
+
+    len = strlen(nsc->sasl_creds->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);
+
+    if (! *psecret)
+       return SASL_NOMEM;
+
+    (*psecret)->len = len;
+    strcpy((char *) (*psecret)->data, nsc->sasl_creds->password);
+
+    nsc->sasl_secret = *psecret;
+
+    return SASL_OK;
+}
+#endif /* CYRUS_SASL */
+
+/*
+ * Negotiate SASL on this connection
+ */
+
+int
+netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
+{
+#ifdef CYRUS_SASL
+    sasl_security_properties_t secprops;
+    char *chosen_mech;
+    sasl_ssf_t *ssf;
+    int rc;
+
+    return OK;
+#else
+    *errstr = netsec_errstr("SASL not supported");
+
+    return NOTOK;
+#endif /* CYRUS_SASL */
+}
+
+/*
+ * Retrieve our chosen SASL mechanism
+ */
+
+char *
+netsec_get_sasl_mechanism(netsec_context *nsc)
+{
+#ifdef CYRUS_SASL
+    return nsc->sasl_chosen_mech;
+#else /* CYRUS_SASL */
+    return NULL;
+#endif /* CYRUS_SASL */
+}
+
 /*
  * Initialize (and enable) TLS for this connection
  */
 /*
  * Initialize (and enable) TLS for this connection
  */
@@ -251,6 +546,50 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
 #endif /* TLS_SUPPORT */
 }
 
 #endif /* TLS_SUPPORT */
 }
 
+/*
+ * Start TLS negotiation on this connection
+ */
+
+int
+netsec_negotiate_tls(netsec_context *nsc, char **errstr)
+{
+#ifdef TLS_SUPPORT
+    if (! nsc->ssl_io) {
+       *errstr = netsec_errstr("TLS has not been configured for this "
+                               "connection");
+       return NOTOK;
+    }
+
+    if (BIO_do_handshake(nsc->ssl_io) < 1) {
+       *errstr = netsec_errstr("TLS negotiation failed: %s",
+                               ERR_error_string(ERR_get_error(), NULL));
+       return NOTOK;
+    }
+
+    if (nsc->ns_snoop) {
+       SSL *ssl;
+
+       if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+           fprintf(stderr, "WARNING: cannot determine SSL ciphers\n");
+       } else {
+           const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+           fprintf(stderr, "TLS negotation successful: %s(%d) %s\n",
+                   SSL_CIPHER_get_name(cipher),
+                   SSL_CIPHER_get_bits(cipher, NULL),
+                   SSL_CIPHER_get_version(cipher));
+       }
+    }
+
+    nsc->tls_active = 1;
+
+    return OK;
+#else /* TLS_SUPPORT */
+    *errstr = netsec_errstr("TLS not supported");
+
+    return NOTOK;
+#endif /* TLS_SUPPORT */
+}
+
 /*
  * Generate an (allocated) error string
  */
 /*
  * Generate an (allocated) error string
  */