Added note about which RFC it implements, and where that RFC
[freeradius.git] / src / modules / rlm_mschap / rlm_mschap.c
1 /*
2  * rlm_mschap.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 2000  The FreeRADIUS server project
21  */
22
23
24 /*
25  *  mschap.c    MS-CHAP module
26  *
27  *  Jay Miller  jaymiller@socket.net
28  *
29  *  This implements MS-CHAP, as described in RFC 2548
30  *
31  *  http://www.freeradius.org/rfc/rfc2548.txt
32  *
33  */
34
35 #include        "autoconf.h"
36 #include        "libradius.h"
37
38 #include        <stdio.h>
39 #include        <stdlib.h>
40 #include        <string.h>
41
42 #include        "radiusd.h"
43 #include        "modules.h"
44
45 #include        "des.h"
46 #include        "md4.h"
47
48 #define PW_MSCHAP_RESPONSE  ((311 << 16) | 1)
49 #define PW_MSCHAP_CHALLENGE ((311 << 16) | 11)
50
51 static void parity_key(char * szOut, const char * szIn);
52 static void des_encrypt(const char *szClear, const char *szKey, char *szOut);
53 static void mschap(const char *szChallenge, const char *szPassword, char *szResponse);
54
55 /* 
56  *      parity_key takes a 7-byte string in szIn and returns an
57  *      8-byte string in szOut.  It inserts a 1 into every 8th bit.
58  *      DES just strips these back out.
59  */
60 static void parity_key(char * szOut, const char * szIn) {
61         int i;
62         unsigned char cNext = 0;
63         unsigned char cWorking = 0;
64         
65         for (i = 0; i < 7; i++) {
66                 /* Shift operator works in place.  Copy the char out */
67                 cWorking = szIn[i];
68                 szOut[i] = (cWorking >> i) | cNext | 1;
69                 cWorking = szIn[i];
70                 cNext = (cWorking << (7 - i));
71         }
72         szOut[i] = cNext | 1;
73 }
74
75 /*
76  *      des_encrypt takes an 8-byte string and a 7-byte key and
77  *      returns an 8-byte DES encrypted string in szOut
78  */
79 static void des_encrypt(const char *szClear, const char *szKey, char *szOut) {
80         char szParityKey[9];
81         unsigned long ulK[16][2];
82         
83         parity_key(szParityKey, szKey); /* Insert parity bits */
84         strncpy(szOut, szClear, 8);     /* des encrypts in place */
85         deskey(ulK, (unsigned char *) szParityKey, 0);  /* generate keypair */
86         des(ulK, szOut);  /* encrypt */
87 }
88
89 /*
90  *      mschap takes an 8-byte challenge string and a plain text
91  *      password (up to 253 bytes) and returns a 24-byte response
92  *      string in szResponse
93  */
94 static void mschap(const char *szChallenge, const char *szPassword, char *szResponse) {
95         char szMD4[21];
96         char szUnicodePass[513];
97         char nPasswordLen;
98         int i;
99         
100         /* initialize hash string */
101         for (i = 0; i < 21; i++) {
102                 szMD4[i] = '\0';
103         }
104         
105         /*
106          *      Microsoft passwords are unicode.  Convert plain text password
107          *      to unicode by inserting a zero every other byte
108          */
109         nPasswordLen = strlen(szPassword);
110         for (i = 0; i < nPasswordLen; i++) {
111                 szUnicodePass[2 * i] = szPassword[i];
112                 szUnicodePass[2 * i + 1] = 0;
113         }
114         
115         /* Encrypt plain text password to a 16-byte MD4 hash */
116         md4_calc(szMD4, szUnicodePass, nPasswordLen * 2);
117         
118         /*
119          *
120          *      challenge_response takes an 8-byte challenge string and a
121          *      21-byte hash (16-byte hash padded to 21 bytes with zeros) and
122          *      returns a 24-byte response in szResponse
123          */
124         des_encrypt(szChallenge, szMD4, szResponse);
125         des_encrypt(szChallenge, szMD4 + 7, szResponse + 8);
126         des_encrypt(szChallenge, szMD4 + 14, szResponse + 16);
127 }   
128
129
130 /* validate userid/passwd */
131 static int mschap_auth(void *instance, REQUEST *request)
132 {
133         VALUE_PAIR *challenge, *response;
134         uint8_t calculated[32];
135
136         instance = instance;    /* -Wunused */
137
138         /*
139          *      We need an MS-CHAP-Challenge attribute to calculate
140          *      the response.
141          */
142         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
143         if (!challenge) {
144                 radlog(L_AUTH, "rlm_mschap: Attribute \"MS-CHAP-Challenge\" is required for authentication.");
145                 return RLM_MODULE_INVALID;
146         }
147
148         /*
149          *      We need an MS-CHAP-Challenge attribute to calculate
150          *      the response.
151          */
152         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
153         if (!response) {
154                 radlog(L_AUTH, "rlm_mschap: Attribute \"MS-CHAP-Response\" is required for authentication.");
155                 return RLM_MODULE_INVALID;
156         }
157
158         /*
159          *      We can only authenticate user requests which HAVE
160          *      a Password attribute.
161          */
162         if (!request->password) {
163                 radlog(L_AUTH, "rlm_mschap: Attribute \"Password\" is required for authentication.");
164                 return RLM_MODULE_INVALID;
165         }
166
167         /*
168          *  Ensure that we're being passed a plain-text password,
169          *  and not anything else.
170          */
171         if (request->password->attribute != PW_PASSWORD) {
172                 radlog(L_AUTH, "rlm_mschap: Attribute \"Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
173                 return RLM_MODULE_INVALID;
174         }
175         
176         /*
177          *      Calculate the MS-CHAP response
178          */
179         mschap(challenge->strvalue, request->password->strvalue, calculated);
180         if (memcmp(response->strvalue + 26, calculated, 24) == 0) {
181                 return RLM_MODULE_OK;
182         }
183         
184         return RLM_MODULE_REJECT;
185 }
186
187 module_t rlm_mschap = {
188   "MS-CHAP",
189   0,                            /* type */
190   NULL,                         /* initialize */
191   NULL,                         /* instantiation */
192   {
193           mschap_auth,          /* authenticate */
194           NULL,                 /* authorize */
195           NULL,                 /* pre-accounting */
196           NULL,                 /* accounting */
197           NULL                  /* checksimul */
198   },
199   NULL,                         /* detach */
200   NULL,                         /* destroy */
201 };