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