constification
[exim.git] / src / src / auths / cram_md5.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
0756eb3c
PH
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
4c04137d 41/* Default private options block for the condition authentication method. */
0756eb3c
PH
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
d185889f
JH
50#ifdef MACRO_PREDEF
51
52/* Dummy values */
53void auth_cram_md5_init(auth_instance *ablock) {}
54int auth_cram_md5_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
55int auth_cram_md5_client(auth_instance *ablock, void *sx, int timeout,
56 uschar *buffer, int buffsize) {return 0;}
d185889f
JH
57
58#else /*!MACRO_PREDEF*/
59
60
0756eb3c
PH
61/*************************************************
62* Initialization entry point *
63*************************************************/
64
65/* Called for each instance, after its options have been read, to
66enable consistency checks to be done, or anything else that needs
67to be set up. */
68
69void
70auth_cram_md5_init(auth_instance *ablock)
71{
72auth_cram_md5_options_block *ob =
73 (auth_cram_md5_options_block *)(ablock->options_block);
74if (ob->server_secret != NULL) ablock->server = TRUE;
75if (ob->client_secret != NULL)
76 {
77 ablock->client = TRUE;
78 if (ob->client_name == NULL) ob->client_name = primary_hostname;
79 }
80}
81
d185889f 82#endif /*!MACRO_PREDEF*/
0756eb3c
PH
83#endif /* STAND_ALONE */
84
85
86
d185889f 87#ifndef MACRO_PREDEF
0756eb3c 88/*************************************************
4c04137d 89* Perform the CRAM-MD5 algorithm *
0756eb3c
PH
90*************************************************/
91
92/* The CRAM-MD5 algorithm is described in RFC 2195. It computes
93
94 MD5((secret XOR opad), MD5((secret XOR ipad), challenge))
95
96where secret is padded out to 64 characters (after being reduced to an MD5
97digest if longer than 64) and ipad and opad are 64-byte strings of 0x36 and
980x5c respectively, and comma means concatenation.
99
100Arguments:
101 secret the shared secret
102 challenge the challenge text
103 digest 16-byte slot to put the answer in
104
105Returns: nothing
106*/
107
108static void
109compute_cram_md5(uschar *secret, uschar *challenge, uschar *digestptr)
110{
111md5 base;
0756eb3c
PH
112int len = Ustrlen(secret);
113uschar isecret[64];
114uschar osecret[64];
115uschar md5secret[16];
116
117/* If the secret is longer than 64 characters, we compute its MD5 digest
118and use that. */
119
120if (len > 64)
121 {
122 md5_start(&base);
5903c6ff
JH
123 md5_end(&base, US secret, len, md5secret);
124 secret = US md5secret;
0756eb3c
PH
125 len = 16;
126 }
127
128/* The key length is now known to be <= 64. Set up the padded and xor'ed
129versions. */
130
131memcpy(isecret, secret, len);
132memset(isecret+len, 0, 64-len);
133memcpy(osecret, isecret, 64);
134
d7978c0f 135for (int i = 0; i < 64; i++)
0756eb3c
PH
136 {
137 isecret[i] ^= 0x36;
138 osecret[i] ^= 0x5c;
139 }
140
141/* Compute the inner MD5 digest */
142
143md5_start(&base);
144md5_mid(&base, isecret);
5903c6ff 145md5_end(&base, US challenge, Ustrlen(challenge), md5secret);
0756eb3c
PH
146
147/* Compute the outer MD5 digest */
148
149md5_start(&base);
150md5_mid(&base, osecret);
151md5_end(&base, md5secret, 16, digestptr);
152}
153
154
155#ifndef STAND_ALONE
156
157/*************************************************
158* Server entry point *
159*************************************************/
160
161/* For interface, see auths/README */
162
163int
164auth_cram_md5_server(auth_instance *ablock, uschar *data)
165{
166auth_cram_md5_options_block *ob =
167 (auth_cram_md5_options_block *)(ablock->options_block);
438257ba
PP
168uschar *challenge = string_sprintf("<%d.%ld@%s>", getpid(),
169 (long int) time(NULL), primary_hostname);
0756eb3c
PH
170uschar *clear, *secret;
171uschar digest[16];
172int i, rc, len;
173
174/* If we are running in the test harness, always send the same challenge,
175an example string taken from the RFC. */
176
8768d548 177if (f.running_in_test_harness)
0756eb3c
PH
178 challenge = US"<1896.697170952@postoffice.reston.mci.net>";
179
180/* No data should have been sent with the AUTH command */
181
182if (*data != 0) return UNEXPECTED;
183
184/* Send the challenge, read the return */
185
186if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
f4d091fb 187if ((len = b64decode(data, &clear)) < 0) return BAD64;
0756eb3c
PH
188
189/* The return consists of a user name, space-separated from the CRAM-MD5
f78eb7c6
PH
190digest, expressed in hex. Extract the user name and put it in $auth1 and $1.
191The former is now the preferred variable; the latter is the original one. Then
192check that the remaining length is 32. */
0756eb3c 193
f78eb7c6 194auth_vars[0] = expand_nstring[1] = clear;
0756eb3c
PH
195while (*clear != 0 && !isspace(*clear)) clear++;
196if (!isspace(*clear)) return FAIL;
197*clear++ = 0;
198
199expand_nlength[1] = clear - expand_nstring[1] - 1;
200if (len - expand_nlength[1] - 1 != 32) return FAIL;
201expand_nmax = 1;
202
203/* Expand the server_secret string so that it can compute a value dependent on
204the user name if necessary. */
205
206debug_print_string(ablock->server_debug_string); /* customized debugging */
207secret = expand_string(ob->server_secret);
208
209/* A forced fail implies failure of authentication - i.e. we have no secret for
210the given name. */
211
212if (secret == NULL)
213 {
8768d548 214 if (f.expand_string_forcedfail) return FAIL;
0756eb3c
PH
215 auth_defer_msg = expand_string_message;
216 return DEFER;
217 }
218
219/* Compute the CRAM-MD5 digest that we should have received from the client. */
220
221compute_cram_md5(secret, challenge, digest);
222
223HDEBUG(D_auth)
224 {
225 uschar buff[64];
f78eb7c6 226 debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]);
0756eb3c
PH
227 debug_printf(" challenge = %s\n", challenge);
228 debug_printf(" received = %s\n", clear);
229 Ustrcpy(buff," digest = ");
230 for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
231 debug_printf("%.54s\n", buff);
232 }
233
234/* We now have to compare the digest, which is 16 bytes in binary, with the
235data received, which is expressed in lower case hex. We checked above that
236there were 32 characters of data left. */
237
238for (i = 0; i < 16; i++)
239 {
240 int a = *clear++;
241 int b = *clear++;
242 if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) +
243 ((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL;
244 }
245
16ff981e
PH
246/* Expand server_condition as an authorization check */
247return auth_check_serv_cond(ablock);
0756eb3c
PH
248}
249
250
251
252/*************************************************
253* Client entry point *
254*************************************************/
255
256/* For interface, see auths/README */
257
258int
259auth_cram_md5_client(
260 auth_instance *ablock, /* authenticator block */
251b9eb4 261 void * sx, /* smtp connextion */
0756eb3c 262 int timeout, /* command timeout */
4730f942 263 uschar *buffer, /* for reading response */
0756eb3c
PH
264 int buffsize) /* size of buffer */
265{
266auth_cram_md5_options_block *ob =
267 (auth_cram_md5_options_block *)(ablock->options_block);
268uschar *secret = expand_string(ob->client_secret);
269uschar *name = expand_string(ob->client_name);
270uschar *challenge, *p;
271int i;
272uschar digest[16];
273
274/* If expansion of either the secret or the user name failed, return CANCELLED
4c04137d 275or ERROR, as appropriate. */
0756eb3c 276
d129bdf7 277if (!secret || !name)
0756eb3c 278 {
8768d548 279 if (f.expand_string_forcedfail)
4730f942
PH
280 {
281 *buffer = 0; /* No message */
282 return CANCELLED;
283 }
0756eb3c
PH
284 string_format(buffer, buffsize, "expansion of \"%s\" failed in "
285 "%s authenticator: %s",
d129bdf7 286 !secret ? ob->client_secret : ob->client_name,
0756eb3c
PH
287 ablock->name, expand_string_message);
288 return ERROR;
289 }
290
291/* Initiate the authentication exchange and read the challenge, which arrives
292in base 64. */
293
251b9eb4 294if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
0756eb3c 295 return FAIL_SEND;
251b9eb4 296if (!smtp_read_response(sx, buffer, buffsize, '3', timeout))
0756eb3c
PH
297 return FAIL;
298
f4d091fb 299if (b64decode(buffer + 4, &challenge) < 0)
0756eb3c
PH
300 {
301 string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
302 big_buffer + 4);
303 return ERROR;
304 }
305
306/* Run the CRAM-MD5 algorithm on the secret and the challenge */
307
308compute_cram_md5(secret, challenge, digest);
309
310/* Create the response from the user name plus the CRAM-MD5 digest */
311
312string_format(big_buffer, big_buffer_size - 36, "%s", name);
d129bdf7 313for (p = big_buffer; *p; ) p++;
0756eb3c
PH
314*p++ = ' ';
315
316for (i = 0; i < 16; i++)
5976eb99 317 p += sprintf(CS p, "%02x", digest[i]);
0756eb3c
PH
318
319/* Send the response, in base 64, and check the result. The response is
f4d091fb 320in big_buffer, but b64encode() returns its result in working store,
0756eb3c
PH
321so calling smtp_write_command(), which uses big_buffer, is OK. */
322
323buffer[0] = 0;
1f20760b 324if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS big_buffer,
0756eb3c
PH
325 p - big_buffer)) < 0) return FAIL_SEND;
326
251b9eb4 327return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
d129bdf7 328 ? OK : FAIL;
0756eb3c
PH
329}
330#endif /* STAND_ALONE */
331
332
333/*************************************************
334**************************************************
335* Stand-alone test program *
336**************************************************
337*************************************************/
338
339#ifdef STAND_ALONE
340
341int main(int argc, char **argv)
342{
343int i;
344uschar *secret = US argv[1];
345uschar *challenge = US argv[2];
346uschar digest[16];
347
348compute_cram_md5(secret, challenge, digest);
349
350for (i = 0; i < 16; i++) printf("%02x", digest[i]);
351printf("\n");
352
353return 0;
354}
355
356#endif
357
d185889f 358#endif /*!MACRO_PREDEF*/
0756eb3c 359/* End of cram_md5.c */