Patches from "Alan Curry" <pacman-radius@cqc.com>
[freeradius.git] / src / main / modules.c
1 /*
2  * modules.c    Radius module support.
3  *
4  * Author:      Alan DeKok <aland@ox.org>
5  *
6  * Version:     $Id$
7  *
8  */
9
10 static const char rcsid[] = "$Id$";
11
12 #include        "autoconf.h"
13
14 #include        <stdio.h>
15 #include        <stdlib.h>
16 #include        <string.h>
17
18 #include        "radiusd.h"
19 #include        "modules.h"
20 #include        "conffile.h"
21 #include        "ltdl.h"
22
23 #define RLM_AUTHORIZE           1
24 #define RLM_AUTHENTICATE        2
25 #define RLM_ACCOUNTING          4
26
27 /*
28  *      Keep track of which modules we've loaded.
29  */
30 typedef struct module_list_t {
31         char                    filename[MAX_STRING_LEN];
32         int                     auth_type;
33         module_t                *module;
34         lt_dlhandle             handle;
35         struct module_list_t    *next;
36 } module_list_t;
37
38 static module_list_t *module_list = NULL;
39
40 typedef struct module_instance_t {
41         module_list_t           *entry;
42         char                    name[MAX_STRING_LEN];
43         void                    *insthandle;
44         struct module_instance_t *next;
45 } module_instance_t;
46
47 static module_instance_t *module_instance_list = NULL;
48
49 typedef struct config_module_t {
50         module_instance_t       *instance;
51         struct config_module_t  *next;
52 } config_module_t;
53
54 static config_module_t *authorize = NULL;
55 static config_module_t *authenticate = NULL;
56 static config_module_t *preacct = NULL;
57 static config_module_t *accounting = NULL;
58
59 static void config_list_free(config_module_t **cf)
60 {
61         config_module_t *c, *next;
62
63         c = *cf;
64         while (c) {
65                 next = c->next;
66                 free(c);
67                 c = next;
68         }
69         *cf = NULL;
70 }
71
72 static void instance_list_free(module_instance_t **i)
73 {
74         module_instance_t       *c, *next;
75
76         c = *i;
77         while (c) {
78                 next = c->next;
79                 if(c->entry->module->detach)
80                         (c->entry->module->detach)(c->insthandle);
81                 free(c);
82                 c = next;
83         }
84         *i = NULL;
85 }
86
87 static void module_list_free(void)
88 {
89         module_list_t *ml, *next;
90
91         config_list_free(&authenticate);
92         config_list_free(&authorize);
93         config_list_free(&preacct);
94         config_list_free(&accounting);
95
96         instance_list_free(&module_instance_list);
97
98         ml = module_list;
99         while (ml) {
100                 next = ml->next;
101                 if (ml->module->destroy)
102                         (ml->module->destroy)();
103                 lt_dlclose(ml->handle); /* ignore any errors */
104                 free(ml);
105                 ml = next;
106         }
107
108         module_list = NULL;
109 }
110
111 /*
112  *  New Auth-Type's start at a large number, and go up from there.
113  *
114  *  We could do something more intelligent, but this should work almost
115  *  all of the time.
116  *
117  * FIXME: move this to dict.c as dict_valadd() and dict_valdel()
118  *        also clear value in module_list free (nessecary?)
119  */
120 static int new_authtype_value(const char *name)
121 {
122   static int max_value = 32767;
123   DICT_VALUE *old_value, *new_value;
124
125   /*
126    *  Check to see if it's already defined.
127    *  If so, return the old value.
128    */
129   old_value = dict_valbyname(name);
130   if (old_value) return old_value->value;
131
132   /* Look for the predefined Auth-Type value */
133   old_value = dict_valbyattr(PW_AUTHTYPE, 0);
134   if (!old_value) return 0;     /* something WIERD is happening */
135
136   /* allocate a new value */
137   new_value = (DICT_VALUE *) malloc(sizeof(DICT_VALUE));
138   if (!new_value) {
139     fprintf(stderr, "Out of memory\n");
140     exit(1);
141   }
142
143   /* copy the old to the new */
144   memcpy(new_value, old_value, sizeof(DICT_VALUE));
145   old_value->next = new_value;
146
147   /* set it up */
148   strNcpy(new_value->name, name, sizeof(new_value->name));
149   new_value->value = max_value++;
150
151   return new_value->value;
152 }
153
154 static module_list_t *find_module(module_list_t *head, const char *filename,
155                                   const char *module_name,
156                                   const char *cffilename, int cflineno)
157 {
158         module_list_t   **last, *node;
159         module_list_t   *new;
160         void            *handle;
161         const char      *error;
162
163         while (head) {
164                 if (strcmp(head->filename, filename) == 0)
165                         return head;
166                 head = head->next;
167         }
168
169         last = &module_list;
170         node = module_list;
171         while (node) {
172                 last = &node->next;
173                 node = node->next;
174         }
175
176         /*
177          * Keep the handle around so we can dlclose() it.
178          * Also ensure that any further dependencies are exported,
179          * so that PAM can work.
180          *
181          * i.e. rlm_pam.so links to libpam.so, which in turn dlopen()'s
182          * pam_foo.so.  Without RTLD_GLOBAL, the functions in libpam.so
183          * won't get exported to pam_foo.so.
184          */
185         handle = lt_dlopenext(filename);
186         if (handle == NULL) {
187                 fprintf(stderr, "%s[%d] Failed to link to module %s:"
188                         " %s\n", cffilename, cflineno, filename, lt_dlerror());
189                 return NULL;
190         }
191
192         /* make room for the module type */
193         new = (module_list_t *) malloc(sizeof(module_list_t));
194         if (new == NULL) {
195                 fprintf(stderr, "%s[%d] Failed to allocate memory.\n",
196                         cffilename, cflineno);
197                 lt_dlclose(handle);     /* ignore any errors */
198                 return NULL;
199         }
200
201         /* fill in the module structure */
202         new->next = NULL;
203         new->handle = handle;
204         strNcpy(new->filename, filename, sizeof(new->filename));
205
206         new->module = (module_t *) lt_dlsym(new->handle, module_name);
207         error = lt_dlerror();
208         if (!new->module) {
209                 fprintf(stderr, "%s[%d] Failed linking to "
210                                 "%s structure in %s: %s\n",
211                                 cffilename, cflineno,
212                                 module_name, cffilename, error);
213                 lt_dlclose(new->handle);        /* ignore any errors */
214                 free(new);
215                 return NULL;
216         }
217
218         /* If there's an authentication method, add a new Auth-Type */
219         if (new->module->authenticate)
220                 new->auth_type =
221                         new_authtype_value(new->module->name);
222
223         /* call the modules initialization */
224         if (new->module->init &&
225             (new->module->init)() < 0) {
226                 fprintf(stderr,
227                    "%s[%d] Module initialization failed.\n",
228                         cffilename, cflineno);
229                 lt_dlclose(new->handle);        /* ignore any errors */
230                 free(new);
231                 return NULL;
232         }
233
234         DEBUG("Module: Loaded %s ", new->module->name);
235
236         *last = new;
237
238         return new;
239 }
240
241 static module_instance_t *find_module_instance(module_instance_t *head,
242                                                const char *instname)
243 {
244         CONF_SECTION *cs, *inst_cs;
245         module_instance_t *new;
246         char library[256];
247         char module_name[256];
248
249         while (head) {
250                 if (strcmp(head->name, instname) == 0)
251                         return head;
252                 head = head->next;
253         }
254
255         /* Instance doesn't exist yet. Try to find the corresponding config
256          * section and create it. */
257
258         new = malloc(sizeof *new);
259         if (!new) {
260                 fprintf(stderr, "Out of memory\n");
261                 exit(1);
262         }
263
264         cs = cf_section_find("modules");
265         if (!cs) {
266                 return NULL;
267         }
268
269         /* Module instances are declared in the modules{} block and referenced
270          * later by their name, which is the name2 from the config section,
271          * or name1 if there was no name2. */
272
273         for(inst_cs=cf_subsection_find_next(cs, NULL, NULL)
274             ; inst_cs ;
275             inst_cs=cf_subsection_find_next(cs, inst_cs, NULL)) {
276                 if ( (inst_cs->name2 && !strcmp(inst_cs->name2, instname)) ||
277                      (!inst_cs->name2 && !strcmp(inst_cs->name1, instname)) )
278                         break;
279         }
280         if (!inst_cs)
281                 return NULL;
282
283         snprintf(library, sizeof library, "rlm_%s.so", inst_cs->name1);
284         snprintf(module_name, sizeof module_name, "rlm_%s", inst_cs->name1);
285         new->entry = find_module(module_list, library, module_name,
286                                  "radiusd.conf", inst_cs->lineno);
287         if(!new->entry) {
288                 free(new);
289                 return NULL;
290         }
291
292         if(!new->entry->module->instantiate) {
293                 new->insthandle = 0;
294         } else if((new->entry->module->instantiate)(inst_cs,
295                                                     &new->insthandle) < 0) {
296                 fprintf(stderr,
297                         "radiusd.conf[%d]: %s: Module instantiation failed.\n",
298                         inst_cs->lineno, instname);
299                 free(new);
300                 return NULL;
301         }
302
303         strNcpy(new->name, instname, sizeof(new->name));
304         new->next = module_instance_list;
305         module_instance_list = new;
306
307         DEBUG("Module: Instantiated %s (%s) ", inst_cs->name1, new->name);
308
309         return new;
310 }
311
312 /*
313  *      Add one entry at the end of the config_module_t list.
314  */
315 static void add_to_list(config_module_t **head, module_instance_t *instance)
316 {
317         config_module_t *node = *head;
318         config_module_t **last = head;
319
320         while (node) {
321                 last = &node->next;
322                 node = node->next;
323         }
324
325         node = (config_module_t *) malloc(sizeof(config_module_t));
326         if (!node) {
327                 fprintf(stderr, "Out of memory\n");
328                 exit(1);
329         }
330
331         node->next = NULL;
332         node->instance = instance;
333         *last = node;
334 }
335
336
337 /* Why is this both here and in radiusd.c:server_config? --Pac. */
338 static CONF_PARSER module_config[] = {
339   { "libdir",            PW_TYPE_STRING_PTR, &radlib_dir,        LIBDIR },
340
341   { NULL, -1, NULL, NULL }
342 };
343
344 /*
345  *      Parse the module config sections, and load
346  *      and call each module's init() function.
347  *
348  *      Libtool makes your life a LOT easier, especially with libltdl.
349  *      see: http://www.gnu.org/software/libtool/
350  */
351 int setup_modules(void)
352 {
353         module_instance_t *this;
354         const char      *control;
355         int             comp;
356         CONF_SECTION    *cs;
357         CONF_PAIR       *modref;
358         const char *filename="radiusd.conf";
359
360         this = NULL; /* Shut up stupid gcc */
361
362         /*
363          *      And parse the modules configuration values.
364          */
365         cs = cf_section_find(NULL);
366         if (cs) {
367                 cf_section_parse(cs, module_config);
368         }
369
370         /*
371          *      No current list of modules: Go initialize libltdl.
372          */
373         if (!module_list) {
374                 if (lt_dlinit() != 0) {
375                         fprintf(stderr, "Failed to initialize libraries: %s\n",
376                                 lt_dlerror());
377                         exit(1); /* FIXME */
378                         
379                 }
380                 /*
381                  *      Set the default list of preloaded symbols.
382                  *      This is used to initialize libltdl's list of
383                  *      preloaded modules. 
384                  *
385                  *      i.e. Static modules.
386                  */
387                 LTDL_SET_PRELOADED_SYMBOLS();
388
389                 /*
390                  *      Set the search path to ONLY our library directory.
391                  *      This prevents the modules fromn being found from
392                  *      any location on the disk.
393                  */
394                 lt_dlsetsearchpath(radlib_dir);
395                 
396                 /*
397                  *      Add 'raddb' directory, so that testing the server
398                  *      in the source directory can work.
399                  */
400                 lt_dladdsearchdir(radius_dir);
401                 
402                 DEBUG2("modules: Library search path is %s",
403                        lt_dlgetsearchpath());
404
405         } else {
406                 module_list_free();
407         }
408
409         for (comp=0; comp<RLM_COMPONENT_COUNT; ++comp) {
410         switch(comp) {
411                 case RLM_COMPONENT_AUTZ: control="authorize"; break;
412                 case RLM_COMPONENT_AUTH: control="authenticate"; break;
413                 case RLM_COMPONENT_PREACCT: control="preacct"; break;
414                 case RLM_COMPONENT_ACCT: control="accounting"; break;
415                 default: control="unknown";
416         }
417
418         cs = cf_section_find(control);
419         if (!cs)
420                 continue;
421
422         /* These need to be subsections instead of attr/value pairs if we're
423          * going to do failover config here. I will wait until I've figured
424          * out the shorthand empty-subsection syntax first though.
425          * --Pac */
426         for(modref=cf_pair_find_next(cs, NULL, NULL)
427             ; modref ;
428             modref=cf_pair_find_next(cs, modref, NULL)) {
429
430         /*
431          *      Yes, we're missing two indents here.
432          *      It's yucky but otherwise it doesn't fit. That
433          *      ofcourse means that this function should
434          *      be split up....
435          */
436         this = find_module_instance(module_instance_list, cf_pair_attr(modref));
437         if (this == NULL) {
438                 exit(1); /* FIXME */
439         }
440
441         if (strcmp(control, "authorize") == 0) {
442                 if (!this->entry->module->authorize) {
443                         fprintf(stderr, "%s[%d] Module %s does not contain "
444                                         "an 'authorize' entry\n",
445                                         filename, modref->lineno,
446                                         this->entry->module->name);
447                         exit(1);
448                 }
449
450                 add_to_list(&authorize, this);
451         } else if (strcmp(control, "authenticate") == 0) {
452                 if (!this->entry->module->authenticate) {
453                         fprintf(stderr, "%s[%d] Module %s does not contain "
454                                         "an 'authenticate' entry\n",
455                                         filename, modref->lineno,
456                                         this->entry->module->name);
457                         exit(1);
458                 }
459
460                 add_to_list(&authenticate, this);
461         } else if (strcmp(control, "preacct") == 0) {
462                 if (!this->entry->module->preaccounting) {
463                         fprintf(stderr, "%s[%d] Module %s does not contain "
464                                         "a 'preacct' entry\n",
465                                         filename, modref->lineno,
466                                         this->entry->module->name);
467                         exit(1);
468                 }
469                 add_to_list(&preacct, this);
470         } else if (strcmp(control, "accounting") == 0) {
471                 if (!this->entry->module->accounting) {
472                         fprintf(stderr, "%s[%d] Module %s does not contain "
473                                         "an 'accounting' entry\n",
474                                         filename, modref->lineno,
475                                         this->entry->module->name);
476                         exit(1);
477                 }
478
479                 add_to_list(&accounting, this);
480         } else {
481                 fprintf(stderr, "%s[%d] Unknown control \"%s\".\n",
482                         filename, modref->lineno, control);
483                 exit(1);
484         }
485
486         }
487         } /* YUCK */
488
489         return 0;
490 }
491
492
493 /*
494  *      Update the Stripped-User-Name attribute.
495  */
496 static void update_username(REQUEST *request, char *newname)
497 {
498         VALUE_PAIR *vp;
499
500         /*
501          *      If there isn't a Stripped-User-Name attribute,
502          *      go add one, and make it the definitive user name.
503          */
504         if (request->username->attribute != PW_STRIPPED_USER_NAME) {
505                 vp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
506                 if (!vp) {
507                         log(L_ERR|L_CONS, "no memory");
508                         exit(1);
509                 }
510                 DEBUG2("  authorize: Creating Stripped-User-Name of %s", newname);
511                 strcpy(vp->strvalue, newname);
512                 vp->length = strlen((char *)vp->strvalue);
513                 pairadd(&request->packet->vps, vp);
514                 request->username = vp;
515                 return;
516         }
517
518         /*
519          *      There is one, update it in place.
520          */
521         vp = request->username;
522         DEBUG2("  authorize: Updating Stripped-User-Name from %s to %s",
523                vp->strvalue, newname);
524         strcpy(vp->strvalue, newname);
525         vp->length = strlen((char *)vp->strvalue);
526 }
527
528 /*
529  *      Call all authorization modules until one returns
530  *      somethings else than RLM_MODULE_OK
531  */
532 int module_authorize(REQUEST *request,
533                      VALUE_PAIR **check_items, VALUE_PAIR **reply_items)
534 {
535         config_module_t *this;
536         int             rcode = RLM_MODULE_OK;
537
538         this = authorize;
539         rcode = RLM_MODULE_OK;
540
541         while (this && rcode == RLM_MODULE_OK) {
542                 DEBUG2("  authorize: %s", this->instance->entry->module->name);
543                 rcode = (this->instance->entry->module->authorize)(
544                          this->instance->insthandle, request, check_items,
545                          reply_items);
546                 this = this->next;
547         }
548
549         /*
550          *      Before authenticating the user, update the
551          *      Stripped-User-Name attribute with any additions.
552          *
553          *      No name: nothing to add.
554          */
555         if (request->username != NULL) {
556                 char newname[256];
557                 VALUE_PAIR *vp;
558
559                 /*
560                  *      Try to add a prefix
561                  */
562                 for (vp = request->config_items; vp != NULL; vp = vp->next) {
563                         switch (vp->attribute) {
564                         default:
565                                 break;
566                                 
567                         case PW_ADD_PREFIX:
568                                 if ((size_t)(vp->length + request->username->length) > sizeof(vp->strvalue)) {
569                                         DEBUG2("\"%s\"+\"%s\" too long",
570                                                vp->strvalue,
571                                                request->username->strvalue);
572                                         continue;
573                                 }
574                                 strcpy(newname, vp->strvalue);
575                                 strcat(newname, (char *)request->username->strvalue);
576                                 update_username(request, newname);
577                                 break;
578                                 
579                         case PW_ADD_SUFFIX:
580                                 if ((size_t)(vp->length + request->username->length) > sizeof(vp->strvalue)) {
581                                         DEBUG2("\"%s\"+\"%s\" too long",
582                                                request->username->strvalue,
583                                                vp->strvalue);
584                                         continue;
585                                 }
586                                 strcpy(newname, request->username->strvalue);
587                                 strcat(newname, (char *)vp->strvalue);
588                                 update_username(request, newname);
589                                 break;
590                         }
591                 } /* over all configuration items */
592
593                 pairdelete(&request->config_items, PW_ADD_PREFIX);
594                 pairdelete(&request->config_items, PW_ADD_SUFFIX);
595         }
596
597         return rcode;
598 }
599
600 /*
601  *      Authenticate a user/password with various methods.
602  */
603 int module_authenticate(int auth_type, REQUEST *request)
604 {
605         config_module_t *this;
606
607         /*
608          *  We MUST have a password, of SOME type!
609          */
610         if (request->password == NULL) {
611                 return RLM_MODULE_FAIL;
612         }
613
614         this = authenticate;
615         while (this && this->instance->entry->auth_type != auth_type)
616                 this = this->next;
617
618         if (!this || !this->instance->entry->module->authenticate) {
619                 /*
620                  *      No such auth_type, or module auth_type not defined
621                  */
622                 return RLM_MODULE_FAIL;
623         }
624
625         DEBUG2("  authenticate: %s", this->instance->entry->module->name);
626         return (this->instance->entry->module->authenticate)(
627                 this->instance->insthandle, request);
628 }
629
630
631 /*
632  *      Do pre-accounting for ALL configured sessions
633  */
634 int module_preacct(REQUEST *request)
635 {
636         config_module_t *this;
637         int             rcode;
638
639         this = preacct;
640         rcode = RLM_MODULE_OK;
641
642         while (this && (rcode == RLM_MODULE_OK)) {
643                 DEBUG2("  preacct: %s", this->instance->entry->module->name);
644                 rcode = (this->instance->entry->module->preaccounting)
645                                 (this->instance->insthandle, request);
646                 this = this->next;
647         }
648
649         return rcode;
650 }
651
652 /*
653  *      Do accounting for ALL configured sessions
654  */
655 int module_accounting(REQUEST *request)
656 {
657         config_module_t *this;
658         int             rcode;
659
660         this = accounting;
661         rcode = RLM_MODULE_OK;
662
663         while (this && (rcode == RLM_MODULE_OK)) {
664                 DEBUG2("  accounting: %s", this->instance->entry->module->name);
665                 rcode = (this->instance->entry->module->accounting)
666                                 (this->instance->insthandle, request);
667                 this = this->next;
668         }
669
670         return rcode;
671 }
672
673 /*
674  *      Module malloc() call, which does stuff if the malloc fails.
675  *
676  *      This call ALWAYS succeeds!
677  */
678 void *rlm_malloc(size_t size)
679 {
680         void *ptr = malloc(size);
681         
682         if (ptr == NULL) {
683                 log(L_ERR|L_CONS, "no memory");
684                 exit(1);
685         }
686
687         return ptr;
688 }