From: Ken Hornstein Date: Thu, 15 Sep 2016 20:00:07 +0000 (-0400) Subject: Fleshed out some more, but we're not there just yet. X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/efe9458da1fc73745ded3c8f278501fd88179057?ds=sidebyside;hp=-c Fleshed out some more, but we're not there just yet. --- efe9458da1fc73745ded3c8f278501fd88179057 diff --git a/h/netsec.h b/h/netsec.h index f5abbcb5..98cfe256 100644 --- a/h/netsec.h +++ b/h/netsec.h @@ -38,6 +38,27 @@ void netsec_shutdown(netsec_context *ns_context); 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. @@ -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, ...); +/* + * 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. */ @@ -173,18 +207,50 @@ typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype, * 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 + * errstr - Error string. * * 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 @@ -224,9 +290,10 @@ int netsec_set_tls(netsec_context *context, int tls, char **errstr); * * Arguments: * + * ns_context - Network security context * 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); diff --git a/sbr/netsec.c b/sbr/netsec.c index cd33df9d..7cc9e32b 100644 --- a/sbr/netsec.c +++ b/sbr/netsec.c @@ -23,6 +23,13 @@ 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 @@ -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 */ + 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 */ @@ -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 - 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 */ - 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 */ @@ -96,13 +109,18 @@ netsec_init(void) 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->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; @@ -111,6 +129,56 @@ netsec_init(void) 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 */ @@ -121,6 +189,26 @@ netsec_set_fd(netsec_context *nsc, int 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 */ @@ -131,6 +219,213 @@ netsec_set_snoop(netsec_context *nsc, int 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 */ @@ -251,6 +546,50 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr) #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 */