import from HEAD
[freeradius.git] / src / modules / rlm_pap / rlm_pap.c
1 /*
2  * rlm_pap.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 2001  The FreeRADIUS server project
21  * Copyright 2001  Kostas Kalevras <kkalev@noc.ntua.gr>
22  */
23
24 #include "autoconf.h"
25 #include "libradius.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include "radiusd.h"
33 #include "modules.h"
34 #include "conffile.h"
35 #include "../../include/md5.h"
36 #include "../../include/sha1.h"
37
38 #define PAP_ENC_INVALID -1
39 #define PAP_ENC_CLEAR           0
40 #define PAP_ENC_CRYPT           1
41 #define PAP_ENC_MD5             2
42 #define PAP_ENC_SHA1            3
43 #define PAP_ENC_NT              4
44 #define PAP_MAX_ENC             4
45
46 #define PAP_INST_FREE(inst) \
47         free((char *)inst->scheme); \
48         free(inst)
49
50 static const char rcsid[] = "$Id$";
51
52 /*
53  *      Define a structure for our module configuration.
54  *
55  *      These variables do not need to be in a structure, but it's
56  *      a lot cleaner to do so, and a pointer to the structure can
57  *      be used as the instance handle.
58  */
59 typedef struct rlm_pap_t {
60         char *scheme;  /* password encryption scheme */
61         int sch;
62 } rlm_pap_t;
63
64 /*
65  *      A mapping of configuration file names to internal variables.
66  *
67  *      Note that the string is dynamically allocated, so it MUST
68  *      be freed.  When the configuration file parse re-reads the string,
69  *      it free's the old one, and strdup's the new one, placing the pointer
70  *      to the strdup'd string into 'config.string'.  This gets around
71  *      buffer over-flows.
72  */
73 static CONF_PARSER module_config[] = {
74   { "encryption_scheme", PW_TYPE_STRING_PTR, offsetof(rlm_pap_t,scheme), NULL, "crypt" },
75   { NULL, -1, 0, NULL, NULL }
76 };
77
78 static const char *pap_hextab = "0123456789abcdef";
79
80 /*
81  *  Smaller & faster than snprintf("%x");
82  *  Completely stolen from ns_mta_md5 module
83  */
84 static void pap_hexify(char *buffer, char *str, int len)
85 {
86         char *pch = str;
87         char ch;
88         int i;
89
90         for(i = 0;i < len; i ++) {
91                 ch = pch[i];
92                 buffer[2*i] = pap_hextab[(ch>>4) & 15];
93                 buffer[2*i + 1] = pap_hextab[ch & 15];
94         }
95         return;
96 }
97
98 static int pap_instantiate(CONF_SECTION *conf, void **instance)
99 {
100         rlm_pap_t *inst;
101
102         /*
103          *      Set up a storage area for instance data
104          */
105         inst = rad_malloc(sizeof(*inst));
106         if (!inst) {
107                 return -1;
108         }
109         memset(inst, 0, sizeof(*inst));
110
111         /*
112          *      If the configuration parameters can't be parsed, then
113          *      fail.
114          */
115         if (cf_section_parse(conf, inst, module_config) < 0) {
116                 free(inst);
117                 return -1;
118         }
119         inst->sch = PAP_ENC_INVALID;
120         if (inst->scheme == NULL || strlen(inst->scheme) == 0){
121                 radlog(L_ERR, "rlm_pap: Wrong password scheme passed");
122                 PAP_INST_FREE(inst);
123                 return -1;
124         }
125         if (strcasecmp(inst->scheme,"clear") == 0)
126                 inst->sch = PAP_ENC_CLEAR;
127         else if (strcasecmp(inst->scheme,"crypt") == 0){
128                 inst->sch = PAP_ENC_CRYPT;
129         }
130         else if (strcasecmp(inst->scheme,"md5") == 0)
131                 inst->sch = PAP_ENC_MD5;
132         else if (strcasecmp(inst->scheme,"sha1") == 0)
133                 inst->sch = PAP_ENC_SHA1;
134         else if (strcasecmp(inst->scheme,"nt") == 0)
135                 inst->sch = PAP_ENC_NT;
136         else{
137                 radlog(L_ERR, "rlm_pap: Wrong password scheme passed");
138                 PAP_INST_FREE(inst);
139                 return -1;
140         }
141
142         *instance = inst;
143
144         return 0;
145 }
146
147 /*
148  *      Find the named user in this modules database.  Create the set
149  *      of attribute-value pairs to check and reply with for this user
150  *      from the database. The authentication code only needs to check
151  *      the password, the rest is done here.
152  */
153 static int pap_authenticate(void *instance, REQUEST *request)
154 {
155         VALUE_PAIR *passwd_item;
156         VALUE_PAIR *module_fmsg_vp;
157         char module_fmsg[MAX_STRING_LEN];
158         MD5_CTX md5_context;
159         SHA1_CTX sha1_context;
160         unsigned char digest[20];
161         char buff[MAX_STRING_LEN];
162         rlm_pap_t *inst = (rlm_pap_t *) instance;
163
164         /* quiet the compiler */
165         instance = instance;
166         request = request;
167
168         if(!request->username){
169                 radlog(L_AUTH, "rlm_pap: Attribute \"User-Name\" is required for authentication.\n");
170                 return RLM_MODULE_INVALID;
171         }
172
173         if (!request->password){
174                 radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication.");
175                 return RLM_MODULE_INVALID;
176         }
177
178         if (request->password->attribute != PW_PASSWORD) {
179                 radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
180                 return RLM_MODULE_INVALID;
181         }
182
183         if (request->password->length == 0) {
184                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: empty password supplied");
185                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
186                 pairadd(&request->packet->vps, module_fmsg_vp);
187                 return RLM_MODULE_INVALID;
188         }
189
190         DEBUG("rlm_pap: login attempt by \"%s\" with password %s",
191                 request->username->strvalue, request->password->strvalue);
192
193         if ((((passwd_item = pairfind(request->config_items, PW_PASSWORD)) == NULL) &&
194                 ((passwd_item = pairfind(request->config_items, PW_CRYPT_PASSWORD)) == NULL)) ||
195             (passwd_item->length == 0) || (passwd_item->strvalue[0] == 0)) {
196                 DEBUG("rlm_pap: No password (or empty password) to check against for user %s",request->username->strvalue);
197                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: User password not available");
198                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
199                 pairadd(&request->packet->vps, module_fmsg_vp);
200                 return RLM_MODULE_INVALID;
201         }
202         if (passwd_item->attribute == PW_CRYPT_PASSWORD){
203                 if (inst->sch != PAP_ENC_CRYPT){
204                         radlog(L_ERR, "rlm_pap: Crypt-Password attribute but encryption scheme is not set to CRYPT");
205                         return RLM_MODULE_FAIL;
206                 }       
207         }
208
209         DEBUG("rlm_pap: Using password \"%s\" for user %s authentication.",
210               passwd_item->strvalue, request->username->strvalue);
211
212         if (inst->sch == PAP_ENC_INVALID || inst->sch > PAP_MAX_ENC){
213                 radlog(L_ERR, "rlm_pap: Wrong password scheme");
214                 return RLM_MODULE_FAIL;
215         }
216         switch(inst->sch){
217                 default:
218                         radlog(L_ERR, "rlm_pap: Wrong password scheme");
219                         return RLM_MODULE_FAIL;
220                         break;
221                 case PAP_ENC_CLEAR:
222                         DEBUG("rlm_pap: Using clear text password.");
223                         if (strcmp((char *) passwd_item->strvalue,
224                                    (char *) request->password->strvalue) != 0){
225                                 DEBUG("rlm_pap: Passwords don't match");
226                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
227                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
228                                 pairadd(&request->packet->vps, module_fmsg_vp);
229                                 return RLM_MODULE_REJECT;
230                         }
231                         break;
232                 case PAP_ENC_CRYPT:
233                         DEBUG("rlm_pap: Using CRYPT encryption.");
234                         if (lrad_crypt_check((char *) request->password->strvalue,
235                                                                  (char *) passwd_item->strvalue) != 0) {
236                                 DEBUG("rlm_pap: Passwords don't match");
237                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CRYPT password check failed");
238                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
239                                 pairadd(&request->packet->vps, module_fmsg_vp);
240                                 return RLM_MODULE_REJECT;
241                         }
242                         break;
243                 case PAP_ENC_MD5:
244                         DEBUG("rlm_pap: Using MD5 encryption.");
245
246                         if (passwd_item->length != 32) {
247                                 DEBUG("rlm_pap: Configured MD5 password has incorrect length");
248                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
249                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
250                                 pairadd(&request->packet->vps, module_fmsg_vp);
251                                 return RLM_MODULE_REJECT;
252                         }
253
254                         MD5Init(&md5_context);
255                         MD5Update(&md5_context, request->password->strvalue, request->password->length);
256                         MD5Final(digest, &md5_context);
257                         pap_hexify(buff,digest,16);
258                         buff[32] = '\0';
259                         if (strcmp((char *)passwd_item->strvalue, buff) != 0){
260                                 DEBUG("rlm_pap: Passwords don't match");
261                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: MD5 password check failed");
262                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
263                                 pairadd(&request->packet->vps, module_fmsg_vp);
264                                 return RLM_MODULE_REJECT;
265                         }
266                         break;
267                 case PAP_ENC_SHA1:
268
269                         DEBUG("rlm_pap: Using SHA1 encryption.");
270
271                         if (passwd_item->length != 40) {
272                                 DEBUG("rlm_pap: Configured SHA1 password has incorrect length");
273                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
274                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
275                                 pairadd(&request->packet->vps, module_fmsg_vp);
276                                 return RLM_MODULE_REJECT;
277                         }
278
279                         SHA1Init(&sha1_context);
280                         SHA1Update(&sha1_context, request->password->strvalue, request->password->length);
281                         SHA1Final(digest,&sha1_context);
282                         pap_hexify(buff,digest,20);
283                         buff[40] = '\0';
284                         if (strcmp((char *)passwd_item->strvalue, buff) != 0){
285                                 DEBUG("rlm_pap: Passwords don't match");
286                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SHA1 password check failed");
287                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
288                                 pairadd(&request->packet->vps, module_fmsg_vp);
289                                 return RLM_MODULE_REJECT;
290                         }
291                         break;
292                 case PAP_ENC_NT:
293                         DEBUG("rlm_pap: Using NT HASH encryption.");
294
295                         if (passwd_item->length != 32) {
296                                 DEBUG("rlm_pap: Configured NT password has incorrect length");
297                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT password has incorrect length");
298                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
299                                 pairadd(&request->packet->vps, module_fmsg_vp);
300                                 return RLM_MODULE_REJECT;
301                         } else {
302                                 char szUnicodePass[513];
303                                 int nPasswordLen;
304                                 int i;
305                                 
306                                 /*
307                                  *      NT passwords are unicode.  Convert plain text password
308                                  *      to unicode by inserting a zero every other byte
309                                  */
310                                 nPasswordLen = strlen(request->password->strvalue);
311                                 for (i = 0; i < nPasswordLen; i++) {
312                                         szUnicodePass[i << 1] = request->password->strvalue[i];
313                                         szUnicodePass[(i << 1) + 1] = 0;
314                                 }
315                                 
316                                 /* Encrypt Unicode password to a 16-byte MD4 hash */
317                                 md4_calc(digest, szUnicodePass, (nPasswordLen<<1) );
318                                 
319                                 pap_hexify(buff,digest,16);
320                                 buff[32] = '\0';
321                         }
322                         if (strcmp((char *)passwd_item->strvalue, buff) != 0){
323                                 DEBUG("rlm_pap: Passwords don't match");
324                                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NT HASH password check failed");
325                                 module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
326                                 pairadd(&request->packet->vps, module_fmsg_vp);
327                                 return RLM_MODULE_REJECT;
328                         }
329                         break;
330         }
331
332         DEBUG("rlm_pap: User authenticated succesfully");
333
334         return RLM_MODULE_OK;
335 }
336
337 static int pap_detach(void *instance)
338 {
339         rlm_pap_t *inst = (rlm_pap_t *) instance;
340
341         PAP_INST_FREE(inst);
342         return 0;
343 }
344
345
346 /*
347  *      The module name should be the only globally exported symbol.
348  *      That is, everything else should be 'static'.
349  *
350  *      If the module needs to temporarily modify it's instantiation
351  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
352  *      The server will then take care of ensuring that the module
353  *      is single-threaded.
354  */
355 module_t rlm_pap = {
356         "PAP",
357         0,                              /* type */
358         NULL,                           /* initialization */
359         pap_instantiate,                /* instantiation */
360         {
361                 pap_authenticate,       /* authentication */
362                 NULL,                   /* authorization */
363                 NULL,                   /* preaccounting */
364                 NULL,                   /* accounting */
365                 NULL,                   /* checksimul */
366                 NULL,                   /* pre-proxy */
367                 NULL,                   /* post-proxy */
368                 NULL                    /* post-auth */
369         },
370         pap_detach,                     /* detach */
371         NULL,                           /* destroy */
372 };