X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/5ccbd49adba7e6d48f4094fa6eade7a7fb8ec4cf..b7b980a9e051e6999799eda5a81c5a6d8061e0eb:/uip/popsbr.c?ds=sidebyside diff --git a/uip/popsbr.c b/uip/popsbr.c index ff28705f..5b99c8cf 100644 --- a/uip/popsbr.c +++ b/uip/popsbr.c @@ -8,16 +8,22 @@ #include #include +#include #ifdef CYRUS_SASL # include # include +# if SASL_VERSION_FULL < 0x020125 + /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype, + which has an explicit void parameter list, according to best + practice. So we need to cast to avoid compile warnings. + Provide this prototype for earlier versions. */ + typedef int (*sasl_callback_ft)(); +# endif /* SASL_VERSION_FULL < 0x020125 */ #endif /* CYRUS_SASL */ #include #include -#include -#include #define TRM "." #define TRMLEN (sizeof TRM - 1) @@ -39,13 +45,14 @@ static int sasl_get_user(void *, int, const char **, unsigned *); static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); struct pass_context { char *user; + char *password; char *host; }; static sasl_callback_t callbacks[] = { - { SASL_CB_USER, sasl_get_user, NULL }, + { SASL_CB_USER, (sasl_callback_ft) sasl_get_user, NULL }, #define POP_SASL_CB_N_USER 0 - { SASL_CB_PASS, sasl_get_pass, NULL }, + { SASL_CB_PASS, (sasl_callback_ft) sasl_get_pass, NULL }, #define POP_SASL_CB_N_PASS 1 { SASL_CB_LOG, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL }, @@ -74,26 +81,10 @@ static int sasl_getline (char *, int, FILE *); static int putline (char *, FILE *); -#ifdef CYRUS_SASL -/* - * This function implements the AUTH command for various SASL mechanisms - * - * We do the whole SASL dialog here. If this completes, then we've - * authenticated successfully and have (possibly) negotiated a security - * layer. - */ - int -pop_auth_sasl(char *user, char *host, char *mech) +check_mech(char *server_mechs, size_t server_mechs_size, char *mech) { - int result, status, sasl_capability = 0; - unsigned int buflen, outlen; - char server_mechs[256], *buf, outbuf[BUFSIZ]; - const char *chosen_mech; - sasl_security_properties_t secprops; - struct pass_context p_context; - sasl_ssf_t *ssf; - int *moutbuf; + int status, sasl_capability = 0; /* * First off, we're going to send the CAPA command to see if we can @@ -123,7 +114,7 @@ pop_auth_sasl(char *user, char *host, char *mech) * We've seen the SASL capability. Grab the mech list */ sasl_capability++; - strncpy(server_mechs, response + 5, sizeof(server_mechs)); + strncpy(server_mechs, response + 5, server_mechs_size); } break; } @@ -145,6 +136,42 @@ pop_auth_sasl(char *user, char *host, char *mech) return NOTOK; } + return OK; +} + +#ifdef CYRUS_SASL +/* + * This function implements the AUTH command for various SASL mechanisms + * + * We do the whole SASL dialog here. If this completes, then we've + * authenticated successfully and have (possibly) negotiated a security + * layer. + */ + +#define CHECKB64SIZE(insize, outbuf, outsize) \ + { size_t wantout = (((insize + 2) / 3) * 4) + 32; \ + if (wantout > outsize) { \ + outbuf = mh_xrealloc(outbuf, outsize = wantout); \ + } \ + } + +int +pop_auth_sasl(char *user, char *host, char *mech) +{ + int result, status; + unsigned int buflen, outlen; + char server_mechs[256], *buf, *outbuf = NULL; + size_t outbufsize = 0; + const char *chosen_mech; + sasl_security_properties_t secprops; + struct pass_context p_context; + sasl_ssf_t *ssf; + int *moutbuf; + + if ((status = check_mech(server_mechs, sizeof(server_mechs), mech)) != OK) { + return status; + } + /* * Start the SASL process. First off, initialize the SASL library. */ @@ -203,10 +230,13 @@ pop_auth_sasl(char *user, char *host, char *mech) } if (buflen) { - status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + CHECKB64SIZE(buflen, outbuf, outbufsize); + status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); if (status != SASL_OK) { snprintf(response, sizeof(response), "SASL base64 encode " "failed: %s", sasl_errstring(status, NULL, NULL)); + if (outbuf) + free(outbuf); return NOTOK; } @@ -215,8 +245,13 @@ pop_auth_sasl(char *user, char *host, char *mech) status = command("AUTH %s", chosen_mech); while (result == SASL_CONTINUE) { - if (status == NOTOK) + size_t inlen; + + if (status == NOTOK) { + if (outbuf) + free(outbuf); return NOTOK; + } /* * If we get a "+OK" prefix to our response, then we should @@ -235,16 +270,31 @@ pop_auth_sasl(char *user, char *host, char *mech) command("*"); snprintf(response, sizeof(response), "Malformed authentication message from server"); + if (outbuf) + free(outbuf); return NOTOK; } + /* + * For decode, it will always be shorter, so just make sure + * that outbuf is as at least as big as the encoded response. + */ + + inlen = strlen(response + 2); + + if (inlen > outbufsize) { + outbuf = mh_xrealloc(outbuf, outbufsize = inlen); + } + result = sasl_decode64(response + 2, strlen(response + 2), - outbuf, sizeof(outbuf), &outlen); + outbuf, outbufsize, &outlen); if (result != SASL_OK) { command("*"); snprintf(response, sizeof(response), "SASL base64 decode " "failed: %s", sasl_errstring(result, NULL, NULL)); + if (outbuf) + free(outbuf); return NOTOK; } @@ -255,21 +305,30 @@ pop_auth_sasl(char *user, char *host, char *mech) command("*"); snprintf(response, sizeof(response), "SASL client negotiaton " "failed: %s", sasl_errdetail(conn)); + if (outbuf) + free(outbuf); return NOTOK; } - status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + CHECKB64SIZE(buflen, outbuf, outbufsize); + + status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); if (status != SASL_OK) { command("*"); snprintf(response, sizeof(response), "SASL base64 encode " "failed: %s", sasl_errstring(status, NULL, NULL)); + if (outbuf) + free(outbuf); return NOTOK; } status = command(outbuf); } + if (outbuf) + free(outbuf); + /* * If we didn't get a positive final response, then error out * (that probably means we failed an authorization check). @@ -343,28 +402,59 @@ sasl_get_user(void *context, int id, const char **result, unsigned *len) static int sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) { - NMH_UNUSED (conn); - struct pass_context *p_context = (struct pass_context *) context; - char *pass = NULL; + struct nmh_creds creds = { 0, 0, 0 }; int len; + NMH_UNUSED (conn); + if (! psecret || id != SASL_CB_PASS) return SASL_BADPARAM; - ruserpass(p_context->user, &(p_context->host), &pass); + if (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 (p_context->host, p_context->user, 0, &creds) + != OK) { + return SASL_BADPARAM; + } + } - len = strlen(pass); + len = strlen (creds.password); *psecret = (sasl_secret_t *) mh_xmalloc(sizeof(sasl_secret_t) + len); (*psecret)->len = len; - strcpy((char *) (*psecret)->data, pass); + strcpy((char *) (*psecret)->data, creds.password); return SASL_OK; } #endif /* CYRUS_SASL */ +int +pop_auth_xoauth(const char *client_res) +{ + char server_mechs[256]; + int status = check_mech(server_mechs, sizeof(server_mechs), "XOAUTH"); + + if (status != OK) return status; + + if ((status = command("AUTH XOAUTH2 %s", client_res)) != OK) { + return status; + } + if (strncmp(response, "+OK", 3) == 0) { + return OK; + } + + /* response contains base64-encoded JSON, which is always the same. + * See mts/smtp/smtp.c for more notes on that. */ + /* Then we're supposed to send an empty response ("\r\n"). */ + return command(""); +} /* * Split string containing proxy command into an array of arguments @@ -410,31 +500,44 @@ parse_proxy(char *proxy, char *host) } else if (!isspace(*cur)) *c++ = *cur; } + *c = '\0'; *++p = NULL; return pargv; } int pop_init (char *host, char *port, char *user, char *pass, char *proxy, - int snoop, int sasl, char *mech) + int snoop, int sasl, char *mech, const char *oauth_svc) { int fd1, fd2; char buffer[BUFSIZ]; + const char *xoauth_client_res = NULL; #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 */ + if (proxy && *proxy) { int pid; int inpipe[2]; /* for reading from the server */ int outpipe[2]; /* for sending to the server */ - /* first give up any root priviledges we may have for rpop */ - setuid(getuid()); - - pipe(inpipe); - pipe(outpipe); + if (pipe(inpipe) < 0) { + adios ("inpipe", "pipe"); + } + if (pipe(outpipe) < 0) { + adios ("outpipe", "pipe"); + } pid=fork(); if (pid==0) { @@ -507,6 +610,12 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, return OK; } else # endif /* CYRUS_SASL */ +# if OAUTH_SUPPORT + if (xoauth_client_res != NULL) { + if (pop_auth_xoauth(xoauth_client_res) != NOTOK) + return OK; + } else +# endif /* OAUTH_SUPPORT */ if (command ("USER %s", user) != NOTOK && command ("%s %s", (pophack++, "PASS"), pass) != NOTOK) @@ -739,9 +848,10 @@ command(const char *fmt, ...) static int vcommand (const char *fmt, va_list ap) { - char *cp, buffer[BUFSIZ]; + char *cp, buffer[65536]; vsnprintf (buffer, sizeof(buffer), fmt, ap); + if (poprint) { #ifdef CYRUS_SASL if (sasl_ssf) @@ -840,7 +950,7 @@ sasl_getline (char *s, int n, FILE *iop) *p = 0; if (*--p == '\n') *p = 0; - if (*--p == '\r') + if (p > s && *--p == '\r') *p = 0; return OK; @@ -877,7 +987,9 @@ putline (char *s, FILE *iop) return NOTOK; } - fwrite(buf, buflen, 1, iop); + if (fwrite(buf, buflen, 1, iop) < 1) { + advise ("putline", "fwrite"); + } } #endif /* CYRUS_SASL */