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