#include <h/mh.h>
#include <h/utils.h>
+#include <h/oauth.h>
#ifdef CYRUS_SASL
# include <sasl/sasl.h>
# endif /* SASL_VERSION_FULL < 0x020125 */
#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif /* TLS_SUPPORT */
+
#include <h/popsbr.h>
#include <h/signals.h>
-#include <signal.h>
-#include <errno.h>
#define TRM "."
#define TRMLEN (sizeof TRM - 1)
static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **);
struct pass_context {
char *user;
+ char *password;
char *host;
};
#define SASL_BUFFER_SIZE 262144
};
-#else /* CYRUS_SASL */
-# define sasl_fgetc fgetc
#endif /* CYRUS_SASL */
/*
#ifdef CYRUS_SASL
static int pop_auth_sasl(char *, char *, char *);
-static int sasl_fgetc(FILE *);
#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+static SSL_CTX *sslctx = NULL;
+static SSL *ssl = NULL;
+static BIO *sbior = NULL;
+static BIO *sbiow = NULL;
+static BIO *io = NULL;
+
+static int tls_negotiate(void);
+#endif /* TLS_SUPPORT */
+
+static int tls_active = 0;
+
static int traverse (int (*)(char *), const char *, ...);
static int vcommand(const char *, va_list);
static int sasl_getline (char *, int, FILE *);
+static int sasl_fgetc(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
* 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;
}
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.
*/
memset(&secprops, 0, sizeof(secprops));
secprops.maxbufsize = SASL_BUFFER_SIZE;
- secprops.max_ssf = UINT_MAX;
+ secprops.max_ssf = tls_active ? 0 : UINT_MAX;
result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
}
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;
}
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
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;
}
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).
sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret)
{
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
} 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, int tls, 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) {
SIGNAL (SIGPIPE, SIG_IGN);
+ if (tls && tls_negotiate())
+ return NOTOK;
+
switch (sasl_getline (response, sizeof response, input)) {
case OK:
if (poprint)
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)
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)
- fprintf(stderr, "(encrypted) ");
+ fprintf(stderr, "(sasl-encrypted) ");
#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+ if (tls_active)
+ fprintf(stderr, "(tls-encrypted) ");
+#endif /* TLS_SUPPORT */
if (pophack) {
if ((cp = strchr (buffer, ' ')))
*cp = 0;
*p = 0;
if (*--p == '\n')
*p = 0;
- if (*--p == '\r')
+ if (p > s && *--p == '\r')
*p = 0;
return OK;
int result;
unsigned int buflen;
- if (!sasl_complete) {
+ if (sasl_complete == 0 || sasl_ssf == 0) {
#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+ if (tls_active) {
+ int ret;
+
+ BIO_printf(io, "%s\r\n");
+ ret = BIO_flush(io);
+
+ if (ret != 1) {
+ strncpy(response, "lost connection", sizeof(response));
+ return NOTOK;
+ else {
+ return OK;
+ }
+ } else
+#endif /* TLS_SUPPORT */
fprintf (iop, "%s\r\n", s);
#ifdef CYRUS_SASL
} else {
return NOTOK;
}
- fwrite(buf, buflen, 1, iop);
+ if (fwrite(buf, buflen, 1, iop) < 1) {
+ advise ("putline", "fwrite");
+ }
}
#endif /* CYRUS_SASL */
while (retbufsize == 0) {
+#ifdef TLS_SUPPORT
+ if (tls_active) {
+
cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
if (cc == 0)
return (int) buffer[0];
}
#endif /* CYRUS_SASL */
+
+#ifdef TLS_SUPPORT
+/*
+ * Negotiate TLS at this point. Will call pop_done() on failure.
+ */
+
+static int
+tls_negotiate(void)
+{
+ BIO *ssl_bio;
+
+ if (! sslctx) {
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ sslctx = SSL_CTX_new(SSLv23_client_method());
+
+ if (! sslctx) {
+ pop_done();
+ advise(NULL, "Unable to initialize OpenSSL context: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1);
+ }
+
+ ssl = SSL_new(sslctx);
+
+ if (! ssl) {
+ pop_done();
+ advise(NULL, "Unable to create SSL connection: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ sbior = BIO_new_socket(fileno(input), BIO_NOCLOSE);
+ sbiow = BIO_new_socket(fileno(output), BIO_NOCLOSE);
+
+ if (sbior == NULL || sbiow == NULL) {
+ pop_done();
+ advise(NULL, "Unable to create BIO endpoints: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ SSL_set_bio(ssl, sbior, sbiow);
+ SSL_set_connect_state(ssl);
+
+ /*
+ * Set up a BIO to do buffering for us
+ */
+
+ io = BIO_new(BIO_f_buffer());
+
+ if (! io) {
+ pop_done();
+ advise(NULL, "Unable to create a buffer BIO: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ ssl_bio = BIO_new(BIO_f_ssl());
+
+ if (! ssl_bio) {
+ pop_done();
+ advise(NULL, "Unable to create a SSL BIO: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
+ BIO_push(io, ssl_bio);
+
+ /*
+ * Try doing the handshake now
+ */
+
+ if (BIO_do_handshake(io) < 1) {
+ pop_done();
+ advise(NULL, "Unable to negotiate SSL connection: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
+ if (poprint) {
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+ printf("SSL negotiation successful: %s(%d) %s\n",
+ SSL_CIPHER_get_name(cipher),
+ SSL_CIPHER_get_bits(cipher, NULL),
+ SSL_CIPHER_get_version(cipher));
+ }
+
+ tls_active = 1;
+
+ return OK;
+}
+#endif /* TLS_SUPPORT */