import from HEAD:
[freeradius.git] / src / modules / rlm_digest / rlm_digest.c
1 /*
2  * rlm_chap.c
3  *
4  * Version:  $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2002  The FreeRADIUS server project
21  * Copyright 2002  Alan DeKok <aland@ox.org>
22  */
23
24 #include "autoconf.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "radiusd.h"
31 #include "modules.h"
32 #include "conffile.h"
33
34 static const char rcsid[] = "$Id$";
35
36 static int digest_authorize(void *instance, REQUEST *request)
37 {
38         VALUE_PAIR *vp;
39
40         /* quiet the compiler */
41         instance = instance;
42
43         /*
44          *      We need both of these attributes to do the authentication.
45          */
46         vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
47         if (vp == NULL) {
48                 return RLM_MODULE_NOOP;
49         }
50
51         /*
52          *      Check the sanity of the attribute.
53          */
54         if (vp->length != 32) {
55                 DEBUG("ERROR: Received invalid Digest-Response attribute (length %d should be 32)", vp->length);
56                 return RLM_MODULE_INVALID;
57         }
58
59         /*
60          *      We need these, too.
61          */
62         vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
63         if (vp == NULL) {
64                 DEBUG("ERROR: Received Digest-Response without Digest-Attributes");
65                 return RLM_MODULE_INVALID;
66         }
67
68         /*
69          *      Everything's OK, add a digest authentication type.
70          */
71         if (pairfind(request->config_items, PW_AUTHTYPE) == NULL) {
72                 DEBUG("rlm_digest: Adding Auth-Type = DIGEST");
73                 pairadd(&request->config_items,
74                         pairmake("Auth-Type", "DIGEST", T_OP_EQ));
75         }
76
77         return RLM_MODULE_OK;
78 }
79
80 /*
81  *      Perform all of the wondrous variants of digest authentication.
82  */
83 static int digest_authenticate(void *instance, REQUEST *request)
84 {
85         int i;
86         size_t a1_len, a2_len, kd_len;
87         uint8_t a1[(MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
88         uint8_t a2[(MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
89         uint8_t kd[(MAX_STRING_LEN + 1) * 5];
90         uint8_t hash[16];       /* MD5 output */
91         VALUE_PAIR *vp, *passwd, *algo;
92         VALUE_PAIR *qop, *nonce;
93
94         instance = instance;    /* -Wunused */
95
96         /*
97          *      We require access to the plain-text password.
98          */
99         passwd = pairfind(request->config_items, PW_PASSWORD);
100 #ifdef PW_MD5_PASSWORD
101         if (!passwd) passwd = pairfind(request->config_items, PW_MD5_PASSWORD);
102 #endif
103         if (!passwd) {
104                 radlog(L_AUTH, "rlm_digest: Configuration item \"User-Password\" or MD5-Password is required for authentication.");
105                 return RLM_MODULE_INVALID;
106         }
107
108         /*
109          *      We need these, too.
110          */
111         vp = pairfind(request->packet->vps, PW_DIGEST_ATTRIBUTES);
112         if (vp == NULL) {
113                 DEBUG("ERROR: You set 'Auth-Type = Digest' for a request that did not contain any digest attributes!");
114                 return RLM_MODULE_INVALID;
115         }
116
117         /*
118          *      Loop through the Digest-Attributes, sanity checking them.
119          */
120         DEBUG("    rlm_digest: Converting Digest-Attributes to something sane...");
121         while (vp) {
122                 int length = vp->length;
123                 int attrlen;
124                 uint8_t *p = &vp->strvalue[0];
125                 VALUE_PAIR *sub;
126
127                 /*
128                  *      Until this stupidly encoded attribure is exhausted.
129                  */
130                 while (length > 0) {
131                         /*
132                          *      The attribute type must be valid
133                          */
134                         if ((p[0] == 0) || (p[0] > 10)) {
135                                 DEBUG("ERROR: Received Digest-Attributes with invalid sub-attribute %d", p[0]);
136                                 return RLM_MODULE_INVALID;
137                         }
138
139                         attrlen = p[1]; /* stupid VSA format */
140
141                         /*
142                          *      Too short.
143                          */
144                         if (attrlen < 3) {
145                                 DEBUG("ERROR: Received Digest-Attributes with short sub-attribute %d, of length %d", p[0], attrlen);
146                                 return RLM_MODULE_INVALID;
147                         }
148
149                         /*
150                          *      Too long.
151                          */
152                         if (attrlen > length) {
153                                 DEBUG("ERROR: Received Digest-Attributes with long sub-attribute %d, of length %d", p[0], attrlen);
154                                 return RLM_MODULE_INVALID;
155                         }
156
157                         /*
158                          *      Create a new attribute, broken out of
159                          *      the stupid sub-attribute crap.
160                          *
161                          *      Didn't they know that VSA's exist?
162                          */
163                         sub = paircreate(PW_DIGEST_REALM - 1 + p[0],
164                                          PW_TYPE_STRING);
165                         if (!sub) {
166                                 return RLM_MODULE_FAIL; /* out of memory */
167                         }
168                         memcpy(&sub->strvalue[0], &p[2], attrlen - 2);
169                         sub->strvalue[attrlen - 2] = '\0';
170                         sub->length = attrlen - 2;
171
172                         if (debug_flag) {
173                           putchar('\t');
174                           vp_print(stdout, sub);
175                           putchar('\n');
176                         }
177
178                         /*
179                          *      And add it to the request pairs.
180                          */
181                         pairadd(&request->packet->vps, sub);
182
183                         /*
184                          *      FIXME: Check for the existence
185                          *      of the necessary attributes!
186                          */
187
188                         length -= attrlen;
189                         p += attrlen;
190                 } /* loop over this one attribute */
191
192                 /*
193                  *      Find the next one, if it exists.
194                  */
195                 vp = pairfind(vp->next, PW_DIGEST_ATTRIBUTES);
196         }
197
198         /*
199          *      We require access to the Digest-Nonce-Value
200          */
201         nonce = pairfind(request->packet->vps, PW_DIGEST_NONCE);
202         if (!nonce) {
203                 DEBUG("ERROR: No Digest-Nonce: Cannot perform Digest authentication");
204                 return RLM_MODULE_INVALID;
205         }
206
207         /*
208          *      A1 = Digest-User-Name ":" Realm ":" Password
209          */
210         vp = pairfind(request->packet->vps, PW_DIGEST_USER_NAME);
211         if (!vp) {
212                 DEBUG("ERROR: No Digest-User-Name: Cannot perform Digest authentication");
213                 return RLM_MODULE_INVALID;
214         }
215         memcpy(&a1[0], &vp->strvalue[0], vp->length);
216         a1_len = vp->length;
217
218         a1[a1_len] = ':';
219         a1_len++;
220
221         vp = pairfind(request->packet->vps, PW_DIGEST_REALM);
222         if (!vp) {
223                 DEBUG("ERROR: No Digest-Realm: Cannot perform Digest authentication");
224                 return RLM_MODULE_INVALID;
225         }
226         memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
227         a1_len += vp->length;
228
229         a1[a1_len] = ':';
230         a1_len++;
231
232         if (passwd->attribute == PW_USER_PASSWORD) {
233                 memcpy(&a1[a1_len], &passwd->strvalue[0], passwd->length);
234                 a1_len += passwd->length;
235                 a1[a1_len] = '\0';
236                 DEBUG2("A1 = %s", a1);
237         } else {
238                 a1[a1_len] = '\0';
239                 DEBUG2("A1 = %s (using MD5-Password)", a1);
240                 a1_len = 16;
241         }
242
243         /*
244          *      See which variant we calculate.
245          *      Assume MD5 if no Digest-Algorithm attribute received
246          */
247         algo = pairfind(request->packet->vps, PW_DIGEST_ALGORITHM);
248         if ((algo == NULL) || 
249             (strcasecmp(algo->strvalue, "MD5") == 0)) {
250                 /*
251                  *      Set A1 to MD5-Password if no User-Password found
252                  */
253                 if (passwd->attribute != PW_USER_PASSWORD) {
254                         memcpy(&a1[0], passwd->strvalue, 16);
255                 }
256
257         } else if (strcasecmp(algo->strvalue, "MD5-sess") == 0) {
258                 /*
259                  *      K1 = H(A1) : Digest-Nonce ... : H(A2)
260                  *
261                  *      If we find MD5-Password, we assume it contains
262                  *      H(A1).
263                  */
264                 if (passwd->attribute == PW_USER_PASSWORD) {
265                         librad_md5_calc(hash, &a1[0], a1_len);
266                         lrad_bin2hex(hash, &a1[0], 16);
267                 } else {
268                         lrad_bin2hex(passwd->strvalue, &a1[0], 16);
269                 }
270                 a1_len = 32;
271
272                 a1[a1_len] = ':';
273                 a1_len++;
274
275                 /*
276                  *      Tack on the Digest-Nonce. Length must be even
277                  */
278                 if ((nonce->length & 1) != 0) {
279                         DEBUG("ERROR: Received Digest-Nonce hex string with invalid length: Cannot perform Digest authentication");
280                         return RLM_MODULE_INVALID;
281                 }
282                 memcpy(&a1[a1_len], &nonce->strvalue[0], nonce->length);
283                 a1_len += nonce->length;
284
285                 a1[a1_len] = ':';
286                 a1_len++;
287
288                 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
289                 if (!vp) {
290                         DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
291                         return RLM_MODULE_INVALID;
292                 }
293
294                 /*
295                  *      Digest-CNonce length must be even
296                  */
297                 if ((vp->length & 1) != 0) {
298                         DEBUG("ERROR: Received Digest-CNonce hex string with invalid length: Cannot perform Digest authentication");
299                         return RLM_MODULE_INVALID;
300                 }
301                 memcpy(&a1[a1_len], &vp->strvalue[0], vp->length);
302                 a1_len += vp->length;
303
304         } else if ((algo != NULL) &&
305                    (strcasecmp(algo->strvalue, "MD5") != 0)) {
306                 /*
307                  *      We check for "MD5-sess" and "MD5".
308                  *      Anything else is an error.
309                  */
310                 DEBUG("ERROR: Unknown Digest-Algorithm \"%s\": Cannot perform Digest authentication", vp->strvalue);
311                 return RLM_MODULE_INVALID;
312         }
313
314         /*
315          *      A2 = Digest-Method ":" Digest-URI
316          */
317         vp = pairfind(request->packet->vps, PW_DIGEST_METHOD);
318         if (!vp) {
319                 DEBUG("ERROR: No Digest-Method: Cannot perform Digest authentication");
320                 return RLM_MODULE_INVALID;
321         }
322         memcpy(&a2[0], &vp->strvalue[0], vp->length);
323         a2_len = vp->length;
324
325         a2[a2_len] = ':';
326         a2_len++;
327
328         vp = pairfind(request->packet->vps, PW_DIGEST_URI);
329         if (!vp) {
330                 DEBUG("ERROR: No Digest-URI: Cannot perform Digest authentication");
331                 return RLM_MODULE_INVALID;
332         }
333         memcpy(&a2[a2_len], &vp->strvalue[0], vp->length);
334         a2_len += vp->length;
335
336         /*
337          *  QOP is "auth-int", tack on ": Digest-Body-Digest"
338          */
339         qop = pairfind(request->packet->vps, PW_DIGEST_QOP);
340         if ((qop != NULL) &&
341             (strcasecmp(qop->strvalue, "auth-int") == 0)) {
342                 VALUE_PAIR *body;
343
344                 /*
345                  *      Add in Digest-Body-Digest
346                  */
347                 a2[a2_len] = ':';
348                 a2_len++;
349
350                 /*
351                  *  Must be a hex representation of an MD5 digest.
352                  */
353                 body = pairfind(request->packet->vps, PW_DIGEST_BODY_DIGEST);
354                 if (!body) {
355                         DEBUG("ERROR: No Digest-Body-Digest: Cannot perform Digest authentication");
356                         return RLM_MODULE_INVALID;
357                 }
358
359                 if ((a2_len + body->length) > sizeof(a2)) {
360                         DEBUG("ERROR: Digest-Body-Digest is too long");
361                         return RLM_MODULE_INVALID;
362                 }
363
364                 memcpy(a2 + a2_len, body->strvalue, body->length);
365                 a2_len += body->length;
366
367         } else if ((qop != NULL) &&
368                    (strcasecmp(qop->strvalue, "auth") != 0)) {
369                 DEBUG("ERROR: Unknown Digest-QOP \"%s\": Cannot perform Digest authentication", qop->strvalue);
370                 return RLM_MODULE_INVALID;
371         }
372
373         a2[a2_len] = '\0';
374         DEBUG2("A2 = %s", a2);
375
376         /*
377          *     KD = H(A1) : Digest-Nonce ... : H(A2).
378          *     Compute MD5 if Digest-Algorithm == "MD5-Sess",
379          *     or if we found a User-Password.
380          */
381         if (((algo != NULL) && 
382              (strcasecmp(algo->strvalue, "MD5-Sess") == 0)) ||
383             (passwd->attribute == PW_USER_PASSWORD)) {
384           a1[a1_len] = '\0';
385                 librad_md5_calc(&hash[0], &a1[0], a1_len);
386         } else {
387                 memcpy(&hash[0], &a1[0], a1_len);
388         }
389         lrad_bin2hex(hash, kd, sizeof(hash));
390
391 #ifndef NDEBUG
392         if (debug_flag) {
393                 printf("H(A1) = ");
394                 for (i = 0; i < 16; i++) {
395                         printf("%02x", hash[i]);
396                 }
397                 printf("\n");
398         }
399 #endif
400         kd_len = 32;
401
402         kd[kd_len] = ':';
403         kd_len++;
404
405         memcpy(&kd[kd_len], nonce->strvalue, nonce->length);
406         kd_len += nonce->length;
407
408         /*
409          *      No QOP defined.  Do RFC 2069 compatibility.
410          */
411         if (!qop) {
412                 /*
413                  *      Do nothing here.
414                  */
415
416         } else {                /* Digest-QOP MUST be "auth" or "auth-int" */
417                 /*
418                  *      Tack on ":" Digest-Nonce-Count ":" Digest-CNonce
419                  *             ":" Digest-QOP
420                  */
421                 kd[kd_len] = ':';
422                 kd_len++;
423
424                 vp = pairfind(request->packet->vps, PW_DIGEST_NONCE_COUNT);
425                 if (!vp) {
426                         DEBUG("ERROR: No Digest-Nonce-Count: Cannot perform Digest authentication");
427                         return RLM_MODULE_INVALID;
428                 }
429                 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
430                 kd_len += vp->length;
431
432                 kd[kd_len] = ':';
433                 kd_len++;
434
435                 vp = pairfind(request->packet->vps, PW_DIGEST_CNONCE);
436                 if (!vp) {
437                         DEBUG("ERROR: No Digest-CNonce: Cannot perform Digest authentication");
438                         return RLM_MODULE_INVALID;
439                 }
440                 memcpy(&kd[kd_len], &vp->strvalue[0], vp->length);
441                 kd_len += vp->length;
442
443                 kd[kd_len] = ':';
444                 kd_len++;
445
446                 memcpy(&kd[kd_len], &qop->strvalue[0], qop->length);
447                 kd_len += qop->length;
448         }
449
450         /*
451          *      Tack on ":" H(A2)
452          */
453         kd[kd_len] = ':';
454         kd_len++;
455
456         librad_md5_calc(&hash[0], &a2[0], a2_len);
457
458         lrad_bin2hex(hash, kd + kd_len, sizeof(hash));
459
460 #ifndef NDEBUG
461         if (debug_flag) {
462                 printf("H(A2) = ");
463                 for (i = 0; i < 16; i++) {
464                         printf("%02x", hash[i]);
465                 }
466                 printf("\n");
467         }
468 #endif
469         kd_len += 32;
470
471         kd[kd_len] = 0;
472
473         DEBUG2("KD = %s\n", &kd[0]);
474
475         /*
476          *      Take the hash of KD.
477          */
478         librad_md5_calc(&hash[0], &kd[0], kd_len);
479         memcpy(&kd[0], &hash[0], 16);
480
481         /*
482          *      Get the binary value of Digest-Response
483          */
484         vp = pairfind(request->packet->vps, PW_DIGEST_RESPONSE);
485         if (!vp) {
486                 DEBUG("ERROR: No Digest-Response attribute in the request.  Cannot perform digest authentication");
487                 return RLM_MODULE_INVALID;
488         }
489
490         lrad_hex2bin(&vp->strvalue[0], &hash[0], vp->length >> 1);
491
492 #ifndef NDEBUG
493         if (debug_flag) {
494                 printf("EXPECTED ");
495                 for (i = 0; i < 16; i++) {
496                         printf("%02x", kd[i]);
497                 }
498                 printf("\n");
499
500                 printf("RECEIVED ");
501                 for (i = 0; i < 16; i++) {
502                         printf("%02x", hash[i]);
503                 }
504                 printf("\n");
505         }
506 #endif
507
508         /*
509          *  And finally, compare the digest in the packet with KD.
510          */
511         if (memcmp(&kd[0], &hash[0], 16) == 0) {
512                 return RLM_MODULE_OK;
513         }
514
515         DEBUG("rlm_digest: FAILED authentication");
516         return RLM_MODULE_REJECT;
517 }
518
519 /*
520  *      The module name should be the only globally exported symbol.
521  *      That is, everything else should be 'static'.
522  *
523  *      If the module needs to temporarily modify it's instantiation
524  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
525  *      The server will then take care of ensuring that the module
526  *      is single-threaded.
527  */
528 module_t rlm_digest = {
529         "DIGEST",
530         0,                              /* type */
531         NULL,                           /* initialization */
532         NULL,                           /* instantiation */
533         {
534                 digest_authenticate,    /* authentication */
535                 digest_authorize,       /* authorization */
536                 NULL,                   /* preaccounting */
537                 NULL,                   /* accounting */
538                 NULL,                   /* checksimul */
539                 NULL,                   /* pre-proxy */
540                 NULL,                   /* post-proxy */
541                 NULL                    /* post-auth */
542         },
543         NULL,                           /* detach */
544         NULL,                           /* destroy */
545 };