2 * Copyright 2016-2022 Eduardo Chappa
3 * Last Modified: August 11, 2016
5 /* ========================================================================
6 * Copyright 2008-2011 Mark Crispin
7 * ========================================================================
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
32 #define MD5BLKLEN 64 /* MD5 block length */
33 #define MD5DIGLEN 16 /* MD5 digest length */
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 */
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
,
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
78 long auth_md5_valid (void)
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
91 * parsed network mailbox structure
92 * stream argument for functions
93 * pointer to current trial count
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];
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 */
117 sprintf (tmp
,"%.65s %.33s",user
,hmac_md5 (hshbuf
,challenge
,clen
,
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
);
125 ++*trial
; /* can try again if necessary */
126 ret
= LONGT
; /* check the authentication */
129 memset((void *) tmp
, 0, sizeof(tmp
));
133 memset((void *) pwd
, 0, strlen(pwd
));
134 fs_give((void **) &pwd
);
136 if (!ret
) *trial
= 65535; /* don't retry if bad protocol */
140 /* Server authenticator
141 * Accepts: responder function
144 * Returns: authenticated user name or NIL
146 * This is much hairier than it needs to be due to the necessary of zapping
150 static int md5try
= MAXLOGINTRIALS
;
152 char *auth_md5_server (authresponse_t responder
,int argc
,char *argv
[])
155 char *p
,*u
,*user
,*authuser
,*hash
,chal
[MAILTMPLEN
],hshbuf
[2*MD5DIGLEN
+ 1];
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';
168 if ((p
= auth_md5_pwd ((authuser
&& *authuser
) ? authuser
: user
)) != NULL
) {
170 u
= (md5try
&& !strcmp (hash
,hmac_md5 (hshbuf
,chal
,cl
,p
,pl
))) ?
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 */
185 /* Return MD5 password for user
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
)
196 int fd
= open (MD5ENABLE
,O_RDONLY
,NIL
);
197 unsigned char *s
,*t
,*buf
,*lusr
,*lret
;
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 */
234 * Returns: authenticated user name or NIL
237 char *apop_login (char *chal
,char *user
,char *md5
,int argc
,char *argv
[])
241 char *s
,*authuser
,tmp
[MAILTMPLEN
];
242 unsigned char digest
[MD5DIGLEN
];
244 char *hex
= "0123456789abcdef";
245 /* see if authentication user */
246 if ((authuser
= strchr (user
,'*')) != NULL
) *authuser
++ = '\0';
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];
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 */
275 * RFC 2104 HMAC hashing
276 * Accepts: destination buffer of size 2*MD5DIGLEN + 1
281 * Returns: hash as text, always
284 char *hmac_md5 (char *hshbuf
, char *text
,unsigned long tl
,char *key
,
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
;
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
++) {
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];
320 *s
= '\0'; /* tie off hash text */
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; \
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; \
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; \
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; \
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
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 */
388 * Accepts: destination digest
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 */
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);
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 */
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);
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);
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);
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);
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
478 static void md5_encode (unsigned char *dst
,unsigned long *src
,int len
)
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
493 * length of destination
496 static void md5_decode (unsigned long *dst
,unsigned char *src
,int len
)
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);