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