1 /* $Cambridge: exim/src/src/auths/cram_md5.c,v 1.7 2007/01/08 10:50:19 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2007 */
8 /* See the file NOTICE for conditions of use and distribution. */
11 /* The stand-alone version just tests the algorithm. We have to drag
12 in the MD5 computation functions, without their own stand-alone main
16 #define CRAM_STAND_ALONE
20 /* This is the normal, non-stand-alone case */
26 /* Options specific to the cram_md5 authentication mechanism. */
28 optionlist auth_cram_md5_options[] = {
29 { "client_name", opt_stringptr,
30 (void *)(offsetof(auth_cram_md5_options_block, client_name)) },
31 { "client_secret", opt_stringptr,
32 (void *)(offsetof(auth_cram_md5_options_block, client_secret)) },
33 { "server_secret", opt_stringptr,
34 (void *)(offsetof(auth_cram_md5_options_block, server_secret)) }
37 /* Size of the options list. An extern variable has to be used so that its
38 address can appear in the tables drtables.c. */
40 int auth_cram_md5_options_count =
41 sizeof(auth_cram_md5_options)/sizeof(optionlist);
43 /* Default private options block for the contidion authentication method. */
45 auth_cram_md5_options_block auth_cram_md5_option_defaults = {
46 NULL, /* server_secret */
47 NULL, /* client_secret */
48 NULL /* client_name */
52 /*************************************************
53 * Initialization entry point *
54 *************************************************/
56 /* Called for each instance, after its options have been read, to
57 enable consistency checks to be done, or anything else that needs
61 auth_cram_md5_init(auth_instance *ablock)
63 auth_cram_md5_options_block *ob =
64 (auth_cram_md5_options_block *)(ablock->options_block);
65 if (ob->server_secret != NULL) ablock->server = TRUE;
66 if (ob->client_secret != NULL)
68 ablock->client = TRUE;
69 if (ob->client_name == NULL) ob->client_name = primary_hostname;
73 #endif /* STAND_ALONE */
77 /*************************************************
78 * Peform the CRAM-MD5 algorithm *
79 *************************************************/
81 /* The CRAM-MD5 algorithm is described in RFC 2195. It computes
83 MD5((secret XOR opad), MD5((secret XOR ipad), challenge))
85 where secret is padded out to 64 characters (after being reduced to an MD5
86 digest if longer than 64) and ipad and opad are 64-byte strings of 0x36 and
87 0x5c respectively, and comma means concatenation.
90 secret the shared secret
91 challenge the challenge text
92 digest 16-byte slot to put the answer in
98 compute_cram_md5(uschar *secret, uschar *challenge, uschar *digestptr)
102 int len = Ustrlen(secret);
105 uschar md5secret[16];
107 /* If the secret is longer than 64 characters, we compute its MD5 digest
113 md5_end(&base, (uschar *)secret, len, md5secret);
114 secret = (uschar *)md5secret;
118 /* The key length is now known to be <= 64. Set up the padded and xor'ed
121 memcpy(isecret, secret, len);
122 memset(isecret+len, 0, 64-len);
123 memcpy(osecret, isecret, 64);
125 for (i = 0; i < 64; i++)
131 /* Compute the inner MD5 digest */
134 md5_mid(&base, isecret);
135 md5_end(&base, (uschar *)challenge, Ustrlen(challenge), md5secret);
137 /* Compute the outer MD5 digest */
140 md5_mid(&base, osecret);
141 md5_end(&base, md5secret, 16, digestptr);
147 /*************************************************
148 * Server entry point *
149 *************************************************/
151 /* For interface, see auths/README */
154 auth_cram_md5_server(auth_instance *ablock, uschar *data)
156 auth_cram_md5_options_block *ob =
157 (auth_cram_md5_options_block *)(ablock->options_block);
158 uschar *challenge = string_sprintf("<%d.%d@%s>", getpid(), time(NULL),
160 uschar *clear, *secret;
164 /* If we are running in the test harness, always send the same challenge,
165 an example string taken from the RFC. */
167 if (running_in_test_harness)
168 challenge = US"<1896.697170952@postoffice.reston.mci.net>";
170 /* No data should have been sent with the AUTH command */
172 if (*data != 0) return UNEXPECTED;
174 /* Send the challenge, read the return */
176 if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
177 if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
179 /* The return consists of a user name, space-separated from the CRAM-MD5
180 digest, expressed in hex. Extract the user name and put it in $auth1 and $1.
181 The former is now the preferred variable; the latter is the original one. Then
182 check that the remaining length is 32. */
184 auth_vars[0] = expand_nstring[1] = clear;
185 while (*clear != 0 && !isspace(*clear)) clear++;
186 if (!isspace(*clear)) return FAIL;
189 expand_nlength[1] = clear - expand_nstring[1] - 1;
190 if (len - expand_nlength[1] - 1 != 32) return FAIL;
193 /* Expand the server_secret string so that it can compute a value dependent on
194 the user name if necessary. */
196 debug_print_string(ablock->server_debug_string); /* customized debugging */
197 secret = expand_string(ob->server_secret);
199 /* A forced fail implies failure of authentication - i.e. we have no secret for
204 if (expand_string_forcedfail) return FAIL;
205 auth_defer_msg = expand_string_message;
209 /* Compute the CRAM-MD5 digest that we should have received from the client. */
211 compute_cram_md5(secret, challenge, digest);
216 debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]);
217 debug_printf(" challenge = %s\n", challenge);
218 debug_printf(" received = %s\n", clear);
219 Ustrcpy(buff," digest = ");
220 for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
221 debug_printf("%.54s\n", buff);
224 /* We now have to compare the digest, which is 16 bytes in binary, with the
225 data received, which is expressed in lower case hex. We checked above that
226 there were 32 characters of data left. */
228 for (i = 0; i < 16; i++)
232 if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) +
233 ((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL;
236 /* Expand server_condition as an authorization check */
237 return auth_check_serv_cond(ablock);
242 /*************************************************
243 * Client entry point *
244 *************************************************/
246 /* For interface, see auths/README */
249 auth_cram_md5_client(
250 auth_instance *ablock, /* authenticator block */
251 smtp_inblock *inblock, /* input connection */
252 smtp_outblock *outblock, /* output connection */
253 int timeout, /* command timeout */
254 uschar *buffer, /* for reading response */
255 int buffsize) /* size of buffer */
257 auth_cram_md5_options_block *ob =
258 (auth_cram_md5_options_block *)(ablock->options_block);
259 uschar *secret = expand_string(ob->client_secret);
260 uschar *name = expand_string(ob->client_name);
261 uschar *challenge, *p;
265 /* If expansion of either the secret or the user name failed, return CANCELLED
266 or ERROR, as approriate. */
268 if (secret == NULL || name == NULL)
270 if (expand_string_forcedfail)
272 *buffer = 0; /* No message */
275 string_format(buffer, buffsize, "expansion of \"%s\" failed in "
276 "%s authenticator: %s",
277 (secret == NULL)? ob->client_secret : ob->client_name,
278 ablock->name, expand_string_message);
282 /* Initiate the authentication exchange and read the challenge, which arrives
285 if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
287 if (smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout) < 0)
290 if (auth_b64decode(buffer + 4, &challenge) < 0)
292 string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
297 /* Run the CRAM-MD5 algorithm on the secret and the challenge */
299 compute_cram_md5(secret, challenge, digest);
301 /* Create the response from the user name plus the CRAM-MD5 digest */
303 string_format(big_buffer, big_buffer_size - 36, "%s", name);
308 for (i = 0; i < 16; i++)
310 sprintf(CS p, "%02x", digest[i]);
314 /* Send the response, in base 64, and check the result. The response is
315 in big_buffer, but auth_b64encode() returns its result in working store,
316 so calling smtp_write_command(), which uses big_buffer, is OK. */
319 if (smtp_write_command(outblock, FALSE, "%s\r\n", auth_b64encode(big_buffer,
320 p - big_buffer)) < 0) return FAIL_SEND;
322 return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)?
325 #endif /* STAND_ALONE */
328 /*************************************************
329 **************************************************
330 * Stand-alone test program *
331 **************************************************
332 *************************************************/
336 int main(int argc, char **argv)
339 uschar *secret = US argv[1];
340 uschar *challenge = US argv[2];
343 compute_cram_md5(secret, challenge, digest);
345 for (i = 0; i < 16; i++) printf("%02x", digest[i]);
353 /* End of cram_md5.c */