]> Pileus Git - ~andy/fetchmail/blob - cram.c
Try to get around the CRAM-MD5 bug.
[~andy/fetchmail] / cram.c
1 /*
2  * cram.c -- CRAM-MD5 authentication (see RFC 2195)
3  *
4  * For license terms, see the file COPYING in this directory.
5  */
6
7 #include  "config.h"
8 #include  <stdio.h>
9 #include  <string.h>
10 #include  <ctype.h>
11 #if defined(STDC_HEADERS)
12 #include  <stdlib.h>
13 #endif
14 #include  "fetchmail.h"
15 #include  "socket.h"
16
17 #include  "i18n.h"
18 #include "md5.h"
19
20 static void hmac_md5 (unsigned char *password,  size_t pass_len,
21                       unsigned char *challenge, size_t chal_len,
22                       unsigned char *response,  size_t resp_len)
23 {
24     int i;
25     unsigned char ipad[64];
26     unsigned char opad[64];
27     unsigned char hash_passwd[16];
28
29     MD5_CTX ctx;
30     
31     if (resp_len != 16)
32         return;
33
34     if (pass_len > sizeof (ipad))
35     {
36         MD5Init (&ctx);
37         MD5Update (&ctx, password, pass_len);
38         MD5Final (hash_passwd, &ctx);
39         password = hash_passwd; pass_len = sizeof (hash_passwd);
40     }
41
42     memset (ipad, 0, sizeof (ipad));
43     memset (opad, 0, sizeof (opad));
44     memcpy (ipad, password, pass_len);
45     memcpy (opad, password, pass_len);
46
47     for (i=0; i<64; i++) {
48         ipad[i] ^= 0x36;
49         opad[i] ^= 0x5c;
50     }
51
52     MD5Init (&ctx);
53     MD5Update (&ctx, ipad, sizeof (ipad));
54     MD5Update (&ctx, challenge, chal_len);
55     MD5Final (response, &ctx);
56
57     MD5Init (&ctx);
58     MD5Update (&ctx, opad, sizeof (opad));
59     MD5Update (&ctx, response, resp_len);
60     MD5Final (response, &ctx);
61 }
62
63 int do_cram_md5 (int sock, struct query *ctl)
64 /* authenticate as per RFC2195 */
65 {
66     int result;
67     int len;
68     unsigned char buf1[1024];
69     unsigned char msg_id[768];
70     unsigned char response[16];
71     unsigned char reply[1024];
72
73     gen_send (sock, "AUTHENTICATE CRAM-MD5");
74
75     /* From RFC2195:
76      * The data encoded in the first ready response contains an
77      * presumptively arbitrary string of random digits, a timestamp, and the
78      * fully-qualified primary host name of the server.  The syntax of the
79      * unencoded form must correspond to that of an RFC 822 'msg-id'
80      * [RFC822] as described in [POP3].
81      */
82
83     if ((result = gen_recv (sock, buf1, sizeof (buf1)))) {
84         return result;
85     }
86
87     len = from64tobits (msg_id, buf1);
88     if (len < 0) {
89         report (stderr, _("could not decode BASE64 challenge\n"));
90         return PS_AUTHFAIL;
91     } else if (len < sizeof (msg_id)) {
92         msg_id[len] = 0;
93     } else {
94         msg_id[sizeof (msg_id)-1] = 0;
95     }
96     if (outlevel >= O_DEBUG) {
97         report (stdout, _("decoded as %s\n"), msg_id);
98     }
99
100     /* The client makes note of the data and then responds with a string
101      * consisting of the user name, a space, and a 'digest'.  The latter is
102      * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
103      * the key is a shared secret and the digested text is the timestamp
104      * (including angle-brackets).
105      */
106
107     hmac_md5(ctl->password, strlen(ctl->password),
108               msg_id, strlen (msg_id),
109               response, sizeof (response));
110
111 #ifdef HAVE_SNPRINTF
112     snprintf (reply, sizeof (reply),
113 #else
114     sprintf(reply,
115 #endif
116               "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 
117               ctl->remotename,
118               response[0], response[1], response[2], response[3],
119               response[4], response[5], response[6], response[7],
120               response[8], response[9], response[10], response[11],
121               response[12], response[13], response[14], response[15]);
122
123     if (outlevel >= O_DEBUG) {
124         report (stdout, _("replying with %s\n"), reply);
125     }
126
127     to64frombits (buf1, reply, strlen(reply));
128     if (outlevel >= O_MONITOR) {
129         report (stdout, "CRAM> %s\n", buf1);
130     }
131
132     /* ship the authentication back, accept the server's responses */
133     /* PMDF5.2 IMAP has a bug that requires this to be a single write */
134     result = gen_transact(sock, buf1, sizeof(buf1));
135     if (result)
136         return(result);
137     else
138         return(PS_SUCCESS);
139 }
140
141 /* cram.c ends here */