X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=mts%2Fsmtp%2Fsmtp.c;h=ac6b32e990f13e685795717d0621e1e5afa9999e;hp=c8fb5548d8965eb7a4a1a47aa70447ab4d93bf05;hb=cfc525a9b85207225cb4071d1d3b01e8d1db2424;hpb=df7b16cb148474b3b80dbf1e226e858cf1e47edb diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index c8fb554..ac6b32e 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -1,4 +1,3 @@ - /* * smtp.c -- nmh SMTP interface * @@ -423,6 +422,14 @@ rclient (char *server, char *protocol, char *service) return NOTOK; } +#ifdef CYRUS_SASL +#include +#include +#include +#include +#include +#include +#endif /* CYRUS_SASL */ int sm_winit (int mode, char *from) @@ -1007,6 +1014,306 @@ no_dice: #endif /* MPOP */ +#ifdef CYRUS_SASL +/* + * This function implements SASL authentication for SMTP. If this function + * completes successfully, then authentication is successful and we've + * (optionally) negotiated a security layer. + * + * Right now we don't support session encryption. + */ +static int +sm_auth_sasl(char *user, char *mechlist, char *host) +{ + int result, status, outlen; + unsigned int buflen; + char *buf, outbuf[BUFSIZ]; + const char *chosen_mech; + sasl_security_properties_t secprops; + sasl_external_properties_t extprops; + sasl_ssf_t *ssf; + int *outbufmax; + + /* + * Initialize the callback contexts + */ + + if (user == NULL) + user = getusername(); + + callbacks[SM_SASL_N_CB_USER].context = user; + + /* + * This is a _bit_ of a hack ... but if the hostname wasn't supplied + * to us on the command line, then call getpeername and do a + * reverse-address lookup on the IP address to get the name. + */ + + if (!host) { + struct sockaddr_in sin; + int len = sizeof(sin); + struct hostent *hp; + + if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) { + sm_ierror("getpeername on SMTP socket failed: %s", + strerror(errno)); + return NOTOK; + } + + if ((hp = gethostbyaddr((void *) &sin.sin_addr, sizeof(sin.sin_addr), + sin.sin_family)) == NULL) { + sm_ierror("DNS lookup on IP address %s failed", + inet_ntoa(sin.sin_addr)); + return NOTOK; + } + + host = strdup(hp->h_name); + } + + sasl_pw_context[0] = host; + sasl_pw_context[1] = user; + + callbacks[SM_SASL_N_CB_PASS].context = sasl_pw_context; + + result = sasl_client_init(callbacks); + + if (result != SASL_OK) { + sm_ierror("SASL library initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + result = sasl_client_new("smtp", host, NULL, SASL_SECURITY_LAYER, &conn); + + if (result != SASL_OK) { + sm_ierror("SASL client initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * Initialize the security properties + */ + + memset(&secprops, 0, sizeof(secprops)); + secprops.maxbufsize = BUFSIZ; + secprops.max_ssf = 0; /* XXX change this when we do encryption */ + memset(&extprops, 0, sizeof(extprops)); + + result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + + if (result != SASL_OK) { + sm_ierror("SASL security property initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &extprops); + + if (result != SASL_OK) { + sm_ierror("SASL external property initialization failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * Start the actual protocol. Feed the mech list into the library + * and get out a possible initial challenge + */ + + result = sasl_client_start(conn, mechlist, NULL, NULL, &buf, &buflen, + &chosen_mech); + + if (result != SASL_OK && result != SASL_CONTINUE) { + sm_ierror("SASL client start failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * If we got an initial challenge, send it as part of the AUTH + * command; otherwise, just send a plain AUTH command. + */ + + if (buflen) { + status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + free(buf); + if (status != SASL_OK) { + sm_ierror("SASL base64 encode failed: %s", + sasl_errstring(status, NULL, NULL)); + return NOTOK; + } + + status = smtalk(SM_AUTH, "AUTH %s %s", chosen_mech, outbuf); + } else + status = smtalk(SM_AUTH, "AUTH %s", chosen_mech); + + /* + * Now we loop until we either fail, get a SASL_OK, or a 235 + * response code. Receive the challenges and process them until + * we're all done. + */ + + while (result == SASL_CONTINUE) { + + /* + * If we get a 235 response, that means authentication has + * succeeded and we need to break out of the loop (yes, even if + * we still get SASL_CONTINUE from sasl_client_step()). + * + * Otherwise, if we get a message that doesn't seem to be a + * valid response, then abort + */ + + if (status == 235) + break; + else if (status < 300 || status > 399) + return RP_BHST; + + /* + * Special case; a zero-length response from the SMTP server + * is returned as a single =. If we get that, then set buflen + * to be zero. Otherwise, just decode the response. + */ + + if (strcmp("=", sm_reply.text) == 0) { + outlen = 0; + } else { + result = sasl_decode64(sm_reply.text, sm_reply.length, + outbuf, &outlen); + + if (result != SASL_OK) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL base64 decode failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + } + + result = sasl_client_step(conn, outbuf, outlen, NULL, &buf, &buflen); + + if (result != SASL_OK && result != SASL_CONTINUE) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL client negotiation failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + free(buf); + + if (status != SASL_OK) { + smtalk(SM_AUTH, "*"); + sm_ierror("SASL base64 encode failed: %s", + sasl_errstring(status, NULL, NULL)); + return NOTOK; + } + + status = smtalk(SM_AUTH, outbuf); + } + + /* + * Make sure that we got the correct response + */ + + if (status < 200 || status > 299) + return RP_BHST; + + /* + * Depending on the mechanism, we need to do a FINAL call to + * sasl_client_step(). Do that now. + */ + + result = sasl_client_step(conn, NULL, 0, NULL, &buf, &buflen); + + if (result != SASL_OK) { + sm_ierror("SASL final client negotiation failed: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + /* + * We _should_ have completed the authentication successfully. + * Get a few properties from the authentication exchange. + */ + + result = sasl_getprop(conn, SASL_MAXOUTBUF, (void **) &outbufmax); + + if (result != SASL_OK) { + sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s", + sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + maxoutbuf = *outbufmax; + + result = sasl_getprop(conn, SASL_SSF, (void **) &ssf); + + sasl_ssf = *ssf; + + if (result != SASL_OK) { + sm_ierror("Cannot retrieve SASL negotiated security strength " + "factor: %s", sasl_errstring(result, NULL, NULL)); + return NOTOK; + } + + if (maxoutbuf == 0 || maxoutbuf > BUFSIZ) + maxoutbuf = BUFSIZ; + + sasl_complete = 1; + + return RP_OK; +} + +/* + * Our callback functions to feed data to the SASL library + */ + +static int +sm_get_user(void *context, int id, const char **result, unsigned *len) +{ + char *user = (char *) context; + + if (! result || id != SASL_CB_USER) + return SASL_BADPARAM; + + *result = user; + if (len) + *len = strlen(user); + + return SASL_OK; +} + +static int +sm_get_pass(sasl_conn_t *conn, void *context, int id, + sasl_secret_t **psecret) +{ + char **pw_context = (char **) context; + char *pass = NULL; + int len; + + if (! psecret || id != SASL_CB_PASS) + return SASL_BADPARAM; + + ruserpass(pw_context[0], &(pw_context[1]), &pass); + + len = strlen(pass); + + *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len); + + if (! *psecret) { + free(pass); + return SASL_NOMEM; + } + + (*psecret)->len = len; + strcpy((*psecret)->data, pass); +/* free(pass); */ + + return SASL_OK; +} +#endif /* CYRUS_SASL */ + static int sm_ierror (char *fmt, ...) {