* New version 2.26
[alpine.git] / imap / src / c-client / auth_md5.c
blobe45218dbcea6888d604e26b3993d7c13d0229961
1 /*
2 * Copyright 2016-2022 Eduardo Chappa
3 * Last Modified: August 11, 2016
4 */
5 /* ========================================================================
6 * Copyright 2008-2011 Mark Crispin
7 * ========================================================================
8 */
11 * Program: CRAM-MD5 authenticator
13 * Author: Mark Crispin
15 * Date: 21 October 1998
16 * Last Edited: 8 April 2011
18 * Previous versions of this file were
20 * Copyright 1988-2007 University of Washington
22 * Licensed under the Apache License, Version 2.0 (the "License");
23 * you may not use this file except in compliance with the License.
24 * You may obtain a copy of the License at
26 * http://www.apache.org/licenses/LICENSE-2.0
30 /* MD5 context */
32 #define MD5BLKLEN 64 /* MD5 block length */
33 #define MD5DIGLEN 16 /* MD5 digest length */
35 typedef struct {
36 unsigned long chigh; /* high 32bits of byte count */
37 unsigned long clow; /* low 32bits of byte count */
38 unsigned long state[4]; /* state (ABCD) */
39 unsigned char buf[MD5BLKLEN]; /* input buffer */
40 unsigned char *ptr; /* buffer position */
41 } MD5CONTEXT;
44 /* Prototypes */
46 long auth_md5_valid (void);
47 long auth_md5_client (authchallenge_t challenger,authrespond_t responder,char *base,
48 char *service,NETMBX *mb,void *stream, unsigned long port,
49 unsigned long *trial,char *user);
50 char *auth_md5_server (authresponse_t responder,int argc,char *argv[]);
51 char *auth_md5_pwd (char *user);
52 char *apop_login (char *chal,char *user,char *md5,int argc,char *argv[]);
53 char *hmac_md5 (char *hshbuf, char *text,unsigned long tl,char *key,
54 unsigned long kl);
55 void md5_init (MD5CONTEXT *ctx);
56 void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len);
57 void md5_final (unsigned char *digest,MD5CONTEXT *ctx);
58 static void md5_transform (unsigned long *state,unsigned char *block);
59 static void md5_encode (unsigned char *dst,unsigned long *src,int len);
60 static void md5_decode (unsigned long *dst,unsigned char *src,int len);
63 /* Authenticator linkage */
65 AUTHENTICATOR auth_md5 = {
66 AU_SECURE, /* secure authenticator */
67 "CRAM-MD5", /* authenticator name */
68 auth_md5_valid, /* check if valid */
69 auth_md5_client, /* client method */
70 auth_md5_server, /* server method */
71 NIL /* next authenticator */
74 /* Check if CRAM-MD5 valid on this system
75 * Returns: T, always
78 long auth_md5_valid (void)
80 struct stat sbuf;
81 /* server forbids MD5 if no MD5 enable file */
82 if (stat (MD5ENABLE,&sbuf)) auth_md5.server = NIL;
83 return T; /* MD5 is otherwise valid */
87 /* Client authenticator
88 * Accepts: challenger function
89 * responder function
90 * SASL service name
91 * parsed network mailbox structure
92 * stream argument for functions
93 * pointer to current trial count
94 * returned user name
95 * Returns: T if success, NIL otherwise, number of trials incremented if retry
98 long auth_md5_client (authchallenge_t challenger,authrespond_t responder,char *base,
99 char *service,NETMBX *mb,void *stream, unsigned long port,
100 unsigned long *trial,char *user)
102 char *pwd = NIL,hshbuf[2*MD5DIGLEN + 1];
103 void *challenge;
104 unsigned long clen;
105 long ret = NIL;
106 /* get challenge */
107 if ((challenge = (*challenger) (stream,&clen)) != NULL) {
108 mm_login (mb,user, &pwd,*trial);
109 if (!pwd) { /* user requested abort */
110 fs_give ((void **) &challenge);
111 (*responder) (stream,NIL,NIL,0);
112 *trial = 0; /* cancel subsequent attempts */
113 ret = LONGT; /* will get a BAD response back */
115 else { /* got password, build response */
116 char tmp[128];
117 sprintf (tmp,"%.65s %.33s",user,hmac_md5 (hshbuf,challenge,clen,
118 pwd,strlen (pwd)));
119 fs_give ((void **) &challenge);
120 /* send credentials, allow retry if OK */
121 if ((*responder) (stream,NIL,tmp,strlen (tmp))) {
122 if ((challenge = (*challenger) (stream,&clen)) != NULL)
123 fs_give ((void **) &challenge);
124 else {
125 ++*trial; /* can try again if necessary */
126 ret = LONGT; /* check the authentication */
129 memset((void *) tmp, 0, sizeof(tmp));
132 if(pwd){
133 memset((void *) pwd, 0, strlen(pwd));
134 fs_give((void **) &pwd);
136 if (!ret) *trial = 65535; /* don't retry if bad protocol */
137 return ret;
140 /* Server authenticator
141 * Accepts: responder function
142 * argument count
143 * argument vector
144 * Returns: authenticated user name or NIL
146 * This is much hairier than it needs to be due to the necessary of zapping
147 * the password data.
150 static int md5try = MAXLOGINTRIALS;
152 char *auth_md5_server (authresponse_t responder,int argc,char *argv[])
154 char *ret = NIL;
155 char *p,*u,*user,*authuser,*hash,chal[MAILTMPLEN],hshbuf[2*MD5DIGLEN + 1];
156 unsigned long cl,pl;
157 /* generate challenge */
158 sprintf (chal,"<%lu.%lu@%s>",(unsigned long) getpid (),
159 (unsigned long) time (0),mylocalhost ());
160 /* send challenge, get user and hash */
161 if ((user = (*responder) (chal,cl = strlen (chal),NIL)) != NULL) {
162 /* got user, locate hash */
163 if ((hash = strrchr (user,' ')) != NULL) {
164 *hash++ = '\0'; /* tie off user */
165 /* see if authentication user */
166 if ((authuser = strchr (user,'*')) != NULL) *authuser++ = '\0';
167 /* get password */
168 if ((p = auth_md5_pwd ((authuser && *authuser) ? authuser : user)) != NULL) {
169 pl = strlen (p);
170 u = (md5try && !strcmp (hash,hmac_md5 (hshbuf,chal,cl,p,pl))) ?
171 user : NIL;
172 memset (p,0,pl); /* erase sensitive information */
173 fs_give ((void **) &p); /* flush erased password */
174 /* now log in for real */
175 if (u && authserver_login (u,authuser,argc,argv)) ret = myusername ();
176 else if (md5try) --md5try;
179 fs_give ((void **) &user);
181 if (!ret) sleep (3); /* slow down possible cracker */
182 return ret;
185 /* Return MD5 password for user
186 * Accepts: user name
187 * Returns: plaintext password if success, else NIL
189 * This is much hairier than it needs to be due to the necessary of zapping
190 * the password data. That's why we don't use stdio here.
193 char *auth_md5_pwd (char *user)
195 struct stat sbuf;
196 int fd = open (MD5ENABLE,O_RDONLY,NIL);
197 unsigned char *s,*t,*buf,*lusr,*lret;
198 char *r;
199 char *ret = NIL;
200 if (fd >= 0) { /* found the file? */
201 fstat (fd,&sbuf); /* yes, slurp it into memory */
202 if(read (fd,buf = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size) < 0)
203 fatal("error on read() call in auth_md5_pwd");
204 /* see if any uppercase characters in user */
205 for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++);
206 /* yes, make lowercase copy */
207 lusr = *s ? lcase (cpystr (user)) : NIL;
208 for (s = strtok_r ((char *) buf,"\015\012",&r),lret = NIL; s;
209 s = ret ? NIL : strtok_r (NIL,"\015\012",&r))
210 /* must be valid entry line */
211 if (*s && (*s != '#') && (t = strchr (s,'\t')) && t[1]) {
212 *t++ = '\0'; /* found tab, tie off user, point to pwd */
213 if (!strcmp (s,user)) ret = cpystr (t);
214 else if (lusr && !lret) if (!strcmp (s,lusr)) lret = t;
216 /* accept case-independent name */
217 if (!ret && lret) ret = cpystr (lret);
218 /* don't need lowercase copy any more */
219 if (lusr) fs_give ((void **) &lusr);
220 /* erase sensitive information from buffer */
221 memset (buf,0,sbuf.st_size + 1);
222 fs_give ((void **) &buf); /* flush the buffer */
223 close (fd); /* don't need file any longer */
225 return ret; /* return password */
228 /* APOP server login
229 * Accepts: challenge
230 * desired user name
231 * purported MD5
232 * argument count
233 * argument vector
234 * Returns: authenticated user name or NIL
237 char *apop_login (char *chal,char *user,char *md5,int argc,char *argv[])
239 int i,j;
240 char *ret = NIL;
241 char *s,*authuser,tmp[MAILTMPLEN];
242 unsigned char digest[MD5DIGLEN];
243 MD5CONTEXT ctx;
244 char *hex = "0123456789abcdef";
245 /* see if authentication user */
246 if ((authuser = strchr (user,'*')) != NULL) *authuser++ = '\0';
247 /* get password */
248 if ((s = auth_md5_pwd ((authuser && *authuser) ? authuser : user)) != NULL) {
249 md5_init (&ctx); /* initialize MD5 context */
250 /* build string to get MD5 digest */
251 sprintf (tmp,"%.128s%.128s",chal,s);
252 memset (s,0,strlen (s)); /* erase sensitive information */
253 fs_give ((void **) &s); /* flush erased password */
254 md5_update (&ctx,(unsigned char *) tmp,strlen (tmp));
255 memset (tmp,0,MAILTMPLEN); /* erase sensitive information */
256 md5_final (digest,&ctx);
257 /* convert to printable hex */
258 for (i = 0, s = tmp; i < MD5DIGLEN; i++) {
259 *s++ = hex[(j = digest[i]) >> 4];
260 *s++ = hex[j & 0xf];
262 *s = '\0'; /* tie off hash text */
263 memset (digest,0,MD5DIGLEN);/* erase sensitive information */
264 if (md5try && !strcmp (md5,tmp) &&
265 authserver_login (user,authuser,argc,argv))
266 ret = cpystr (myusername ());
267 else if (md5try) --md5try;
268 memset (tmp,0,MAILTMPLEN); /* erase sensitive information */
270 if (!ret) sleep (3); /* slow down possible cracker */
271 return ret;
275 * RFC 2104 HMAC hashing
276 * Accepts: destination buffer of size 2*MD5DIGLEN + 1
277 * text to hash
278 * text length
279 * key
280 * key length
281 * Returns: hash as text, always
284 char *hmac_md5 (char *hshbuf, char *text,unsigned long tl,char *key,
285 unsigned long kl)
287 int i,j;
288 char *s;
289 MD5CONTEXT ctx;
290 char *hex = "0123456789abcdef";
291 unsigned char digest[MD5DIGLEN],k_ipad[MD5BLKLEN+1],k_opad[MD5BLKLEN+1];
292 if (kl > MD5BLKLEN) { /* key longer than pad length? */
293 md5_init (&ctx); /* yes, set key as MD5(key) */
294 md5_update (&ctx,(unsigned char *) key,kl);
295 md5_final (digest,&ctx);
296 key = (char *) digest;
297 kl = MD5DIGLEN;
299 memcpy (k_ipad,key,kl); /* store key in pads */
300 memset (k_ipad+kl,0,(MD5BLKLEN+1)-kl);
301 memcpy (k_opad,k_ipad,MD5BLKLEN+1);
302 /* XOR key with ipad and opad values */
303 for (i = 0; i < MD5BLKLEN; i++) {
304 k_ipad[i] ^= 0x36;
305 k_opad[i] ^= 0x5c;
307 md5_init (&ctx); /* inner MD5: hash ipad and text */
308 md5_update (&ctx,k_ipad,MD5BLKLEN);
309 md5_update (&ctx,(unsigned char *) text,tl);
310 md5_final (digest,&ctx);
311 md5_init (&ctx); /* outer MD5: hash opad and inner results */
312 md5_update (&ctx,k_opad,MD5BLKLEN);
313 md5_update (&ctx,digest,MD5DIGLEN);
314 md5_final (digest,&ctx);
315 /* convert to printable hex */
316 for (i = 0, s = hshbuf; i < MD5DIGLEN; i++) {
317 *s++ = hex[(j = digest[i]) >> 4];
318 *s++ = hex[j & 0xf];
320 *s = '\0'; /* tie off hash text */
321 return hshbuf;
324 /* Everything after this point is derived from the RSA Data Security, Inc.
325 * MD5 Message-Digest Algorithm
328 /* You may wonder why these strange "a &= 0xffffffff;" statements are here.
329 * This is to ensure correct results on machines with a unsigned long size of
330 * larger than 32 bits.
333 #define RND1(a,b,c,d,x,s,ac) \
334 a += ((b & c) | (d & ~b)) + x + (unsigned long) ac; \
335 a &= 0xffffffff; \
336 a = b + ((a << s) | (a >> (32 - s)));
338 #define RND2(a,b,c,d,x,s,ac) \
339 a += ((b & d) | (c & ~d)) + x + (unsigned long) ac; \
340 a &= 0xffffffff; \
341 a = b + ((a << s) | (a >> (32 - s)));
343 #define RND3(a,b,c,d,x,s,ac) \
344 a += (b ^ c ^ d) + x + (unsigned long) ac; \
345 a &= 0xffffffff; \
346 a = b + ((a << s) | (a >> (32 - s)));
348 #define RND4(a,b,c,d,x,s,ac) \
349 a += (c ^ (b | ~d)) + x + (unsigned long) ac; \
350 a &= 0xffffffff; \
351 a = b + ((a << s) | (a >> (32 - s)));
353 /* Initialize MD5 context
354 * Accepts: context to initialize
357 void md5_init (MD5CONTEXT *ctx)
359 ctx->clow = ctx->chigh = 0; /* initialize byte count to zero */
360 /* initialization constants */
361 ctx->state[0] = 0x67452301; ctx->state[1] = 0xefcdab89;
362 ctx->state[2] = 0x98badcfe; ctx->state[3] = 0x10325476;
363 ctx->ptr = ctx->buf; /* reset buffer pointer */
367 /* MD5 add data to context
368 * Accepts: context
369 * input data
370 * length of data
373 void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len)
375 unsigned long i = (ctx->buf + MD5BLKLEN) - ctx->ptr;
376 /* update double precision number of bytes */
377 if ((ctx->clow += len) < len) ctx->chigh++;
378 while (i <= len) { /* copy/transform data, 64 bytes at a time */
379 memcpy (ctx->ptr,data,i); /* fill up 64 byte chunk */
380 md5_transform (ctx->state,ctx->ptr = ctx->buf);
381 data += i,len -= i,i = MD5BLKLEN;
383 memcpy (ctx->ptr,data,len); /* copy final bit of data in buffer */
384 ctx->ptr += len; /* update buffer pointer */
387 /* MD5 Finalization
388 * Accepts: destination digest
389 * context
392 void md5_final (unsigned char *digest,MD5CONTEXT *ctx)
394 unsigned long i,bits[2];
395 bits[0] = ctx->clow << 3; /* calculate length in bits (before padding) */
396 bits[1] = (ctx->chigh << 3) + (ctx->clow >> 29);
397 *ctx->ptr++ = 0x80; /* padding byte */
398 if ((i = (ctx->buf + MD5BLKLEN) - ctx->ptr) < 8) {
399 memset (ctx->ptr,0,i); /* pad out buffer with zeros */
400 md5_transform (ctx->state,ctx->buf);
401 /* pad out with zeros, leaving 8 bytes */
402 memset (ctx->buf,0,MD5BLKLEN - 8);
403 ctx->ptr = ctx->buf + MD5BLKLEN - 8;
405 else if (i -= 8) { /* need to pad this buffer? */
406 memset (ctx->ptr,0,i); /* yes, pad out with zeros, leaving 8 bytes */
407 ctx->ptr += i;
409 md5_encode (ctx->ptr,bits,2); /* make LSB-first length */
410 md5_transform (ctx->state,ctx->buf);
411 /* store state in digest */
412 md5_encode (digest,ctx->state,4);
413 /* erase context */
414 memset (ctx,0,sizeof (MD5CONTEXT));
417 /* MD5 basic transformation
418 * Accepts: state vector
419 * current 64-byte block
422 static void md5_transform (unsigned long *state,unsigned char *block)
424 unsigned long a = state[0],b = state[1],c = state[2],d = state[3],x[16];
425 md5_decode (x,block,16); /* decode into 16 longs */
426 /* round 1 */
427 RND1 (a,b,c,d,x[ 0], 7,0xd76aa478); RND1 (d,a,b,c,x[ 1],12,0xe8c7b756);
428 RND1 (c,d,a,b,x[ 2],17,0x242070db); RND1 (b,c,d,a,x[ 3],22,0xc1bdceee);
429 RND1 (a,b,c,d,x[ 4], 7,0xf57c0faf); RND1 (d,a,b,c,x[ 5],12,0x4787c62a);
430 RND1 (c,d,a,b,x[ 6],17,0xa8304613); RND1 (b,c,d,a,x[ 7],22,0xfd469501);
431 RND1 (a,b,c,d,x[ 8], 7,0x698098d8); RND1 (d,a,b,c,x[ 9],12,0x8b44f7af);
432 RND1 (c,d,a,b,x[10],17,0xffff5bb1); RND1 (b,c,d,a,x[11],22,0x895cd7be);
433 RND1 (a,b,c,d,x[12], 7,0x6b901122); RND1 (d,a,b,c,x[13],12,0xfd987193);
434 RND1 (c,d,a,b,x[14],17,0xa679438e); RND1 (b,c,d,a,x[15],22,0x49b40821);
435 /* round 2 */
436 RND2 (a,b,c,d,x[ 1], 5,0xf61e2562); RND2 (d,a,b,c,x[ 6], 9,0xc040b340);
437 RND2 (c,d,a,b,x[11],14,0x265e5a51); RND2 (b,c,d,a,x[ 0],20,0xe9b6c7aa);
438 RND2 (a,b,c,d,x[ 5], 5,0xd62f105d); RND2 (d,a,b,c,x[10], 9, 0x2441453);
439 RND2 (c,d,a,b,x[15],14,0xd8a1e681); RND2 (b,c,d,a,x[ 4],20,0xe7d3fbc8);
440 RND2 (a,b,c,d,x[ 9], 5,0x21e1cde6); RND2 (d,a,b,c,x[14], 9,0xc33707d6);
441 RND2 (c,d,a,b,x[ 3],14,0xf4d50d87); RND2 (b,c,d,a,x[ 8],20,0x455a14ed);
442 RND2 (a,b,c,d,x[13], 5,0xa9e3e905); RND2 (d,a,b,c,x[ 2], 9,0xfcefa3f8);
443 RND2 (c,d,a,b,x[ 7],14,0x676f02d9); RND2 (b,c,d,a,x[12],20,0x8d2a4c8a);
444 /* round 3 */
445 RND3 (a,b,c,d,x[ 5], 4,0xfffa3942); RND3 (d,a,b,c,x[ 8],11,0x8771f681);
446 RND3 (c,d,a,b,x[11],16,0x6d9d6122); RND3 (b,c,d,a,x[14],23,0xfde5380c);
447 RND3 (a,b,c,d,x[ 1], 4,0xa4beea44); RND3 (d,a,b,c,x[ 4],11,0x4bdecfa9);
448 RND3 (c,d,a,b,x[ 7],16,0xf6bb4b60); RND3 (b,c,d,a,x[10],23,0xbebfbc70);
449 RND3 (a,b,c,d,x[13], 4,0x289b7ec6); RND3 (d,a,b,c,x[ 0],11,0xeaa127fa);
450 RND3 (c,d,a,b,x[ 3],16,0xd4ef3085); RND3 (b,c,d,a,x[ 6],23, 0x4881d05);
451 RND3 (a,b,c,d,x[ 9], 4,0xd9d4d039); RND3 (d,a,b,c,x[12],11,0xe6db99e5);
452 RND3 (c,d,a,b,x[15],16,0x1fa27cf8); RND3 (b,c,d,a,x[ 2],23,0xc4ac5665);
453 /* round 4 */
454 RND4 (a,b,c,d,x[ 0], 6,0xf4292244); RND4 (d,a,b,c,x[ 7],10,0x432aff97);
455 RND4 (c,d,a,b,x[14],15,0xab9423a7); RND4 (b,c,d,a,x[ 5],21,0xfc93a039);
456 RND4 (a,b,c,d,x[12], 6,0x655b59c3); RND4 (d,a,b,c,x[ 3],10,0x8f0ccc92);
457 RND4 (c,d,a,b,x[10],15,0xffeff47d); RND4 (b,c,d,a,x[ 1],21,0x85845dd1);
458 RND4 (a,b,c,d,x[ 8], 6,0x6fa87e4f); RND4 (d,a,b,c,x[15],10,0xfe2ce6e0);
459 RND4 (c,d,a,b,x[ 6],15,0xa3014314); RND4 (b,c,d,a,x[13],21,0x4e0811a1);
460 RND4 (a,b,c,d,x[ 4], 6,0xf7537e82); RND4 (d,a,b,c,x[11],10,0xbd3af235);
461 RND4 (c,d,a,b,x[ 2],15,0x2ad7d2bb); RND4 (b,c,d,a,x[ 9],21,0xeb86d391);
462 /* update state */
463 state[0] += a; state[1] += b; state[2] += c; state[3] += d;
464 memset (x,0,sizeof (x)); /* erase sensitive data */
467 /* You may wonder why these strange "& 0xff" maskings are here. This is to
468 * ensure correct results on machines with a char size of larger than 8 bits.
469 * For example, the KCC compiler on the PDP-10 uses 9-bit chars.
472 /* MD5 encode unsigned long into LSB-first bytes
473 * Accepts: destination pointer
474 * source
475 * length of source
478 static void md5_encode (unsigned char *dst,unsigned long *src,int len)
480 int i;
481 for (i = 0; i < len; i++) {
482 *dst++ = (unsigned char) (src[i] & 0xff);
483 *dst++ = (unsigned char) ((src[i] >> 8) & 0xff);
484 *dst++ = (unsigned char) ((src[i] >> 16) & 0xff);
485 *dst++ = (unsigned char) ((src[i] >> 24) & 0xff);
490 /* MD5 decode LSB-first bytes into unsigned long
491 * Accepts: destination pointer
492 * source
493 * length of destination
496 static void md5_decode (unsigned long *dst,unsigned char *src,int len)
498 int i, j;
499 for (i = 0, j = 0; i < len; i++, j += 4)
500 dst[i] = ((unsigned long) (src[j] & 0xff)) |
501 (((unsigned long) (src[j+1] & 0xff)) << 8) |
502 (((unsigned long) (src[j+2] & 0xff)) << 16) |
503 (((unsigned long) (src[j+3] & 0xff)) << 24);