Another wish.
[exim.git] / src / src / auths / cram_md5.c
CommitLineData
c988f1f4 1/* $Cambridge: exim/src/src/auths/cram_md5.c,v 1.2 2005/01/04 10:00:43 ph10 Exp $ */
0756eb3c
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
c988f1f4 7/* Copyright (c) University of Cambridge 1995 - 2005 */
0756eb3c
PH
8/* See the file NOTICE for conditions of use and distribution. */
9
10
11/* The stand-alone version just tests the algorithm. We have to drag
12in the MD5 computation functions, without their own stand-alone main
13program. */
14
15#ifdef STAND_ALONE
16#define CRAM_STAND_ALONE
17#include "md5.c"
18
19
20/* This is the normal, non-stand-alone case */
21
22#else
23#include "../exim.h"
24#include "cram_md5.h"
25
26/* Options specific to the cram_md5 authentication mechanism. */
27
28optionlist 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)) }
35};
36
37/* Size of the options list. An extern variable has to be used so that its
38address can appear in the tables drtables.c. */
39
40int auth_cram_md5_options_count =
41 sizeof(auth_cram_md5_options)/sizeof(optionlist);
42
43/* Default private options block for the contidion authentication method. */
44
45auth_cram_md5_options_block auth_cram_md5_option_defaults = {
46 NULL, /* server_secret */
47 NULL, /* client_secret */
48 NULL /* client_name */
49};
50
51
52/*************************************************
53* Initialization entry point *
54*************************************************/
55
56/* Called for each instance, after its options have been read, to
57enable consistency checks to be done, or anything else that needs
58to be set up. */
59
60void
61auth_cram_md5_init(auth_instance *ablock)
62{
63auth_cram_md5_options_block *ob =
64 (auth_cram_md5_options_block *)(ablock->options_block);
65if (ob->server_secret != NULL) ablock->server = TRUE;
66if (ob->client_secret != NULL)
67 {
68 ablock->client = TRUE;
69 if (ob->client_name == NULL) ob->client_name = primary_hostname;
70 }
71}
72
73#endif /* STAND_ALONE */
74
75
76
77/*************************************************
78* Peform the CRAM-MD5 algorithm *
79*************************************************/
80
81/* The CRAM-MD5 algorithm is described in RFC 2195. It computes
82
83 MD5((secret XOR opad), MD5((secret XOR ipad), challenge))
84
85where secret is padded out to 64 characters (after being reduced to an MD5
86digest if longer than 64) and ipad and opad are 64-byte strings of 0x36 and
870x5c respectively, and comma means concatenation.
88
89Arguments:
90 secret the shared secret
91 challenge the challenge text
92 digest 16-byte slot to put the answer in
93
94Returns: nothing
95*/
96
97static void
98compute_cram_md5(uschar *secret, uschar *challenge, uschar *digestptr)
99{
100md5 base;
101int i;
102int len = Ustrlen(secret);
103uschar isecret[64];
104uschar osecret[64];
105uschar md5secret[16];
106
107/* If the secret is longer than 64 characters, we compute its MD5 digest
108and use that. */
109
110if (len > 64)
111 {
112 md5_start(&base);
113 md5_end(&base, (uschar *)secret, len, md5secret);
114 secret = (uschar *)md5secret;
115 len = 16;
116 }
117
118/* The key length is now known to be <= 64. Set up the padded and xor'ed
119versions. */
120
121memcpy(isecret, secret, len);
122memset(isecret+len, 0, 64-len);
123memcpy(osecret, isecret, 64);
124
125for (i = 0; i < 64; i++)
126 {
127 isecret[i] ^= 0x36;
128 osecret[i] ^= 0x5c;
129 }
130
131/* Compute the inner MD5 digest */
132
133md5_start(&base);
134md5_mid(&base, isecret);
135md5_end(&base, (uschar *)challenge, Ustrlen(challenge), md5secret);
136
137/* Compute the outer MD5 digest */
138
139md5_start(&base);
140md5_mid(&base, osecret);
141md5_end(&base, md5secret, 16, digestptr);
142}
143
144
145#ifndef STAND_ALONE
146
147/*************************************************
148* Server entry point *
149*************************************************/
150
151/* For interface, see auths/README */
152
153int
154auth_cram_md5_server(auth_instance *ablock, uschar *data)
155{
156auth_cram_md5_options_block *ob =
157 (auth_cram_md5_options_block *)(ablock->options_block);
158uschar *challenge = string_sprintf("<%d.%d@%s>", getpid(), time(NULL),
159 primary_hostname);
160uschar *clear, *secret;
161uschar digest[16];
162int i, rc, len;
163
164/* If we are running in the test harness, always send the same challenge,
165an example string taken from the RFC. */
166
167if (running_in_test_harness)
168 challenge = US"<1896.697170952@postoffice.reston.mci.net>";
169
170/* No data should have been sent with the AUTH command */
171
172if (*data != 0) return UNEXPECTED;
173
174/* Send the challenge, read the return */
175
176if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
177if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
178
179/* The return consists of a user name, space-separated from the CRAM-MD5
180digest, expressed in hex. Extract the user name and put it in $1. Then check
181that the remaining length is 32. */
182
183expand_nstring[1] = clear;
184while (*clear != 0 && !isspace(*clear)) clear++;
185if (!isspace(*clear)) return FAIL;
186*clear++ = 0;
187
188expand_nlength[1] = clear - expand_nstring[1] - 1;
189if (len - expand_nlength[1] - 1 != 32) return FAIL;
190expand_nmax = 1;
191
192/* Expand the server_secret string so that it can compute a value dependent on
193the user name if necessary. */
194
195debug_print_string(ablock->server_debug_string); /* customized debugging */
196secret = expand_string(ob->server_secret);
197
198/* A forced fail implies failure of authentication - i.e. we have no secret for
199the given name. */
200
201if (secret == NULL)
202 {
203 if (expand_string_forcedfail) return FAIL;
204 auth_defer_msg = expand_string_message;
205 return DEFER;
206 }
207
208/* Compute the CRAM-MD5 digest that we should have received from the client. */
209
210compute_cram_md5(secret, challenge, digest);
211
212HDEBUG(D_auth)
213 {
214 uschar buff[64];
215 debug_printf("CRAM-MD5: user name = %s\n", expand_nstring[1]);
216 debug_printf(" challenge = %s\n", challenge);
217 debug_printf(" received = %s\n", clear);
218 Ustrcpy(buff," digest = ");
219 for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
220 debug_printf("%.54s\n", buff);
221 }
222
223/* We now have to compare the digest, which is 16 bytes in binary, with the
224data received, which is expressed in lower case hex. We checked above that
225there were 32 characters of data left. */
226
227for (i = 0; i < 16; i++)
228 {
229 int a = *clear++;
230 int b = *clear++;
231 if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) +
232 ((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL;
233 }
234
235return OK;
236}
237
238
239
240/*************************************************
241* Client entry point *
242*************************************************/
243
244/* For interface, see auths/README */
245
246int
247auth_cram_md5_client(
248 auth_instance *ablock, /* authenticator block */
249 smtp_inblock *inblock, /* input connection */
250 smtp_outblock *outblock, /* output connection */
251 int timeout, /* command timeout */
252 uschar *buffer, /* for reading response */
253 int buffsize) /* size of buffer */
254{
255auth_cram_md5_options_block *ob =
256 (auth_cram_md5_options_block *)(ablock->options_block);
257uschar *secret = expand_string(ob->client_secret);
258uschar *name = expand_string(ob->client_name);
259uschar *challenge, *p;
260int i;
261uschar digest[16];
262
263/* If expansion of either the secret or the user name failed, return CANCELLED
264or ERROR, as approriate. */
265
266if (secret == NULL || name == NULL)
267 {
268 if (expand_string_forcedfail) return CANCELLED;
269 string_format(buffer, buffsize, "expansion of \"%s\" failed in "
270 "%s authenticator: %s",
271 (secret == NULL)? ob->client_secret : ob->client_name,
272 ablock->name, expand_string_message);
273 return ERROR;
274 }
275
276/* Initiate the authentication exchange and read the challenge, which arrives
277in base 64. */
278
279if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
280 return FAIL_SEND;
281if (smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout) < 0)
282 return FAIL;
283
284if (auth_b64decode(buffer + 4, &challenge) < 0)
285 {
286 string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
287 big_buffer + 4);
288 return ERROR;
289 }
290
291/* Run the CRAM-MD5 algorithm on the secret and the challenge */
292
293compute_cram_md5(secret, challenge, digest);
294
295/* Create the response from the user name plus the CRAM-MD5 digest */
296
297string_format(big_buffer, big_buffer_size - 36, "%s", name);
298p = big_buffer;
299while (*p != 0) p++;
300*p++ = ' ';
301
302for (i = 0; i < 16; i++)
303 {
304 sprintf(CS p, "%02x", digest[i]);
305 p += 2;
306 }
307
308/* Send the response, in base 64, and check the result. The response is
309in big_buffer, but auth_b64encode() returns its result in working store,
310so calling smtp_write_command(), which uses big_buffer, is OK. */
311
312buffer[0] = 0;
313if (smtp_write_command(outblock, FALSE, "%s\r\n", auth_b64encode(big_buffer,
314 p - big_buffer)) < 0) return FAIL_SEND;
315
316return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)?
317 OK : FAIL;
318}
319#endif /* STAND_ALONE */
320
321
322/*************************************************
323**************************************************
324* Stand-alone test program *
325**************************************************
326*************************************************/
327
328#ifdef STAND_ALONE
329
330int main(int argc, char **argv)
331{
332int i;
333uschar *secret = US argv[1];
334uschar *challenge = US argv[2];
335uschar digest[16];
336
337compute_cram_md5(secret, challenge, digest);
338
339for (i = 0; i < 16; i++) printf("%02x", digest[i]);
340printf("\n");
341
342return 0;
343}
344
345#endif
346
347/* End of cram_md5.c */