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 "fm_md5.h"
19
20
void hmac_md5 (const unsigned char *password,  size_t pass_len,
21
               const 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, const char *command, struct query *ctl, const char *strip)
64
/* authenticate as per RFC2195 */
65
{
66
    int result;
67
    int len;
68
    char buf1[1024];
69
    char msg_id[768];
70
    unsigned char response[16];
71
    char reply[1024];
72
    char *respdata;
73
74
    gen_send (sock, "%s CRAM-MD5", command);
75
76
    /* From RFC2195:
77
     * The data encoded in the first ready response contains an
78
     * presumptively arbitrary string of random digits, a timestamp, and the
79
     * fully-qualified primary host name of the server.  The syntax of the
80
     * unencoded form must correspond to that of an RFC 822 'msg-id'
81
     * [RFC822] as described in [POP3].
82
     */
83
84
    if ((result = gen_recv (sock, buf1, sizeof (buf1)))) {
85
	return result;
86
    }
87
88
    /* caller may specify a response prefix we should strip if present */
89
    respdata = buf1;
90
    if (strip && strncmp(buf1, strip, strlen(strip)) == 0)
91
	respdata += strlen(strip);
92
    len = from64tobits (msg_id, respdata, sizeof(msg_id));
93
94
    if (len < 0) {
95
	report (stderr, GT_("could not decode BASE64 challenge\n"));
96
	return PS_AUTHFAIL;
97
    } else if ((size_t)len < sizeof (msg_id)) {
98
        msg_id[len] = 0;
99
    } else {
100
        msg_id[sizeof (msg_id)-1] = 0;
101
    }
102
    if (outlevel >= O_DEBUG) {
103
        report (stdout, GT_("decoded as %s\n"), msg_id);
104
    }
105
106
    /* The client makes note of the data and then responds with a string
107
     * consisting of the user name, a space, and a 'digest'.  The latter is
108
     * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
109
     * the key is a shared secret and the digested text is the timestamp
110
     * (including angle-brackets).
111
     */
112
113
    hmac_md5((unsigned char *)ctl->password, strlen(ctl->password),
114
              (unsigned char *)msg_id, strlen (msg_id),
115
              response, sizeof (response));
116
117
    snprintf (reply, sizeof(reply),
118
              "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 
119
              ctl->remotename,
120
              response[0], response[1], response[2], response[3],
121
              response[4], response[5], response[6], response[7],
122
              response[8], response[9], response[10], response[11],
123
              response[12], response[13], response[14], response[15]);
124
125
    to64frombits (buf1, reply, strlen(reply));
126
127
    /* ship the authentication back, accept the server's responses */
128
    /* PMDF5.2 IMAP has a bug that requires this to be a single write */
129
    suppress_tags = TRUE;
130
    result = gen_transact(sock, "%s", buf1);
131
    suppress_tags = FALSE;
132
    if (result)
133
	return(result);
134
    else
135
	return(PS_SUCCESS);
136
}
137
138
/* cram.c ends here */