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