diff options
-rw-r--r-- | account.c | 13 | ||||
-rw-r--r-- | doc/manual.sgml.head | 59 | ||||
-rw-r--r-- | imap/auth_sasl.c | 41 | ||||
-rw-r--r-- | imap/imap_ssl.c | 6 | ||||
-rw-r--r-- | mutt_sasl.c | 245 | ||||
-rw-r--r-- | mutt_sasl.h | 26 | ||||
-rw-r--r-- | mutt_socket.c | 19 | ||||
-rw-r--r-- | mutt_socket.h | 4 |
8 files changed, 355 insertions, 58 deletions
@@ -56,6 +56,8 @@ int mutt_account_match (const ACCOUNT* a1, const ACCOUNT* a2) /* mutt_account_getuser: retrieve username into ACCOUNT, if neccessary */ int mutt_account_getuser (ACCOUNT* account) { + char prompt[SHORT_STRING]; + /* already set */ if (account->flags & M_ACCT_USER) return 0; @@ -70,9 +72,9 @@ int mutt_account_getuser (ACCOUNT* account) /* prompt (defaults to unix username), copy into account->user */ else { + snprintf (prompt, sizeof (prompt), _("Username at %s: "), account->host); strfcpy (account->user, NONULL (Username), sizeof (account->user)); - if (mutt_get_field (_("Username: "), account->user, - sizeof (account->user), 0)) + if (mutt_get_field (prompt, account->user, sizeof (account->user), 0)) return -1; } @@ -84,6 +86,8 @@ int mutt_account_getuser (ACCOUNT* account) /* mutt_account_getpass: fetch password into ACCOUNT, if neccessary */ int mutt_account_getpass (ACCOUNT* account) { + char prompt[SHORT_STRING]; + if (account->flags & M_ACCT_PASS) return 0; #ifdef USE_IMAP @@ -96,9 +100,10 @@ int mutt_account_getpass (ACCOUNT* account) #endif else { + snprintf (prompt, sizeof (prompt), _("Password for %s@%s: "), + account->user, account->host); account->pass[0] = '\0'; - if (mutt_get_field (_("Password: "), account->pass, - sizeof (account->pass), M_PASS)) + if (mutt_get_field (prompt, account->pass, sizeof (account->pass), M_PASS)) return -1; } diff --git a/doc/manual.sgml.head b/doc/manual.sgml.head index 182129f1..9f6cce7d 100644 --- a/doc/manual.sgml.head +++ b/doc/manual.sgml.head @@ -2049,8 +2049,7 @@ You can access the remote inbox by selecting the folder server and <tt/inbox/ is the special name for your spool mailbox on the IMAP server. If you want to access another mail folder at the IMAP server, you should use <tt>{imapserver}path/to/folder</tt> where -<tt>path/to/folder</tt> is the path of the folder you want to access -(relative to your home directory if you aren't using Cyrus). +<tt>path/to/folder</tt> is the path of the folder you want to access. You can select an alternative port by specifying it with the server, ie: <tt/{imapserver:port}inbox/. @@ -2059,7 +2058,7 @@ You can also specify different username for each folder, ie: <tt/{username@imapserver[:port]}inbox/. If Mutt was compiled with SSL support (by running the <em/configure/ -script with the <em/--enable-ssl/ flag), connections to IMAP servers +script with the <em/--with-ssl/ flag), connections to IMAP servers can be encrypted. This naturally requires that the server supports SSL encrypted connections. To access a folder with IMAP/SSL, you should use <tt>{[username@]imapserver[:port]/ssl}path/to/folder</tt> as your @@ -2080,11 +2079,6 @@ the <ref id="imap_checkinterval" name="$imap_checkinterval"> variable, which defaults to every 60 seconds. -Mutt is designed to work with IMAP4rev1 servers, and was originally tested -with both the UWash IMAP server v11.241 and the Cyrus IMAP server v1.5.14. -Nowadays it is primarily developed against UW-IMAP 12.250. It appears -to work more-or-less correctly against Cyrus 1.6.11 as well. - Note that if you are using mbox as the mail store on UW servers prior to v12.250, the server has been reported to disconnect a client if another client selects the same folder. @@ -2115,34 +2109,37 @@ following differences: <sect2>Authentication <p> -Mutt supports three authentication methods with IMAP servers: GSSAPI, CRAM-MD5, -and LOGIN (there is a patch by Grant Edwards to add NTLM authentication for you -poor exchange users out there, but it has yet to be integrated into the main -tree). Mutt will try whichever methods are available on the server, in order from -most secure to least. That is, mutt will first try GSSAPI authentication (ie -Kerberos V), then CRAM-MD5, and finally LOGIN (the worst possible choice - your -password travels across the net in the clear). +Mutt supports four authentication methods with IMAP servers: SASL, +GSSAPI, CRAM-MD5, and LOGIN (there is a patch by Grant Edwards to add +NTLM authentication for you poor exchange users out there, but it has +yet to be integrated into the main tree). There is also support for +the pseudo-protocol ANONYMOUS, which allows you to log in to a public +IMAP server without having an account. To use ANONYMOUS, simply make +your username blank or "anonymous". +<p> +SASL is a special super-authenticator, which selects among several protocols +(including GSSAPI, CRAM-MD5, ANONYMOUS, and DIGEST-MD5) the most secure +method available on your host and the server. Using some of these methods +(including DIGEST-MD5 and possibly GSSAPI), your entire session will be +encrypted and invisible to those teeming network snoops. It is the best +option if you have it. To use it, you must have the Cyrus SASL library +installed on your system and compile mutt with the <em/--with-sasl/ flag. +<p> +Mutt will try whichever methods are compiled in and available on the server, +in the following order: SASL, ANONYMOUS, GSSAPI, CRAM-MD5, LOGIN. There are a few variables which control authentication: <itemize> -<item><ref id="imap_user" name="$imap_user"> - controls the - username under which you request authentication on the IMAP server, for all - authenticators. -<item><ref id="imap_pass" name="$imap_pass"> - the password - to use to authenticate you using the LOGIN method. If this is set, and other - methods fail, Mutt will use this without asking you. So if you use GSSAPI - or CRAM-MD5, don't set this variable. -<item><ref id="imap_cramkey" name="$imap_cramkey"> - the - secret used in CRAM-MD5 authentication (ie your CRAM password). If this is - not set and your server supports CRAM-MD5, Mutt will prompt you for it. +<item><ref id="imap_user" name="$imap_user"> - controls + the username under which you request authentication on the IMAP server, + for all authenticators. This is overridden by an explicit username in + the mailbox path (ie by using a mailbox name of the form + <tt/{user@host}/). +<item><ref id="imap_pass" name="$imap_pass"> - a + password which you may preset, used by all authentication methods where + a password is needed. </itemize> -<bf/Note:/ The IMAP support has had very limited testing due to a lack -of developers using it. It should work with the reference servers -mentioned above, but if you need a more stable way to access your -IMAP folder, consider using a specialized program, such as <htmlurl -url="http://www.ccil.org/~esr/fetchmail" name="fetchmail">. - <sect1>Start a WWW Browser on URLs (EXTERNAL)<label id="urlview"> <p> If a message contains URLs (<em/unified ressource locator/ = address in the diff --git a/imap/auth_sasl.c b/imap/auth_sasl.c index 8b78151c..946500ec 100644 --- a/imap/auth_sasl.c +++ b/imap/auth_sasl.c @@ -30,6 +30,7 @@ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata) { sasl_conn_t* saslconn; + sasl_interact_t* interaction = NULL; int rc; char buf[LONG_STRING]; const char* mech; @@ -43,7 +44,8 @@ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata) /* TODO: set fourth option to SASL_SECURITY_LAYER once we have a wrapper * (ie more than auth code) for SASL. */ rc = sasl_client_new ("imap", idata->conn->account.host, - mutt_sasl_get_callbacks (&idata->conn->account), 0, &saslconn); + mutt_sasl_get_callbacks (&idata->conn->account), SASL_SECURITY_LAYER, + &saslconn); if (rc != SASL_OK) { @@ -67,8 +69,14 @@ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata) &mech); if (rc != SASL_OK && rc != SASL_CONTINUE) - rc = sasl_client_start (saslconn, idata->capstr, NULL, NULL, &pc, &olen, - &mech); + do + { + rc = sasl_client_start (saslconn, idata->capstr, NULL, &interaction, + &pc, &olen, &mech); + if (rc == SASL_INTERACT) + mutt_sasl_interact (interaction); + } + while (rc == SASL_INTERACT); client_start = (olen > 0); @@ -107,17 +115,30 @@ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata) } if (!client_start) - rc = sasl_client_step (saslconn, buf, len, NULL, &pc, &olen); + do + { + rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen); + if (rc == SASL_INTERACT) + mutt_sasl_interact (interaction); + } + while (rc == SASL_INTERACT); else client_start = 0; /* send out response, or line break if none needed */ - if (olen && sasl_encode64 (pc, olen, buf, sizeof (buf), &olen) != SASL_OK) + if (pc) { - dprint (1, (debugfile, "imap_auth_sasl: error base64-encoding client response.\n")); - goto bail; - } + if (sasl_encode64 (pc, olen, buf, sizeof (buf), &olen) != SASL_OK) + { + dprint (1, (debugfile, "imap_auth_sasl: error base64-encoding client response.\n")); + goto bail; + } + /* sasl_client_st(art|ep) allocate pc with malloc, expect me to + * free it */ + free (pc); + } + if (olen || rc == SASL_CONTINUE) { strfcpy (buf + olen, "\r\n", sizeof (buf) - olen); @@ -134,9 +155,7 @@ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata) if (imap_code (buf)) { - /* later we'll want to keep saslconn, when we support a protection layer. - * For now it shouldn't hurt to dispose of it at this point. */ - sasl_dispose (&saslconn); + mutt_sasl_setup_conn (idata->conn, saslconn); return IMAP_AUTH_SUCCESS; } diff --git a/imap/imap_ssl.c b/imap/imap_ssl.c index c1643986..42915e68 100644 --- a/imap/imap_ssl.c +++ b/imap/imap_ssl.c @@ -161,7 +161,7 @@ static int ssl_socket_open_err (CONNECTION *conn) static int ssl_check_certificate (sslsockdata * data); static int ssl_socket_read (CONNECTION * conn); -static int ssl_socket_write (CONNECTION * conn, const char *buf); +static int ssl_socket_write (CONNECTION* conn, const char* buf, size_t len); static int ssl_socket_open (CONNECTION * conn); static int ssl_socket_close (CONNECTION * conn); @@ -187,10 +187,10 @@ int ssl_socket_read (CONNECTION * conn) return SSL_read (data->ssl, conn->inbuf, LONG_STRING); } -int ssl_socket_write (CONNECTION * conn, const char *buf) +int ssl_socket_write (CONNECTION* conn, const char* buf, size_t len) { sslsockdata *data = conn->sockdata; - return SSL_write (data->ssl, buf, mutt_strlen (buf)); + return SSL_write (data->ssl, buf, len); } int ssl_socket_open (CONNECTION * conn) diff --git a/mutt_sasl.c b/mutt_sasl.c index 30764a6f..0bd24917 100644 --- a/mutt_sasl.c +++ b/mutt_sasl.c @@ -25,7 +25,7 @@ #include <sasl.h> -static sasl_callback_t mutt_sasl_callbacks[4]; +static sasl_callback_t mutt_sasl_callbacks[5]; /* callbacks */ static int mutt_sasl_cb_log (void* context, int priority, const char* message); @@ -34,6 +34,13 @@ static int mutt_sasl_cb_authname (void* context, int id, const char** result, static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id, sasl_secret_t** psecret); +/* socket wrappers for a SASL security layer */ +static int mutt_sasl_conn_open (CONNECTION* conn); +static int mutt_sasl_conn_close (CONNECTION* conn); +static int mutt_sasl_conn_read (CONNECTION* conn); +static int mutt_sasl_conn_write (CONNECTION* conn, const char* buf, + size_t count); + /* mutt_sasl_start: called before doing a SASL exchange - initialises library * (if neccessary). */ int mutt_sasl_start (void) @@ -91,6 +98,11 @@ sasl_callback_t* mutt_sasl_get_callbacks (ACCOUNT* account) callback->context = account; callback++; + callback->id = SASL_CB_GETREALM; + callback->proc = NULL; + callback->context = NULL; + callback++; + callback->id = SASL_CB_LIST_END; callback->proc = NULL; callback->context = NULL; @@ -98,6 +110,77 @@ sasl_callback_t* mutt_sasl_get_callbacks (ACCOUNT* account) return mutt_sasl_callbacks; } +int mutt_sasl_interact (sasl_interact_t* interaction) +{ + char prompt[SHORT_STRING]; + char resp[SHORT_STRING]; + + while (interaction->id != SASL_CB_LIST_END) + { + dprint (2, (debugfile, "mutt_sasl_interact: filling in SASL interaction %ld.\n", interaction->id)); + + snprintf (prompt, sizeof (prompt), "%s: ", interaction->prompt); + resp[0] = '\0'; + if (mutt_get_field (prompt, resp, sizeof (resp), 0)) + return SASL_FAIL; + + interaction->len = mutt_strlen (resp)+1; + interaction->result = safe_malloc (interaction->len); + memcpy (interaction->result, resp, interaction->len); + + interaction++; + } + + return SASL_OK; +} + +/* SASL can stack a protection layer on top of an existing connection. + * To handle this, we store a saslconn_t in conn->sockdata, and write + * wrappers which en/decode the read/write stream, then replace sockdata + * with an embedded copy of the old sockdata and call the underlying + * functions (which we've also preserved). I thought about trying to make + * a general stackable connection system, but it seemed like overkill - + * something is wrong if we have 15 filters on top of a socket. Anyway, + * anything else which wishes to stack can use the same method. The only + * disadvantage is we have to write wrappers for all the socket methods, + * even if we only stack over read and write. Thinking about it, the + * abstraction problem is that there is more in CONNECTION than there + * needs to be. Ideally it would have only (void*)data and methods. */ + +/* mutt_sasl_setup_conn: replace connection methods, sockdata with + * SASL wrappers, for protection layers. Also get ssf, as a fastpath + * for the read/write methods. */ +void mutt_sasl_setup_conn (CONNECTION* conn, sasl_conn_t* saslconn) +{ + SASL_DATA* sasldata = (SASL_DATA*) safe_malloc (sizeof (SASL_DATA)); + + sasldata->saslconn = saslconn; + /* get ssf so we know whether we have to (en|de)code read/write */ + sasl_getprop (saslconn, SASL_SSF, (void**) &sasldata->ssf); + dprint (2, (debugfile, "SASL protection strength: %u\n", *sasldata->ssf)); + sasl_getprop (saslconn, SASL_MAXOUTBUF, (void**) &sasldata->pbufsize); + dprint (2, (debugfile, "SASL protection buffer size: %u\n", *sasldata->pbufsize)); + + /* clear input buffer */ + sasldata->buf = NULL; + sasldata->bpos = 0; + sasldata->blen = 0; + + /* preserve old functions */ + sasldata->sockdata = conn->sockdata; + sasldata->open = conn->open; + sasldata->close = conn->close; + sasldata->read = conn->read; + sasldata->write = conn->write; + + /* and set up new functions */ + conn->sockdata = sasldata; + conn->open = mutt_sasl_conn_open; + conn->close = mutt_sasl_conn_close; + conn->read = mutt_sasl_conn_read; + conn->write = mutt_sasl_conn_write; +} + /* mutt_sasl_cb_log: callback to log SASL messages */ static int mutt_sasl_cb_log (void* context, int priority, const char* message) { @@ -159,3 +242,163 @@ static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id, return SASL_OK; } + +/* mutt_sasl_conn_open: empty wrapper for underlying open function. We + * don't know in advance that a connection will use SASL, so we + * replace conn's methods with sasl methods when authentication + * is successful, using mutt_sasl_setup_conn */ +static int mutt_sasl_conn_open (CONNECTION* conn) +{ + SASL_DATA* sasldata; + int rc; + + sasldata = (SASL_DATA*) conn->sockdata; + conn->sockdata = sasldata->sockdata; + rc = (sasldata->open) (conn); + conn->sockdata = sasldata; + + return rc; +} + +/* mutt_sasl_conn_close: calls underlying close function and disposes of + * the sasl_conn_t object, then restores connection to pre-sasl state */ +static int mutt_sasl_conn_close (CONNECTION* conn) +{ + SASL_DATA* sasldata; + int rc; + + sasldata = (SASL_DATA*) conn->sockdata; + + /* restore connection's underlying methods */ + conn->sockdata = sasldata->sockdata; + conn->open = sasldata->open; + conn->close = sasldata->close; + conn->read = sasldata->read; + conn->write = sasldata->write; + + /* release sasl resources */ + sasl_dispose (&sasldata->saslconn); + FREE (&sasldata->buf); + FREE (&sasldata); + + /* call underlying close */ + rc = (conn->close) (conn); + + return rc; +} + +static int mutt_sasl_conn_read (CONNECTION* conn) +{ + SASL_DATA* sasldata; + int rc; + + unsigned int olen; + + sasldata = (SASL_DATA*) conn->sockdata; + + /* if we still have data in our read buffer, copy it into conn->inbuf */ + if (sasldata->blen > sasldata->bpos) + { + olen = (sasldata->blen - sasldata->bpos > sizeof (conn->inbuf)) ? + sizeof (conn->inbuf) : sasldata->blen - sasldata->bpos; + + memcpy (conn->inbuf, sasldata->buf+sasldata->bpos, olen); + sasldata->bpos += olen; + + return olen; + } + + conn->sockdata = sasldata->sockdata; + + FREE (&sasldata->buf); + sasldata->bpos = 0; + sasldata->blen = 0; + + /* and decode the result, if necessary */ + if (*sasldata->ssf) + { + do + { + /* call the underlying read function to fill the buffer */ + rc = (sasldata->read) (conn); + if (rc <= 0) + goto out; + + rc = sasl_decode (sasldata->saslconn, conn->inbuf, rc, &sasldata->buf, + &sasldata->blen); + if (rc != SASL_OK) + { + dprint (1, (debugfile, "SASL decode failed: %s\n", + sasl_errstring (rc, NULL, NULL))); + goto out; + } + } + while (!sasldata->blen); + + olen = (sasldata->blen - sasldata->bpos > sizeof (conn->inbuf)) ? + sizeof (conn->inbuf) : sasldata->blen - sasldata->bpos; + + memcpy (conn->inbuf, sasldata->buf, olen); + sasldata->bpos += olen; + + rc = olen; + } + else + rc = (sasldata->read) (conn); + + out: + conn->sockdata = sasldata; + + return rc; +} + +static int mutt_sasl_conn_write (CONNECTION* conn, const char* buf, + size_t len) +{ + SASL_DATA* sasldata; + int rc; + + char* pbuf; + unsigned int olen, plen; + + sasldata = (SASL_DATA*) conn->sockdata; + conn->sockdata = sasldata->sockdata; + + /* encode data, if necessary */ + if (*sasldata->ssf) + { + /* handle data larger than MAXOUTBUF */ + do + { + olen = (len > *sasldata->pbufsize) ? *sasldata->pbufsize : len; + + rc = sasl_encode (sasldata->saslconn, buf, olen, &pbuf, &plen); + if (rc != SASL_OK) + { + dprint (1, (debugfile, "SASL encoding failed: %s\n", + sasl_errstring (rc, NULL, NULL))); + goto fail; + } + + rc = (sasldata->write) (conn, pbuf, plen); + FREE (&pbuf); + if (rc != plen) + goto fail; + + len -= olen; + buf += olen; + } + while (len > *sasldata->pbufsize); + } + else + /* just write using the underlying socket function */ + rc = (sasldata->write) (conn, buf, len); + + conn->sockdata = sasldata; + + return rc; + + fail: + conn->sockdata = sasldata; + return -1; +} diff --git a/mutt_sasl.h b/mutt_sasl.h index e979e160..777fc746 100644 --- a/mutt_sasl.h +++ b/mutt_sasl.h @@ -21,11 +21,33 @@ #ifndef _MUTT_SASL_H_ #define _MUTT_SASL_H_ 1 -#include "mutt_socket.h" - #include <sasl.h> +#include "mutt_socket.h" + int mutt_sasl_start (void); sasl_callback_t* mutt_sasl_get_callbacks (ACCOUNT* account); +int mutt_sasl_interact (sasl_interact_t* interaction); +void mutt_sasl_setup_conn (CONNECTION* conn, sasl_conn_t* saslconn); + +typedef struct +{ + sasl_conn_t* saslconn; + const sasl_ssf_t* ssf; + const unsigned int* pbufsize; + + /* read buffer */ + char* buf; + unsigned int blen; + unsigned int bpos; + + /* underlying socket data */ + void* sockdata; + int (*open) (CONNECTION* conn); + int (*close) (CONNECTION* conn); + int (*read) (CONNECTION* conn); + int (*write) (CONNECTION* conn, const char* buf, size_t count); +} +SASL_DATA; #endif /* _MUTT_SASL_H_ */ diff --git a/mutt_socket.c b/mutt_socket.c index a588fe1e..12dc8f45 100644 --- a/mutt_socket.c +++ b/mutt_socket.c @@ -59,9 +59,19 @@ int mutt_socket_close (CONNECTION* conn) int mutt_socket_write_d (CONNECTION *conn, const char *buf, int dbg) { + int rc; + dprint (dbg, (debugfile,"> %s", buf)); - return conn->write (conn, buf); + if ((rc = conn->write (conn, buf, mutt_strlen (buf))) < 0) + { + dprint (1, (debugfile, "mutt_socket_write: error writing, closing socket\n")); + mutt_socket_close (conn); + + return -1; + } + + return rc; } /* simple read buffering to speed things up. */ @@ -88,8 +98,9 @@ int mutt_socket_readln_d (char* buf, size_t buflen, CONNECTION* conn, int dbg) { if (mutt_socket_readchar (conn, &ch) != 1) { - dprint (1, (debugfile, "mutt_socket_readln_d: read error")); + dprint (1, (debugfile, "mutt_socket_readln_d: read error, closing socket")); buf[i] = '\0'; + mutt_socket_close (conn); return -1; } if (ch == '\n') @@ -230,9 +241,9 @@ int raw_socket_read (CONNECTION *conn) return read (conn->fd, conn->inbuf, LONG_STRING); } -int raw_socket_write (CONNECTION *conn, const char *buf) +int raw_socket_write (CONNECTION* conn, const char* buf, size_t count) { - return write (conn->fd, buf, mutt_strlen (buf)); + return write (conn->fd, buf, count); } int raw_socket_open (CONNECTION* conn) diff --git a/mutt_socket.h b/mutt_socket.h index 4e151836..fd776d2d 100644 --- a/mutt_socket.h +++ b/mutt_socket.h @@ -42,7 +42,7 @@ typedef struct _connection void *sockdata; int (*read) (struct _connection *conn); - int (*write) (struct _connection *conn, const char *buf); + int (*write) (struct _connection *conn, const char *buf, size_t count); int (*open) (struct _connection *conn); int (*close) (struct _connection *conn); } CONNECTION; @@ -61,7 +61,7 @@ void mutt_socket_free (CONNECTION* conn); CONNECTION* mutt_conn_find (const CONNECTION* start, const ACCOUNT* account); int raw_socket_read (CONNECTION *conn); -int raw_socket_write (CONNECTION *conn, const char *buf); +int raw_socket_write (CONNECTION* conn, const char* buf, size_t count); int raw_socket_open (CONNECTION *conn); int raw_socket_close (CONNECTION *conn); |