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