summaryrefslogtreecommitdiffstats
path: root/imap/auth.c
diff options
context:
space:
mode:
authorThomas Roessler <roessler@does-not-exist.org>1999-08-20 08:24:01 +0000
committerThomas Roessler <roessler@does-not-exist.org>1999-08-20 08:24:01 +0000
commit9febf2c9a98ebcebded37065bc6058adfb0847a9 (patch)
treec6f06f934b0b32f38486a743e0596da140ee08d6 /imap/auth.c
parentd79ed35520d7d5a3658e309af0e3e00d30ddf45d (diff)
Brendan Cully's latest IMAP clean-up.
Diffstat (limited to 'imap/auth.c')
-rw-r--r--imap/auth.c291
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;
+}