diff options
author | Thomas Roessler <roessler@does-not-exist.org> | 1999-08-20 08:24:01 +0000 |
---|---|---|
committer | Thomas Roessler <roessler@does-not-exist.org> | 1999-08-20 08:24:01 +0000 |
commit | 9febf2c9a98ebcebded37065bc6058adfb0847a9 (patch) | |
tree | c6f06f934b0b32f38486a743e0596da140ee08d6 /imap/auth.c | |
parent | d79ed35520d7d5a3658e309af0e3e00d30ddf45d (diff) |
Brendan Cully's latest IMAP clean-up.
Diffstat (limited to 'imap/auth.c')
-rw-r--r-- | imap/auth.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/imap/auth.c b/imap/auth.c new file mode 100644 index 00000000..e04c6212 --- /dev/null +++ b/imap/auth.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * Copyright (C) 1996-9 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Brendan Cully <brendan@kublai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* IMAP login/authentication code */ + +#include "mutt.h" +#include "imap_private.h" +#include "md5.h" + +#define MD5_BLOCK_LEN 64 +#define MD5_DIGEST_LEN 16 + +/* forward declarations */ +static void hmac_md5 (const char* password, char* challenge, + unsigned char* response); +static int imap_auth_cram_md5 (IMAP_DATA* idata, const char* user, + const char* pass); + +/* hmac_md5: produce CRAM-MD5 challenge response. */ +static void hmac_md5 (const char* password, char* challenge, + unsigned char* response) +{ + MD5_CTX ctx; + unsigned char ipad[MD5_BLOCK_LEN], opad[MD5_BLOCK_LEN]; + unsigned char secret[MD5_BLOCK_LEN+1]; + unsigned char hash_passwd[MD5_DIGEST_LEN]; + unsigned int secret_len, chal_len; + int i; + + secret_len = strlen (password); + chal_len = strlen (challenge); + + /* passwords longer than MD5_BLOCK_LEN bytes are substituted with their MD5 + * digests */ + if (secret_len > MD5_BLOCK_LEN) + { + MD5Init (&ctx); + MD5Update (&ctx, (unsigned char*) password, secret_len); + MD5Final (hash_passwd, &ctx); + strfcpy ((char*) secret, (char*) hash_passwd, MD5_DIGEST_LEN); + secret_len = MD5_DIGEST_LEN; + } + else + strfcpy (secret, password, sizeof (secret)); + + memset (ipad, 0, sizeof (ipad)); + memset (opad, 0, sizeof (opad)); + memcpy (ipad, secret, secret_len); + memcpy (opad, secret, secret_len); + + for (i = 0; i < MD5_BLOCK_LEN; i++) + { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + /* inner hash: challenge and ipadded secret */ + MD5Init (&ctx); + MD5Update (&ctx, ipad, MD5_BLOCK_LEN); + MD5Update (&ctx, (unsigned char*) challenge, chal_len); + MD5Final (response, &ctx); + + /* outer hash: inner hash and opadded secret */ + MD5Init (&ctx); + MD5Update (&ctx, opad, MD5_BLOCK_LEN); + MD5Update (&ctx, response, MD5_DIGEST_LEN); + MD5Final (response, &ctx); +} + +/* imap_auth_cram_md5: AUTH=CRAM-MD5 support. Used unconditionally if the + * server supports it */ +static int imap_auth_cram_md5 (IMAP_DATA* idata, const char* user, + const char* pass) +{ + char ibuf[LONG_STRING], obuf[LONG_STRING]; + unsigned char hmac_response[MD5_DIGEST_LEN]; + int len; + char seq[16]; + + dprint (2, (debugfile, "Attempting CRAM-MD5 login...\n")); + mutt_message _("Logging in (CRAM-MD5)..."); + imap_make_sequence (seq, sizeof (seq)); + snprintf (obuf, LONG_STRING, "%s AUTHENTICATE CRAM-MD5\r\n", seq); + mutt_socket_write (idata->conn, obuf); + + /* From RFC 2195: + * The data encoded in the first ready response contains a presumptively + * arbitrary string of random digits, a timestamp, and the fully-qualified + * primary host name of the server. The syntax of the unencoded form must + * correspond to that of an RFC 822 'msg-id' [RFC822] as described in [POP3]. + */ + if (mutt_socket_read_line_d (ibuf, LONG_STRING, idata->conn) < 0) + { + dprint (1, (debugfile, "Error receiving server response.\n")); + + return -1; + } + + if (ibuf[0] != '+') + { + dprint (1, (debugfile, "Invalid response from server: %s\n", ibuf)); + + return -1; + } + + if ((len = mutt_from_base64 (obuf, ibuf + 2)) == -1) + { + dprint (1, (debugfile, "Error decoding base64 response.\n")); + + return -1; + } + + obuf[len] = '\0'; + dprint (2, (debugfile, "CRAM challenge: %s\n", obuf)); + + /* The client makes note of the data and then responds with a string + * consisting of the user name, a space, and a 'digest'. The latter is + * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where the + * key is a shared secret and the digested text is the timestamp (including + * angle-brackets). + * + * (Note: it's unspecified whether the user name needs IMAP quoting.) + */ + hmac_md5 (pass, obuf, hmac_response); + dprint (2, (debugfile, "CRAM response: %s,[%s]->", obuf, pass)); + /* dubious optimisation I saw elsewhere: make the whole string in one call */ + snprintf (obuf, sizeof (obuf), + "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + user, + hmac_response[0], hmac_response[1], hmac_response[2], hmac_response[3], + hmac_response[4], hmac_response[5], hmac_response[6], hmac_response[7], + hmac_response[8], hmac_response[9], hmac_response[10], hmac_response[11], + hmac_response[12], hmac_response[13], hmac_response[14], hmac_response[15]); + dprint(2, (debugfile, "%s\n", obuf)); + + mutt_to_base64 ((unsigned char*) ibuf, (unsigned char*) obuf, strlen (obuf)); + strcpy (ibuf + strlen (ibuf), "\r\n"); + mutt_socket_write (idata->conn, ibuf); + + if (mutt_socket_read_line_d (ibuf, LONG_STRING, idata->conn) < 0) + { + dprint (1, (debugfile, "Error receiving server response.\n")); + + return -1; + } + + if (imap_code (ibuf)) + { + dprint (2, (debugfile, "CRAM login complete.\n")); + + return 0; + } + + dprint (2, (debugfile, "CRAM login failed.\n")); + return -1; +} + +/* imap_authenticate: loop until success or user abort. At each loop, all + * supported authentication methods are tried, from strongest to weakest. + * Currently available: + * CRAM-MD5: like APOP or CHAP. Safe against replay and sniffing, but + * requires that your key be stored on the server, readable by the + * server account. UW-IMAP supports this method since at least 4.5, if + * the key file exists on the server. + * LOGIN: ugh. Don't use this if you can help it. Uses cleartext password + * exchange, furthermore uses unix login techniques so this same password + * can be used to log in to the server or others that share the + * credentials database. + * Pending: + * GSSAPI: strongest available form, requires Kerberos V infrastructure, + * or possibly alternatively Heimdal. + * Unavailable: + * KERBEROS_V4. Superceded by GSSAPI. + */ +int imap_authenticate (IMAP_DATA *idata, CONNECTION *conn) +{ + char buf[LONG_STRING]; + char user[SHORT_STRING], q_user[SHORT_STRING]; + char ckey[SHORT_STRING]; + char pass[SHORT_STRING], q_pass[SHORT_STRING]; + char seq[16]; + + int r = 1; + + while (r != 0) + { + if (!ImapUser) + { + strfcpy (user, NONULL(Username), sizeof (user)); + if (mutt_get_field (_("IMAP Username: "), user, sizeof (user), 0) != 0 || + !user[0]) + { + user[0] = 0; + return (-1); + } + } + else + strfcpy (user, ImapUser, sizeof (user)); + + /* attempt CRAM-MD5 if available */ + if (mutt_bit_isset (idata->capabilities, ACRAM_MD5)) + { + if (!ImapCRAMKey) + { + ckey[0] = '\0'; + snprintf (buf, sizeof (buf), _("CRAM key for %s@%s: "), user, + conn->server); + if (mutt_get_field (buf, ckey, sizeof (ckey), M_PASS) != 0 || + !ckey[0]) + return -1; + else + /* strip CR */ + ckey[strlen (ckey)] = '\0'; + } + else + strfcpy (ckey, ImapCRAMKey, sizeof (ckey)); + + if ((r = imap_auth_cram_md5 (idata, user, ckey))) + { + mutt_error _("CRAM-MD5 login failed."); + sleep (1); + } + else + return 0; + } + else + dprint (2, (debugfile, "CRAM-MD5 authentication is not supported\n")); + + if (!ImapPass) + { + pass[0]=0; + snprintf (buf, sizeof (buf), _("Password for %s@%s: "), user, conn->server); + if (mutt_get_field (buf, pass, sizeof (pass), M_PASS) != 0 || + !pass[0]) + { + return (-1); + } + } + else + strfcpy (pass, ImapPass, sizeof (pass)); + + imap_quote_string (q_user, sizeof (q_user), user); + imap_quote_string (q_pass, sizeof (q_pass), pass); + + mutt_message _("Logging in..."); + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s LOGIN %s %s\r\n", seq, q_user, q_pass); + r = imap_exec (buf, sizeof (buf), idata, seq, buf, IMAP_OK_FAIL); + if (r == -1) + { + /* connection or protocol problem */ + imap_error ("imap_open_connection()", buf); + return (-1); + } + else if (r == -2) + { + /* Login failed, try again */ + mutt_error _("Login failed."); + sleep (1); + FREE (&ImapUser); + FREE (&ImapPass); + } + else + { + /* If they have a successful login, we may as well cache the + * user/password. */ + if (!ImapUser) + ImapUser = safe_strdup (user); + if (!ImapPass) + ImapPass = safe_strdup (pass); + } + } + return 0; +} |