dc233c5a71b8e7fdb1368aa7e63d731a5a25c2f6
[freeradius.git] / src / modules / rlm_fastusers / rlm_fastusers.c
1 /*
2  * rlm_fastusers.c      authorization: Find a user in the hashed "users" file.
3  *              accounting:    Do nothing.  Auth module only.
4  *
5  */
6
7 #include        "autoconf.h"
8
9 #include        <sys/types.h>
10 #include        <sys/socket.h>
11 #include        <sys/time.h>
12 #include        <sys/stat.h>
13 #include        <netinet/in.h>
14
15 #include        <stdio.h>
16 #include        <stdlib.h>
17 #include        <string.h>
18 #include        <errno.h>
19 #include        <pwd.h>
20 #include        <grp.h>
21 #include        <time.h>
22 #include        <ctype.h>
23 #include        <fcntl.h>
24 #include        <unistd.h>
25 #include <limits.h>
26
27 #if HAVE_MALLOC_H
28 #  include      <malloc.h>
29 #endif
30
31 #include        "radiusd.h"
32 #include        "modules.h"
33
34 struct fastuser_instance {
35         char *compat_mode;
36
37         /* hash table */
38         long hashsize;
39         PAIR_LIST **hashtable;
40         PAIR_LIST *users;
41         PAIR_LIST *default_entry;
42
43         char *usersfile;
44 };
45
46 /* Function declarations */
47 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable);
48 static int fastuser_hash(const char *s, long hashtablesize);
49 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
50 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable, const char *user,
51                                                                                                                         long hashsize);
52
53 /*
54  *      A temporary holding area for config values to be extracted
55  *      into, before they are copied into the instance data
56  */
57 static struct fastuser_instance config;
58
59 static CONF_PARSER module_config[] = {
60         { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
61         { "hashsize",     PW_TYPE_INTEGER, &config.hashsize, "100000" },
62         { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
63         { NULL, -1, NULL, NULL }
64 };
65
66 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable)
67 {
68         int rcode;
69         PAIR_LIST *users = NULL;
70         int compat_mode = FALSE;
71         PAIR_LIST *entry, *next;
72         VALUE_PAIR *vp;
73         int hashindex = 0;
74
75         rcode = pairlist_read(filename, &users, 1);
76         if (rcode < 0) {
77                 return -1;
78         }
79
80         if (strcmp(config.compat_mode, "cistron") == 0) {
81                 compat_mode = TRUE;
82         }
83         
84         entry = users;
85         while (entry) {
86                 if (compat_mode) {
87                         DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
88                                 filename, entry->lineno, entry->name);
89                 }
90
91                 /*
92                  *      Look for improper use of '=' in the
93                  *      check items.  They should be using
94                  *      '==' for on-the-wire RADIUS attributes,
95                  *      and probably ':=' for server
96                  *      configuration items.
97                  */
98                 for (vp = entry->check; vp != NULL; vp = vp->next) {
99                         /*
100                          *      Ignore attributes which are set
101                          *      properly.
102                          */
103                         if (vp->operator != T_OP_EQ) 
104                                 continue;
105                                 
106
107                         /*
108                          *      If it's a vendor attribute,
109                          *      or it's a wire protocol, 
110                          *      ensure it has '=='.
111                          */
112                         if (((vp->attribute & ~0xffff) != 0) ||
113                                 (vp->attribute < 0x100)) {
114                                 if (!compat_mode) {
115                                         DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
116                                         filename, entry->lineno, vp->name, vp->name, entry->name);
117                                 } else {
118                                         DEBUG("\tChanging '%s =' to '%s =='",
119                                                 vp->name, vp->name);
120                                 }
121                                 vp->operator = T_OP_CMP_EQ;
122                                 continue;
123                         }
124                                 
125                         /*
126                          *      Cistron Compatibility mode.
127                          *
128                          *      Re-write selected attributes
129                          *      to be '+=', instead of '='.
130                          *
131                          *      All others get set to '=='
132                          */
133                         if (compat_mode) {
134                                 /*
135                                  *      Non-wire attributes become +=
136                                  *
137                                  *      On the write attributes
138                                  *      become ==
139                                  */
140                                 if ((vp->attribute >= 0x100) &&
141                                         (vp->attribute <= 0xffff) &&
142                                         (vp->attribute != PW_HINT) &&
143                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
144                                         DEBUG("\tChanging '%s =' to '%s +='",
145                                                 vp->name, vp->name);
146                                                 vp->operator = T_OP_ADD;
147                                 } else {
148                                         DEBUG("\tChanging '%s =' to '%s =='",
149                                                 vp->name, vp->name);
150                                         vp->operator = T_OP_CMP_EQ;
151                                 }
152                         }
153                                 
154                 } /* end of loop over check items */
155                 
156                 
157                 /*
158                  *      Look for server configuration items
159                  *      in the reply list.
160                  *
161                  *      It's a common enough mistake, that it's
162                  *      worth doing.
163                  */
164                 for (vp = entry->reply; vp != NULL; vp = vp->next) {
165                         /*
166                          *      If it's NOT a vendor attribute,
167                          *      and it's NOT a wire protocol
168                          *      and we ignore Fall-Through,
169                          *      then bitch about it, giving a
170                          *      good warning message.
171                          */
172                         if (!(vp->attribute & ~0xffff) &&
173                                 (vp->attribute > 0xff) &&
174                                 (vp->attribute > 1000)) {
175                                 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
176                                         "\tfound in reply item list for user \"%s\".\n"
177                                         "\tThis attribute MUST go on the first line"
178                                         " with the other check items", 
179                                         filename, entry->lineno, vp->name,
180                                         entry->name);
181                         }
182                 }
183
184                 /*
185                  * Ok, we've done all the same BS as
186                  * rlm_users, so here we tear apart the
187                  * linked list, and store our users in
188                  * the hashtable we've built instead
189                  */
190
191                 /* Save what was next */
192                 next = entry->next;
193
194                 /* Save the DEFAULT entry specially */
195                 if(strcmp(entry->name, "DEFAULT")==0) {
196                         config.default_entry = entry;
197                         config.default_entry->next = NULL;
198
199                 } else {
200
201                         /* Hash the username */
202                         hashindex = fastuser_hash(entry->name, config.hashsize);
203
204                         /* Store user in the hash */
205                         fastuser_store(hashtable, entry, hashindex);
206
207                         /* Restore entry to next pair_list */
208                 }
209                 entry = next;
210
211         } /* while(entry) loop */
212
213         /* 
214          * Remove auth-type from the Default
215          * entry because it's not a real
216          * default
217          */
218
219         return 0;
220 }
221
222 /* Hashes the username sent to it and returns index into hashtable */
223 int fastuser_hash(const char *s, long hashtablesize) {
224      unsigned long hash = 0;
225
226      while (*s != '\0') {
227          hash = hash * 7907 + (unsigned char)*s++;
228       }
229
230      return (hash % hashtablesize);
231 }
232
233 /* Stores the username sent into the hashtable */
234 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
235
236    /* store new record at beginning of list */
237    new->next = hashtable[idx];
238    hashtable[idx] = new;
239
240    return 1;
241 }
242
243 /*
244  * Looks up user in hashtable.  If user can't be found, returns 0.
245  * Otherwise returns a pointer to the structure for the user
246  */
247 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable, 
248                 const char *user, long hashsize)
249 {
250
251    PAIR_LIST *cur;
252    int idx;
253
254    /* first hash the username and get the index into the hashtable */
255    idx = fastuser_hash(user, hashsize);
256
257    cur = hashtable[idx];
258
259    while((cur != NULL) && (strcmp(cur->name, user))) {
260       cur = cur->next;
261    }
262
263    if(cur) {
264       DEBUG2("  HASH:  user %s found in hashtable bucket %d", user, idx);
265       return cur;
266    }
267
268    return (PAIR_LIST *)0;
269
270 }
271
272
273 /*
274  *      (Re-)read the "users" file into memory.
275  */
276 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
277 {
278         struct fastuser_instance *inst=0;
279         int rcode;
280         long memsize=0;
281
282         inst = malloc(sizeof *inst);
283         if (!inst) {
284                 radlog(L_ERR|L_CONS, "Out of memory\n");
285                 return -1;
286         }
287         memset(inst, 0, sizeof(inst));
288
289         if (cf_section_parse(conf, module_config) < 0) {
290                 free(inst);
291                 return -1;
292         }
293
294         /* 
295          * Sue me.  The tradeoff for this extra variable
296          * is clean code below
297          */
298         memsize = sizeof(PAIR_LIST *) * inst->hashsize;
299         /* 
300          * Allocate space for hash table here
301          */
302         if( (inst->hashtable = (PAIR_LIST **)malloc(memsize)) == NULL) {
303                 radlog(L_ERR, "fastusers:  Can't build hashtable, out of memory!");
304                 return -1;
305         }
306         memset((PAIR_LIST *)inst->hashtable, 0, memsize);
307
308         rcode = fastuser_getfile(config.usersfile, inst->hashtable);
309         if (rcode != 0) {
310                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
311                 return -1;
312         }
313
314         inst->usersfile = config.usersfile;
315         inst->hashsize = config.hashsize;
316         inst->default_entry = config.default_entry;
317         inst->compat_mode = config.compat_mode;
318         inst->users = NULL;
319
320         config.usersfile = NULL;
321         config.hashtable = NULL;
322         config.default_entry = NULL;
323         config.users = NULL;
324         config.compat_mode = NULL;
325
326         *instance = inst;
327         return 0;
328 }
329
330 /*
331  *      Find the named user in the database.  Create the
332  *      set of attribute-value pairs to check and reply with
333  *      for this user from the database. The main code only
334  *      needs to check the password, the rest is done here.
335  */
336 static int fastuser_authorize(void *instance, REQUEST *request)
337 {
338         VALUE_PAIR      *namepair;
339         VALUE_PAIR      *request_pairs;
340         VALUE_PAIR      *check_tmp;
341         VALUE_PAIR      *reply_tmp;
342         VALUE_PAIR      **check_pairs;
343         VALUE_PAIR      **reply_pairs;
344         VALUE_PAIR      *check_save;
345         PAIR_LIST               *user;
346         const char      *name;
347         int                     found=0;
348         struct fastuser_instance *inst = instance;
349
350         request_pairs = request->packet->vps;
351         check_pairs = &request->config_items;
352         reply_pairs = &request->reply->vps;
353
354         /*
355          *      Grab the canonical user name.
356          */
357         namepair = request->username;
358         name = namepair ? (char *) namepair->strvalue : "NONE";
359
360         /*
361          *      Find the entry for the user.
362          */
363         if((user=fastuser_find(inst->hashtable, name, inst->hashsize))==NULL) {
364                 return RLM_MODULE_NOTFOUND;
365         }
366
367         if(mainconfig.do_usercollide) {
368                 /* Save the orginal config items */
369                 check_save = paircopy(request->config_items);
370
371                 while((user) && (!found) && (strcmp(user->name, name)==0)) {
372                         DEBUG2("  fastusers: Checking %s at %d", user->name, user->lineno);
373
374                         /* Copy this users check pairs to the request */
375                         check_tmp = paircopy(user->check);
376                         pairmove(check_pairs, &check_tmp);
377                         pairfree(check_tmp); 
378
379                         /* Check the req to see if we matched */
380                         if(rad_check_password(request)==0) {
381                                 found = 1;
382
383                         /* We didn't match here */
384                         } else {
385                                 /* Restore check items */
386                                 pairfree(request->config_items); 
387                                 request->config_items = paircopy(check_save);
388                                 check_pairs = &request->config_items;
389                                 user = user->next;
390                         }
391                 }
392
393                 /* Free our saved config items */
394                 pairfree(check_save);
395
396                 if(!found) 
397                         return RLM_MODULE_NOTFOUND;
398         } 
399
400         DEBUG2("  fastusers: Matched %s at %d", user->name, user->lineno);
401         
402         /* We've already done this above if(mainconfig.do_usercollide) */
403         if(!mainconfig.do_usercollide) {
404                 check_tmp = paircopy(user->check);
405                 pairmove(check_pairs, &check_tmp);
406                 pairfree(check_tmp); 
407         }
408         reply_tmp = paircopy(user->reply);
409         pairmove(reply_pairs, &reply_tmp);
410         pairfree(reply_tmp);
411
412         /* 
413          * We also need to add the pairs from 
414          * inst->default_entry if the vp is
415          * not already present.
416          */
417                 
418         if(inst->default_entry) {
419                 check_tmp = paircopy(inst->default_entry->check);
420                 reply_tmp = paircopy(inst->default_entry->reply);
421                 pairmove(reply_pairs, &reply_tmp);
422                 pairmove(check_pairs, &check_tmp);
423                 pairfree(reply_tmp);
424                 pairfree(check_tmp); 
425         }
426
427         return RLM_MODULE_UPDATED;
428 }
429
430 /*
431  *      Authentication - unused.
432  */
433 static int fastuser_authenticate(void *instance, REQUEST *request)
434 {
435         instance = instance;
436         request = request;
437         return RLM_MODULE_OK;
438 }
439
440 /*
441  *  Clean up.
442  */
443 static int fastuser_detach(void *instance)
444 {
445         struct fastuser_instance *inst = instance;
446         int hashindex;
447         PAIR_LIST *cur;
448
449
450         /* Free hash table */
451         for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
452                 if(inst->hashtable[hashindex]) {
453                         cur = inst->hashtable[hashindex];
454                         pairlist_free(&cur);
455                 }
456         } 
457
458         free(inst->hashtable);
459         pairlist_free(&inst->users);
460         pairlist_free(&inst->default_entry);
461         free(inst->usersfile);
462         free(inst->compat_mode);
463         free(inst);
464   return 0;
465 }
466
467 /*
468  *      This function is unused
469  */
470 static int fastuser_preacct(void *instance, REQUEST *request)
471 {
472         return RLM_MODULE_FAIL;
473 }
474
475 /*
476  *      This function is unused
477  */
478 static int fastuser_accounting(void *instance, REQUEST *request)
479 {
480         return RLM_MODULE_FAIL;
481 }
482
483 /* globally exported name */
484 module_t rlm_fastusers = {
485         "fastusers",
486         0,                              /* type: reserved */
487         NULL,                   /* initialization */
488         fastuser_instantiate,           /* instantiation */
489         fastuser_authorize,             /* authorization */
490         fastuser_authenticate,          /* authentication */
491         fastuser_preacct,                       /* preaccounting */
492         fastuser_accounting,            /* accounting */
493         NULL,                                                                   /* checksimul */
494         fastuser_detach,                        /* detach */
495         NULL                            /* destroy */
496 };
497