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