Jeff Carneal <jeff@apex.net>
[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         int      normal_defaults;
37
38         /* hash table */
39         long hashsize;
40         PAIR_LIST **hashtable;
41         PAIR_LIST *users;
42         PAIR_LIST *default_entry;
43
44         char *usersfile;
45 };
46
47 /* Function declarations */
48 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable);
49 static int fastuser_hash(const char *s, long hashtablesize);
50 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
51 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable, const char *user,
52                                                                                                                         long hashsize);
53
54 /*
55  *      A temporary holding area for config values to be extracted
56  *      into, before they are copied into the instance data
57  */
58 static struct fastuser_instance config;
59
60 static CONF_PARSER module_config[] = {
61         { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
62         { "hashsize",     PW_TYPE_INTEGER, &config.hashsize, "100000" },
63         { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
64         { "normal_defaults", PW_TYPE_BOOLEAN, &config.normal_defaults, "yes" },
65         { NULL, -1, NULL, NULL }
66 };
67
68 static int fastuser_getfile(const char *filename, PAIR_LIST **hashtable)
69 {
70         int rcode;
71         PAIR_LIST *users = NULL;
72         int compat_mode = FALSE;
73         PAIR_LIST *entry, *next, *cur;
74         VALUE_PAIR *vp;
75         int hashindex = 0;
76         int numdefaults = 0;
77
78         rcode = pairlist_read(filename, &users, 1);
79         if (rcode < 0) {
80                 return -1;
81         }
82
83         if (strcmp(config.compat_mode, "cistron") == 0) {
84                 compat_mode = TRUE;
85         }
86         
87         entry = users;
88         while (entry) {
89                 if (compat_mode) {
90                         DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
91                                 filename, entry->lineno, entry->name);
92                 }
93
94                 /*
95                  *      Look for improper use of '=' in the
96                  *      check items.  They should be using
97                  *      '==' for on-the-wire RADIUS attributes,
98                  *      and probably ':=' for server
99                  *      configuration items.
100                  */
101                 for (vp = entry->check; vp != NULL; vp = vp->next) {
102                         /*
103                          *      Ignore attributes which are set
104                          *      properly.
105                          */
106                         if (vp->operator != T_OP_EQ) 
107                                 continue;
108                                 
109
110                         /*
111                          *      If it's a vendor attribute,
112                          *      or it's a wire protocol, 
113                          *      ensure it has '=='.
114                          */
115                         if (((vp->attribute & ~0xffff) != 0) ||
116                                 (vp->attribute < 0x100)) {
117                                 if (!compat_mode) {
118                                         DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
119                                         filename, entry->lineno, vp->name, vp->name, entry->name);
120                                 } else {
121                                         DEBUG("\tChanging '%s =' to '%s =='",
122                                                 vp->name, vp->name);
123                                 }
124                                 vp->operator = T_OP_CMP_EQ;
125                                 continue;
126                         }
127                                 
128                         /*
129                          *      Cistron Compatibility mode.
130                          *
131                          *      Re-write selected attributes
132                          *      to be '+=', instead of '='.
133                          *
134                          *      All others get set to '=='
135                          */
136                         if (compat_mode) {
137                                 /*
138                                  *      Non-wire attributes become +=
139                                  *
140                                  *      On the write attributes
141                                  *      become ==
142                                  */
143                                 if ((vp->attribute >= 0x100) &&
144                                         (vp->attribute <= 0xffff) &&
145                                         (vp->attribute != PW_HINT) &&
146                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
147                                         DEBUG("\tChanging '%s =' to '%s +='",
148                                                 vp->name, vp->name);
149                                                 vp->operator = T_OP_ADD;
150                                 } else {
151                                         DEBUG("\tChanging '%s =' to '%s =='",
152                                                 vp->name, vp->name);
153                                         vp->operator = T_OP_CMP_EQ;
154                                 }
155                         }
156                                 
157                 } /* end of loop over check items */
158                 
159                 
160                 /*
161                  *      Look for server configuration items
162                  *      in the reply list.
163                  *
164                  *      It's a common enough mistake, that it's
165                  *      worth doing.
166                  */
167                 for (vp = entry->reply; vp != NULL; vp = vp->next) {
168                         /*
169                          *      If it's NOT a vendor attribute,
170                          *      and it's NOT a wire protocol
171                          *      and we ignore Fall-Through,
172                          *      then bitch about it, giving a
173                          *      good warning message.
174                          */
175                         if (!(vp->attribute & ~0xffff) &&
176                                 (vp->attribute > 0xff) &&
177                                 (vp->attribute > 1000)) {
178                                 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
179                                         "\tfound in reply item list for user \"%s\".\n"
180                                         "\tThis attribute MUST go on the first line"
181                                         " with the other check items", 
182                                         filename, entry->lineno, vp->name,
183                                         entry->name);
184                         }
185                 }
186
187                 /*
188                  * Ok, we've done all the same BS as
189                  * rlm_users, so here we tear apart the
190                  * linked list, and store our users in
191                  * the hashtable we've built instead
192                  */
193
194                 /* Save what was next */
195                 next = entry->next;
196
197                 /* Save the DEFAULT entry specially */
198                 if(strcmp(entry->name, "DEFAULT")==0) {
199                                 numdefaults++;
200                                 /* put it at the end of the list */
201                                 if(config.default_entry) {
202                                         for(cur=config.default_entry; cur->next; cur=cur->next);
203                                         cur->next = entry;
204                                         entry->next = NULL;
205                                 } else {
206                                         config.default_entry = entry;
207                                         config.default_entry->next = NULL;
208                                 }
209
210                 } else {
211
212                         /* Hash the username */
213                         hashindex = fastuser_hash(entry->name, config.hashsize);
214
215                         /* Store user in the hash */
216                         fastuser_store(hashtable, entry, hashindex);
217
218                         /* Restore entry to next pair_list */
219                 }
220                 entry = next;
221
222         } /* while(entry) loop */
223
224         if(!config.normal_defaults && (numdefaults>1)) {
225                 radlog(L_INFO, "Warning:  fastusers found multiple DEFAULT entries.  Using the first.");
226         }
227
228         /* 
229          * We *should* do this to help out clueless admins
230          * but it's documented, so it will confuse those who
231          * do read the docs if we do it here as well
232         if(!config.normal_defaults) {
233                 pairdelete(&config.default_entry->check, PW_AUTHTYPE);
234         }
235          */
236
237         return 0;
238 }
239
240 /* Hashes the username sent to it and returns index into hashtable */
241 int fastuser_hash(const char *s, long hashtablesize) {
242      unsigned long hash = 0;
243
244      while (*s != '\0') {
245          hash = hash * 7907 + (unsigned char)*s++;
246       }
247
248      return (hash % hashtablesize);
249 }
250
251 /* Stores the username sent into the hashtable */
252 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
253
254    /* store new record at beginning of list */
255    new->next = hashtable[idx];
256    hashtable[idx] = new;
257
258    return 1;
259 }
260
261 /*
262  * Looks up user in hashtable.  If user can't be found, returns 0.
263  * Otherwise returns a pointer to the structure for the user
264  */
265 static PAIR_LIST *fastuser_find(PAIR_LIST **hashtable, 
266                 const char *user, long hashsize)
267 {
268
269    PAIR_LIST *cur;
270    int idx;
271
272    /* first hash the username and get the index into the hashtable */
273    idx = fastuser_hash(user, hashsize);
274
275    cur = hashtable[idx];
276
277    while((cur != NULL) && (strcmp(cur->name, user))) {
278       cur = cur->next;
279    }
280
281    if(cur) {
282       DEBUG2("  fastusers:  user %s found in hashtable bucket %d", user, idx);
283       return cur;
284    }
285
286    return (PAIR_LIST *)0;
287
288 }
289
290
291 /*
292  *      (Re-)read the "users" file into memory.
293  */
294 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
295 {
296         struct fastuser_instance *inst=0;
297         int rcode;
298         long memsize=0;
299
300         inst = malloc(sizeof *inst);
301         if (!inst) {
302                 radlog(L_ERR|L_CONS, "Out of memory\n");
303                 return -1;
304         }
305         memset(inst, 0, sizeof(inst));
306
307         if (cf_section_parse(conf, module_config) < 0) {
308                 free(inst);
309                 return -1;
310         }
311
312         /* 
313          * Sue me.  The tradeoff for this extra variable
314          * is clean code below
315          */
316         memsize = sizeof(PAIR_LIST *) * config.hashsize;
317         /* 
318          * Allocate space for hash table here
319          */
320         if( (inst->hashtable = (PAIR_LIST **)malloc(memsize)) == NULL) {
321                 radlog(L_ERR, "fastusers:  Can't build hashtable, out of memory!");
322                 return -1;
323         }
324         memset((PAIR_LIST *)inst->hashtable, 0, memsize);
325
326         rcode = fastuser_getfile(config.usersfile, inst->hashtable);
327         if (rcode != 0) {
328                 radlog(L_ERR|L_CONS, "Errors reading %s", config.usersfile);
329                 return -1;
330         }
331
332         inst->usersfile = config.usersfile;
333         inst->hashsize = config.hashsize;
334         inst->default_entry = config.default_entry;
335         inst->compat_mode = config.compat_mode;
336         inst->normal_defaults = config.normal_defaults;
337         inst->users = NULL;
338
339         config.usersfile = NULL;
340         config.hashtable = NULL;
341         config.default_entry = NULL;
342         config.users = NULL;
343         config.compat_mode = NULL;
344
345         *instance = inst;
346         return 0;
347 }
348
349 /*
350  *      Find the named user in the database.  Create the
351  *      set of attribute-value pairs to check and reply with
352  *      for this user from the database. The main code only
353  *      needs to check the password, the rest is done here.
354  */
355 static int fastuser_authorize(void *instance, REQUEST *request)
356 {
357
358         VALUE_PAIR      *namepair;
359         VALUE_PAIR      *request_pairs;
360         VALUE_PAIR      *check_tmp;
361         VALUE_PAIR      *reply_tmp;
362         VALUE_PAIR      **check_pairs;
363         VALUE_PAIR      **reply_pairs;
364         VALUE_PAIR      *check_save;
365         PAIR_LIST               *user;
366         const char      *name;
367         int                     found=0;
368         int                     checkdefault = 0;
369         struct fastuser_instance *inst = instance;
370
371         request_pairs = request->packet->vps;
372         check_pairs = &request->config_items;
373         reply_pairs = &request->reply->vps;
374
375         /*
376          *      Grab the canonical user name.
377          */
378         namepair = request->username;
379         name = namepair ? (char *) namepair->strvalue : "NONE";
380
381         /*
382          *      Find the entry for the user.
383          */
384         if((user=fastuser_find(inst->hashtable, name, inst->hashsize))==NULL) {
385                 if(inst->normal_defaults) {
386                         checkdefault = 1;
387                 } else {
388                         return RLM_MODULE_NOTFOUND;
389                 }
390         }
391
392         /*
393          * Usercollide means we have to compare check pairs
394          * _and_ the password
395          */
396         if(mainconfig.do_usercollide && !checkdefault) {
397                 /* Save the orginal config items */
398                 check_save = paircopy(request->config_items);
399
400                 while((user) && (!found) && (strcmp(user->name, name)==0)) {
401                         if(paircmp(request_pairs, user->check, reply_pairs) != 0) {
402                                 user = user->next;
403                                 continue;
404                         }
405                         DEBUG2("  fastusers(uc): Checking %s at %d", user->name, user->lineno);
406
407                         /* Copy this users check pairs to the request */
408                         check_tmp = paircopy(user->check);
409                         pairmove(check_pairs, &check_tmp);
410                         pairfree(check_tmp); 
411
412                         /* Check the req to see if we matched */
413                         if(rad_check_password(request)==0) {
414                                 found = 1;
415
416                         /* We didn't match here */
417                         } else {
418                                 /* Restore check items */
419                                 pairfree(request->config_items); 
420                                 request->config_items = paircopy(check_save);
421                                 check_pairs = &request->config_items;
422                                 user = user->next;
423                         }
424                 }
425
426                 /* Free our saved config items */
427                 pairfree(check_save);
428         }
429
430         /*
431          * No usercollide, just compare check pairs
432          */
433         if(!mainconfig.do_usercollide && !checkdefault) {
434                 while((user) && (!found) && (strcmp(user->name, name)==0)) {
435                         if(paircmp(request_pairs, user->check, reply_pairs) == 0) {
436                                 found = 1;
437                                 DEBUG2("  fastusers: Matched %s at %d", user->name, user->lineno);
438                         } else {
439                                 user = user->next;
440                         }       
441                 }
442         }
443
444         /* 
445          * When we get here, we've either found the user or not
446          * and we either do normal DEFAULTs or not.  
447          */
448         
449         /*
450          * We found the user & normal default 
451          * copy relevant pairs and return
452          */
453         if(found && inst->normal_defaults) {
454                 check_tmp = paircopy(user->check);
455                 pairmove(check_pairs, &check_tmp);
456                 pairfree(check_tmp); 
457                 reply_tmp = paircopy(user->reply);
458                 pairmove(reply_pairs, &reply_tmp);
459                 pairfree(reply_tmp);
460                 return RLM_MODULE_UPDATED;
461         }
462
463         /*
464          * We didn't find the user, and we aren't supposed to
465          * check defaults.  So just report not found.
466          */
467         if(!found && !inst->normal_defaults) {
468                 return RLM_MODULE_NOTFOUND;
469         }
470
471         /*
472          * We didn't find the user, but we should 
473          * check the defaults.  
474          */
475         if(!found && inst->normal_defaults) {
476                 user = inst->default_entry;
477                 while((user) && (!found)) {
478                         if(paircmp(request_pairs, user->check, reply_pairs) == 0) {
479                                 DEBUG2("  fastusers: Matched %s at %d", user->name, user->lineno);
480                                 found = 1;
481                         } else {
482                                 user = user->next;
483                         }
484                 }
485
486                 if(found) {
487                         check_tmp = paircopy(user->check);
488                         pairmove(check_pairs, &check_tmp);
489                         pairfree(check_tmp); 
490                         reply_tmp = paircopy(user->reply);
491                         pairmove(reply_pairs, &reply_tmp);
492                         pairfree(reply_tmp);
493                         return RLM_MODULE_UPDATED;
494
495                 } else {
496                         return RLM_MODULE_NOTFOUND;
497                 }
498         }
499
500         /*
501          * We found the user, and we don't use normal defaults.
502          * So copy the check and reply pairs from the default
503          * entry to the request
504          */
505         if(found && !inst->normal_defaults) {
506
507                 /* We've already done this above if(mainconfig.do_usercollide) */
508                 if(!mainconfig.do_usercollide) {
509                         check_tmp = paircopy(user->check);
510                         pairmove(check_pairs, &check_tmp);
511                         pairfree(check_tmp); 
512                 }
513                 reply_tmp = paircopy(user->reply);
514                 pairmove(reply_pairs, &reply_tmp);
515                 pairfree(reply_tmp);
516
517                 /* 
518                  * We also need to add the pairs from 
519                  * inst->default_entry if the vp is
520                  * not already present.
521                  */
522                 
523                 if(inst->default_entry) {
524                         check_tmp = paircopy(inst->default_entry->check);
525                         reply_tmp = paircopy(inst->default_entry->reply);
526                         pairmove(reply_pairs, &reply_tmp);
527                         pairmove(check_pairs, &check_tmp);
528                         pairfree(reply_tmp);
529                         pairfree(check_tmp); 
530                 }
531
532                 return RLM_MODULE_UPDATED;
533         }
534
535 }
536
537 /*
538  *      Authentication - unused.
539  */
540 static int fastuser_authenticate(void *instance, REQUEST *request)
541 {
542         instance = instance;
543         request = request;
544         return RLM_MODULE_OK;
545 }
546
547 /*
548  *  Clean up.
549  */
550 static int fastuser_detach(void *instance)
551 {
552         struct fastuser_instance *inst = instance;
553         int hashindex;
554         PAIR_LIST *cur;
555
556
557         /* Free hash table */
558         for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
559                 if(inst->hashtable[hashindex]) {
560                         cur = inst->hashtable[hashindex];
561                         pairlist_free(&cur);
562                 }
563         } 
564
565         free(inst->hashtable);
566         pairlist_free(&inst->users);
567         pairlist_free(&inst->default_entry);
568         free(inst->usersfile);
569         free(inst->compat_mode);
570         free(inst);
571   return 0;
572 }
573
574 /*
575  *      This function is unused
576  */
577 static int fastuser_preacct(void *instance, REQUEST *request)
578 {
579         return RLM_MODULE_FAIL;
580 }
581
582 /*
583  *      This function is unused
584  */
585 static int fastuser_accounting(void *instance, REQUEST *request)
586 {
587         return RLM_MODULE_FAIL;
588 }
589
590 /* globally exported name */
591 module_t rlm_fastusers = {
592         "fastusers",
593         0,                              /* type: reserved */
594         NULL,                   /* initialization */
595         fastuser_instantiate,           /* instantiation */
596         fastuser_authorize,             /* authorization */
597         fastuser_authenticate,          /* authentication */
598         fastuser_preacct,                       /* preaccounting */
599         fastuser_accounting,            /* accounting */
600         NULL,                                                                   /* checksimul */
601         fastuser_detach,                        /* detach */
602         NULL                            /* destroy */
603 };
604