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