import from HEAD:
[freeradius.git] / src / modules / rlm_eap / types / rlm_eap_leap / eap_leap.c
1 /*
2  * eap_leap.c  EAP LEAP functionality.
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 2003 Alan DeKok <aland@freeradius.org>
21  */
22
23 /*
24  *
25  *  LEAP Packet Format in EAP Type-Data
26  *  --- ------ ------ -- --- ---------
27  *    0                   1                   2                 3
28  *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
29  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30  *  |     Type 0x11 |  Version 0x01 | Unused 0x00   | Count 0x08    |
31  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32  *  |               Peer Challenge                                 |
33  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34  *  |               Peer Challenge                                 |
35  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36  *  |   User Name .....
37  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-
38  *
39  *  Count is 8 octets since the Peer challenge is 8 bytes.
40  *  Count is 24 for EAP response, with MSCHAP response.
41  *  Length is the total number of octets in the EAP-Message.
42  *
43  *  The LEAP type (0x11) is *not* included in the type data...
44  */
45
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include "eap.h"
49 #include "eap_leap.h"
50
51 /*
52  *      Allocate a new LEAP_PACKET
53  */
54 LEAP_PACKET *eapleap_alloc(void)
55 {
56         LEAP_PACKET   *rp;
57
58         if ((rp = malloc(sizeof(LEAP_PACKET))) == NULL) {
59                 radlog(L_ERR, "rlm_eap_leap: out of memory");
60                 return NULL;
61         }
62         memset(rp, 0, sizeof(LEAP_PACKET));
63         return rp;
64 }
65
66 /*
67  *      Free LEAP_PACKET
68  */
69 void eapleap_free(LEAP_PACKET **leap_packet_ptr)
70 {
71         LEAP_PACKET *leap_packet;
72
73         if (!leap_packet_ptr) return;
74         leap_packet = *leap_packet_ptr;
75         if (leap_packet == NULL) return;
76
77         if (leap_packet->challenge) free(leap_packet->challenge);
78         if (leap_packet->name) free(leap_packet->name);
79
80         free(leap_packet);
81
82         *leap_packet_ptr = NULL;
83 }
84
85 /*
86  *   Extract the data from the LEAP packet.
87  */
88 LEAP_PACKET *eapleap_extract(EAP_DS *eap_ds)
89 {
90         leap_packet_t   *data;
91         LEAP_PACKET     *packet;
92         int             name_len;
93
94         /*
95          *      LEAP can have EAP-Response or EAP-Request (step 5)
96          *      messages sent to it.
97          */
98         if (!eap_ds ||
99             !eap_ds->response ||
100             ((eap_ds->response->code != PW_EAP_RESPONSE) &&
101              (eap_ds->response->code != PW_EAP_REQUEST)) ||
102             eap_ds->response->type.type != PW_EAP_LEAP ||
103             !eap_ds->response->type.data ||
104             (eap_ds->response->length < LEAP_HEADER_LEN) ||
105             (eap_ds->response->type.data[0] != 0x01)) { /* version 1 */
106                 radlog(L_ERR, "rlm_eap_leap: corrupted data");
107                 return NULL;
108         }
109
110         /*
111          *      Hmm... this cast isn't the best thing to do.
112          */
113         data = (leap_packet_t *)eap_ds->response->type.data;
114
115         /*
116          *      Some simple sanity checks on the incoming packet.
117          *
118          *      See 'leap.txt' in this directory for a description
119          *      of the stages.
120          */
121         switch (eap_ds->response->code) {
122         case PW_EAP_RESPONSE:
123                 if (data->count != 24) {
124                         radlog(L_ERR, "rlm_eap_leap: Bad NTChallengeResponse in LEAP stage 3");
125                         return NULL;
126                 }
127                 break;
128
129         case PW_EAP_REQUEST:
130                 if (data->count != 8) {
131                         radlog(L_ERR, "rlm_eap_leap: Bad AP Challenge in LEAP stage 5");
132                         return NULL;
133                 }
134                 break;
135
136         default:
137                 radlog(L_ERR, "rlm_eap_leap: Invalid EAP code %d",
138                        eap_ds->response->code);
139                 return NULL;
140                 break;
141         }
142
143         packet = eapleap_alloc();
144         if (!packet) return NULL;
145
146         /*
147          *      Remember code, length, and id.
148          */
149         packet->code = eap_ds->response->code;
150         packet->id = eap_ds->response->id;
151
152         /*
153          *      The size of the LEAP portion of the packet, not
154          *      counting the EAP header and the type.
155          */
156         packet->length = eap_ds->response->length - EAP_HEADER_LEN - 1;
157
158         /*
159          *      Remember the length of the challenge.
160          */
161         packet->count = data->count;
162
163         packet->challenge = malloc(packet->count);
164         if (packet->challenge == NULL) {
165                 radlog(L_ERR, "rlm_eap_leap: out of memory");
166                 eapleap_free(&packet);
167                 return NULL;
168         }
169         memcpy(packet->challenge, data->challenge, packet->count);
170
171         /*
172          *      The User-Name comes after the challenge.
173          *
174          *      Length of the EAP-LEAP portion of the packet, minus
175          *      3 octets for data, minus the challenge size, is the
176          *      length of the user name.
177          */
178         name_len = packet->length - 3 - packet->count;
179         if (name_len > 0) {
180                 packet->name = malloc(name_len + 1);
181                 if (!packet->name) {
182                         radlog(L_ERR, "rlm_eap_leap: out of memory");
183                         eapleap_free(&packet);
184                         return NULL;
185                 }
186                 memcpy(packet->name, &data->challenge[packet->count],
187                        name_len);
188                 packet->name[name_len] = '\0';
189                 packet->name_len = name_len;
190         }
191
192         return packet;
193 }
194
195 /*
196  *  Get the NT-Password hash.
197  */
198 static int eapleap_ntpwdhash(unsigned char *ntpwdhash, VALUE_PAIR *password)
199 {
200         if (password->attribute == PW_PASSWORD) {
201                 int i;
202                 unsigned char unicode[512];
203
204                 /*
205                  *      Convert the password to NT's weird Unicode format.
206                  */
207                 memset(unicode, 0, sizeof(unicode));
208                 for (i = 0; i < password->length; i++) {
209                         /*
210                          *  Yes, the *even* bytes have the values,
211                          *  and the *odd* bytes are zero.
212                          */
213                         unicode[(i << 1)] = password->strvalue[i];
214                 }
215
216                 /*
217                  *  Get the NT Password hash.
218                  */
219                 md4_calc(ntpwdhash, unicode, password->length * 2);
220
221         } else {                /* MUST be NT-Password */
222                 if (password->length == 32) {
223                         password->length = lrad_hex2bin(password->strvalue,
224                                                         password->strvalue,
225                                                         16);
226                 }
227                 if (password->length != 16) {
228                         radlog(L_ERR, "rlm_eap_leap: Bad NT-Password");
229                         return 0;
230                 }
231
232                 memcpy(ntpwdhash, password->strvalue, 16);
233         }
234         return 1;
235 }
236
237
238 /*
239  *      Verify the MS-CHAP response from the user.
240  */
241 int eapleap_stage4(LEAP_PACKET *packet, VALUE_PAIR* password,
242                    leap_session_t *session)
243 {
244         unsigned char ntpwdhash[16];
245         unsigned char response[24];
246
247
248         /*
249          *      No password or previous packet.  Die.
250          */
251         if ((password == NULL) || (session == NULL)) {
252                 return 0;
253         }
254
255         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
256                 return 0;
257         }
258
259         /*
260          *      Calculate and verify the CHAP challenge.
261          */
262         eapleap_mschap(ntpwdhash, session->peer_challenge, response);
263         if (memcmp(response, packet->challenge, 24) == 0) {
264                 DEBUG2("  rlm_eap_leap: NtChallengeResponse from AP is valid");
265                 memcpy(session->peer_response, response, sizeof(response));
266                 return 1;
267         }
268
269         DEBUG2("  rlm_eap_leap: FAILED incorrect NtChallengeResponse from AP");
270         return 0;
271 }
272
273 /*
274  *      Verify ourselves to the AP
275  */
276 LEAP_PACKET *eapleap_stage6(LEAP_PACKET *packet, REQUEST *request,
277                             VALUE_PAIR *user_name, VALUE_PAIR* password,
278                             leap_session_t *session, VALUE_PAIR **reply_vps)
279 {
280         int i;
281         unsigned char ntpwdhash[16], ntpwdhashhash[16];
282         unsigned char buffer[256];
283         LEAP_PACKET *reply;
284         char *p;
285         VALUE_PAIR *vp;
286
287         /*
288          *      No password or previous packet.  Die.
289          */
290         if ((password == NULL) || (session == NULL)) {
291                 return NULL;
292         }
293
294         reply = eapleap_alloc();
295         if (!reply) return NULL;
296
297         reply->code = PW_EAP_RESPONSE;
298         reply->length = LEAP_HEADER_LEN + 24 + user_name->length;
299         reply->count = 24;
300
301         reply->challenge = malloc(reply->count);
302         if (reply->challenge == NULL) {
303                 radlog(L_ERR, "rlm_eap_leap: out of memory");
304                 eapleap_free(&reply);
305                 return NULL;
306         }
307
308         /*
309          *      The LEAP packet also contains the user name.
310          */
311         reply->name = malloc(user_name->length + 1);
312         if (reply->name == NULL) {
313                 radlog(L_ERR, "rlm_eap_leap: out of memory");
314                 eapleap_free(&reply);
315                 return NULL;
316         }
317
318         /*
319          *      Copy the name over, and ensure it's NUL terminated.
320          */
321         memcpy(reply->name, user_name->strvalue, user_name->length);
322         reply->name[user_name->length] = '\0';
323         reply->name_len = user_name->length;
324
325         /*
326          *  MPPE hash = ntpwdhash(ntpwdhash(unicode(pw)))
327          */
328         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
329                 eapleap_free(&reply);
330                 return NULL;
331         }
332         md4_calc(ntpwdhashhash, ntpwdhash, 16);
333
334         /*
335          *      Calculate our response, to authenticate ourselves
336          *      to the AP.
337          */
338         eapleap_mschap(ntpwdhashhash, packet->challenge, reply->challenge);
339
340         /*
341          *  Calculate the leap:session-key attribute
342          */
343         vp = pairmake("Cisco-AVPair", "leap:session-key=", T_OP_ADD);
344         if (!vp) {
345                 radlog(L_ERR, "rlm_eap_leap: Failed to create Cisco-AVPair attribute.  LEAP cancelled.");
346                 eapleap_free(&reply);
347                 return NULL;
348         }
349
350         /*
351          *      And calculate the MPPE session key.
352          */
353         p = buffer;
354         memcpy(p, ntpwdhashhash, 16); /* MPPEHASH */
355         p += 16;
356         memcpy(p, packet->challenge, 8); /* APC */
357         p += 8;
358         memcpy(p, reply->challenge, 24); /* APR */
359         p += 24;
360         memcpy(p, session->peer_challenge, 8); /* PC */
361         p += 8;
362         memcpy(p, session->peer_response, 24); /* PR */
363         p += 24;
364
365         /*
366          *      These 16 bytes are the session key to use.
367          */
368         librad_md5_calc(ntpwdhash, buffer, 16 + 8 + 24 + 8 + 24);
369
370         memcpy(vp->strvalue + vp->length, ntpwdhash, 16);
371         memset(vp->strvalue + vp->length + 16, 0,
372                sizeof(vp->strvalue) - (vp->length + 16));
373
374         i = 16;
375         rad_tunnel_pwencode(vp->strvalue + vp->length, &i,
376                             request->secret, request->packet->vector);
377         vp->length += i;
378         pairadd(reply_vps, vp);
379
380         return reply;
381 }
382
383 /*
384  *      If an EAP LEAP request needs to be initiated then
385  *      create such a packet.
386  */
387 LEAP_PACKET *eapleap_initiate(UNUSED EAP_DS *eap_ds, VALUE_PAIR *user_name)
388 {
389         int i;
390         LEAP_PACKET     *reply;
391
392         reply = eapleap_alloc();
393         if (reply == NULL)  {
394                 radlog(L_ERR, "rlm_eap_leap: out of memory");
395                 return NULL;
396         }
397
398         reply->code = PW_EAP_REQUEST;
399         reply->length = LEAP_HEADER_LEN + 8 + user_name->length;
400         reply->count = 8;       /* random challenge */
401
402         reply->challenge = malloc(reply->count);
403         if (reply->challenge == NULL) {
404                 radlog(L_ERR, "rlm_eap_leap: out of memory");
405                 eapleap_free(&reply);
406                 return NULL;
407         }
408
409         /*
410          *      Fill the challenge with random bytes.
411          */
412         for (i = 0; i < reply->count; i++) {
413                 reply->challenge[i] = lrad_rand();
414         }
415
416         DEBUG2("  rlm_eap_leap: Issuing AP Challenge");
417
418         /*
419          *      The LEAP packet also contains the user name.
420          */
421         reply->name = malloc(user_name->length + 1);
422         if (reply->name == NULL) {
423                 radlog(L_ERR, "rlm_eap_leap: out of memory");
424                 eapleap_free(&reply);
425                 return NULL;
426         }
427
428         /*
429          *      Copy the name over, and ensure it's NUL terminated.
430          */
431         memcpy(reply->name, user_name->strvalue, user_name->length);
432         reply->name[user_name->length] = '\0';
433         reply->name_len = user_name->length;
434
435         return reply;
436 }
437
438 /*
439  * compose the LEAP reply packet in the EAP reply typedata
440  */
441 int eapleap_compose(EAP_DS *eap_ds, LEAP_PACKET *reply)
442 {
443         leap_packet_t *data;
444
445         /*
446          *  We need the name and the challenge.
447          */
448         switch (reply->code) {
449         case PW_EAP_REQUEST:
450         case PW_EAP_RESPONSE:
451                 eap_ds->request->type.type = PW_EAP_LEAP;
452                 eap_ds->request->type.length = reply->length;
453
454                 eap_ds->request->type.data = malloc(reply->length);
455                 if (eap_ds->request->type.data == NULL) {
456                         radlog(L_ERR, "rlm_eap_leap: out of memory");
457                         return 0;
458                 }
459                 data = (leap_packet_t *) eap_ds->request->type.data;
460                 data->version = 0x01;
461                 data->unused = 0;
462                 data->count = reply->count;
463
464                 /*
465                  *      N bytes of the challenge, followed by the user name.
466                  */
467                 memcpy(&data->challenge[0], reply->challenge, reply->count);
468                 memcpy(&data->challenge[reply->count],
469                        reply->name, reply->name_len);
470                 break;
471
472                 /*
473                  *      EAP-Success packets don't contain any data
474                  *      other than the header.
475                  */
476         case PW_EAP_SUCCESS:
477                 eap_ds->request->type.length = 0;
478                 break;
479
480         default:
481                 radlog(L_ERR, "rlm_eap_leap: Internal sanity check failed");
482                 return 0;
483                 break;
484         }
485
486         /*
487          *      Set the EAP code.
488          */
489         eap_ds->request->code = reply->code;
490
491         return 1;
492 }