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