added 'xlat' function.
[freeradius.git] / src / modules / rlm_perl / rlm_perl.c
1 /*
2  * rlm_perl.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 2002  The FreeRADIUS server project
21  * Copyright 2002  Boian Jordanov <bjordanov@orbitel.bg>
22  */
23
24 #include "autoconf.h"
25 #include "libradius.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "radiusd.h"
32 #include "modules.h"
33 #include "conffile.h"
34
35 #ifdef DEBUG
36 #undef DEBUG
37 #endif
38
39 #include <EXTERN.h>
40 #include <perl.h>
41
42 #ifndef DO_CLEAN
43 #define DO_CLEAN 0
44 #endif
45
46 static const char rcsid[] = "$Id$";
47
48 /*
49  *      Define a structure for our module configuration.
50  *
51  *      These variables do not need to be in a structure, but it's
52  *      a lot cleaner to do so, and a pointer to the structure can
53  *      be used as the instance handle.
54  */
55 typedef struct perl_config {
56         char    *cmd;
57         char    *persistent;
58         char    *xlat_name;
59 } PERL_CONFIG;
60
61 /*
62  * Some other things will be added in future 
63  */
64 typedef struct perl_inst {
65         PerlInterpreter         *perl;
66         HV                      *env_hv;
67         HV                      *result_hv;
68         PERL_CONFIG             *config;
69 } PERL_INST;
70
71 /*
72  *      A mapping of configuration file names to internal variables.
73  *
74  *      Note that the string is dynamically allocated, so it MUST
75  *      be freed.  When the configuration file parse re-reads the string,
76  *      it free's the old one, and strdup's the new one, placing the pointer
77  *      to the strdup'd string into 'config.string'.  This gets around
78  *      buffer over-flows.
79  */
80 static CONF_PARSER module_config[] = {
81   { "cmd",  PW_TYPE_STRING_PTR, offsetof(PERL_CONFIG,cmd), NULL,  NULL},
82   { "persistent", PW_TYPE_STRING_PTR, offsetof(PERL_CONFIG,persistent), NULL, NULL},
83   { NULL, -1, 0, NULL, NULL }           /* end the list */
84 };
85
86 /*
87  * man perlembed
88  */ 
89 EXTERN_C void boot_DynaLoader(pTHX_ CV* cv);
90
91 /*
92  *      Do any per-module initialization.  e.g. set up connections
93  *      to external databases, read configuration files, set up
94  *      dictionary entries, etc.
95  *
96  *      Try to avoid putting too much stuff in here - it's better to
97  *      do it in instantiate() where it is not global.
98  */
99 static int perl_init(void)
100 {
101         /*      
102          *      Everything's OK, return without an error.
103          */
104         return 0;       
105 }
106
107
108 /*
109  * man perlembed
110  */ 
111 static void xs_init(pTHX)
112 {
113         const char *file = __FILE__;
114         dXSUB_SYS; 
115
116         /* DynaLoader is a special case */
117         DEBUG("rlm_perl:: xs_init enter \n");
118         newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); 
119         DEBUG("rlm_perl:: xs_init leave \n");
120         
121 }
122
123 /*
124  * Perl xlat we use already running perl just recompile and store in memory 
125  * REMEMBER: each file will only be compiled once. Note that the process 
126  * will continue to grow for each file that it uses and is not compiled. 
127  * 
128  *  e.g.
129  *  %{perl:/usr/bin/test.pl %{User-Name}} will run /usr/bin/test.pl,
130  * give @ARGV with User-Name in $ARGV[0]
131  *  To return something just assign it to $main::ret_val
132  *
133  * test.pl:
134  *
135  * use strict;
136  * $!=0;
137  * my ($ret_val);
138  * print "ARGV[1]=$ARGV[1] ARGV[2]=$ARGV[2]\n";
139  * $main::ret_val = "B";
140  * die;
141  */ 
142
143 static int perl_xlat(void *instance, REQUEST *request, char *fmt, char *out, int freespace,
144                                         RADIUS_ESCAPE_STRING func)
145 {
146         PERL_INST       *inst=instance;
147         AV      *array_av;
148         SV      *ret_val;
149         char    *args[] = {"", DO_CLEAN, NULL};         
150         char    params[1024], *tmp_ptr, *ptr;
151         int     exitstatus=0,len;
152         I32     key;
153         STRLEN  n_a;
154         
155         /*
156          * Do an xlat on the provided string (nice recursive operation).
157         */
158         
159         if (!radius_xlat(params, sizeof(params), fmt, request, func))
160         {
161                 radlog(L_ERR, "rlm_perl: xlat failed.");
162                 return 0;
163         }
164         
165         PERL_SET_CONTEXT(inst->perl);
166         ptr = strtok(params, " ");
167
168         args[0] = ptr;
169         array_av = get_av("ARGV",0);
170         
171         ret_val = get_sv("main::ret_val",1);
172         
173         key = 0;
174         while ((tmp_ptr=strtok(NULL, " ")) != NULL) {
175                 av_store(array_av,key++,newSVpv(tmp_ptr,0));
176         } 
177         
178         PERL_SET_CONTEXT(inst->perl);
179         call_argv("Embed::Persistent::eval_file", G_DISCARD | G_EVAL, args);
180         
181         if (SvTRUE(ERRSV)) { 
182                 exitstatus = SvIV(perl_get_sv("!",FALSE));
183                 radlog(L_INFO, "perl_embed::perl_xlat exit status=%d, %s\n", exitstatus, SvPV(ERRSV,n_a));
184         } 
185         
186         /*
187          * Now get the variable we need 
188          * His name is $ret_val
189          */
190         
191         out = NULL;     
192         
193         if (SvTRUE(ret_val)) { 
194                 
195                 out =  SvPV(ret_val,n_a);
196                 len = strlen(out);
197                 
198                 radlog(L_INFO,"Len is %d , out is %s", len, out);
199                 
200                 if (len <= freespace)
201                         return len;
202         }
203         
204         return 0;
205          
206 }
207
208 /*
209  *      Do any per-module initialization that is separate to each
210  *      configured instance of the module.  e.g. set up connections
211  *      to external databases, read configuration files, set up
212  *      dictionary entries, etc.
213  *
214  *      If configuration information is given in the config section
215  *      that must be referenced in later calls, store a handle to it
216  *      in *instance otherwise put a null pointer there.
217  */
218 static int perl_instantiate(CONF_SECTION *conf, void **instance)
219 {
220         PERL_INST       *inst;  
221         char *embed[2], *xlat_name;
222         int exitstatus = 0;
223         
224         /*
225          *      Set up a storage area for instance data
226          */
227         
228         inst = rad_malloc(sizeof(PERL_INST));
229         memset(inst, 0, sizeof(PERL_INST));
230                 
231         inst->config = rad_malloc(sizeof(PERL_CONFIG));
232         memset(inst->config, 0, sizeof(PERL_CONFIG));
233         /*
234          *      If the configuration parameters can't be parsed, then
235          *      fail.
236          */
237         if (cf_section_parse(conf, inst->config, module_config) < 0) {
238                 free(inst->config);
239                 return -1;
240         }
241         
242         
243         /*
244          * Boyan
245          * Prepare perl instance 
246          * 
247          */ 
248         if((inst->perl = perl_alloc()) == NULL) {
249                 radlog(L_INFO, "no memory!");
250                 return -1;
251         }
252         
253         PERL_SET_CONTEXT(inst->perl);
254         perl_construct(inst->perl);
255         
256         PERL_SET_CONTEXT(inst->perl);
257
258         embed[0] = NULL;
259         embed[1] = inst->config->persistent;
260         
261         exitstatus = perl_parse(inst->perl, xs_init, 2, embed, NULL);
262
263         PERL_SET_CONTEXT(inst->perl);
264         if(!exitstatus) {
265                 exitstatus = perl_run(inst->perl);
266         } else {
267                 radlog(L_INFO,"perl_parse failed: %s not found or has syntax errors. \n", inst->config->persistent);
268                 return (-1);
269         }
270
271         inst->env_hv = perl_get_hv("ENV",0);
272         inst->result_hv = perl_get_hv("main::result",1);
273         
274         xlat_name = cf_section_name2(conf);
275         if (xlat_name == NULL) 
276                 xlat_name = cf_section_name1(conf);
277         if (xlat_name){ 
278                 inst->config->xlat_name = strdup(xlat_name);
279                 xlat_register(xlat_name, perl_xlat, inst); 
280         } 
281         *instance = inst;
282         
283         return 0;
284 }
285
286 /*
287  *  Boyan get the request and put them in perl hash 
288  *  which will be given to perl cmd
289  */
290 static void perl_env(VALUE_PAIR *vp, PERL_INST *inst)
291 {
292         char            buffer[256];
293
294         hv_clear(inst->env_hv);
295         hv_clear(inst->result_hv);
296
297         for ( ; vp != NULL; vp = vp->next) {
298                 int len;
299
300                 len = vp_prints_value(buffer, sizeof(buffer), vp, FALSE);
301
302                 hv_store(inst->env_hv, vp->name, strlen(vp->name),
303                          newSVpv(buffer, len),0);
304         }
305 }
306
307 /*
308  * return structs and status 0 OK 1 Not
309  * Boyan
310  */
311 static int rlmperl_call(void *instance, REQUEST *request)
312 {
313                 
314         PERL_INST       *inst = (PERL_INST *) instance;
315         SV              *res_sv;
316         VALUE_PAIR      *vp;
317         char            *key, *val, *ptr, *p;
318         char            *args[] = {NULL, DO_CLEAN, NULL};
319         char            answer[4096];
320         I32             key_len,i;
321         int             val_len;
322         int             exitstatus = 0, comma = 0;
323         STRLEN n_a;
324
325         args[0] = inst->config->cmd;
326         
327         perl_env(request->packet->vps, inst);
328         
329         for (i = hv_iterinit(inst->env_hv); i > 0; i--) {
330                 res_sv = hv_iternextsv(inst->env_hv, &key, &key_len);
331                 val = SvPV(res_sv,val_len);
332                 radlog(L_DBG, "ENV %s= %s", key, val); 
333         }
334         
335         PERL_SET_CONTEXT(inst->perl);
336         call_argv("Embed::Persistent::eval_file", G_DISCARD | G_EVAL, args);
337         
338         exitstatus = 0;
339
340         if (SvTRUE(ERRSV)) {
341                 exitstatus = SvIV(perl_get_sv("!",FALSE));;
342                 radlog(L_INFO, "exit status=%d, %s\n", exitstatus,
343                        SvPV(ERRSV,n_a));
344         }
345
346         ptr = answer;
347         PERL_SET_CONTEXT(inst->perl);
348         
349         for (i = hv_iterinit(inst->result_hv); i > 0; i--) {
350                 res_sv = hv_iternextsv(inst->result_hv,&key,&key_len);
351                 val = SvPV(res_sv,val_len);
352                 sprintf(ptr, "%s=\"%s\"\n", key, val); /* FIXME: snprintf */
353                 ptr += key_len + val_len + 4;
354         }
355         /* perl_free(inst->perl); */
356
357         *ptr='\0';
358         vp = NULL;
359         
360         for (p = answer; *p; p++) { 
361                 if (*p == '\n') {
362                         *p = comma ? ' ' : ',';
363                         p++; comma = 0;
364                 } 
365                 if (*p == ',') comma++; 
366         }
367         
368         /*
369          * Replace any trailing comma by a NUL.  
370          */                                
371         if (answer[strlen(answer) - 1] == ',') {
372                 answer[strlen(answer) - 1] = '\0';
373         }
374         radlog(L_INFO,"perl_embed :: value-pairs: %s", answer);
375
376         if (userparse(answer, &vp) < 0) {
377                 radlog(L_ERR, "perl_embed :: %s: unparsable reply", args[0]); 
378         } else {
379                 pairmove(&request->reply->vps, &vp);
380                 pairfree(&vp);
381         } 
382         return exitstatus;
383
384 }
385
386 /*
387  *      Find the named user in this modules database.  Create the set
388  *      of attribute-value pairs to check and reply with for this user
389  *      from the database. The authentication code only needs to check
390  *      the password, the rest is done here.
391  */
392 static int perl_authorize(void *instance, REQUEST *request)
393 {
394         int status = 0;
395         
396         radlog(L_INFO,"perl_embed :: Enter Authorize");
397
398         if ((status = rlmperl_call(instance, request)) == 0) {
399                 return RLM_MODULE_OK;
400         }
401         
402         return RLM_MODULE_FAIL;
403 }
404
405 /*
406  *      Authenticate the user with the given password.
407  */
408 static int perl_authenticate(void *instance, REQUEST *request)
409 {
410         int status = 0;
411
412         radlog(L_INFO,"perl_embed :: Enter Auth");
413
414         if ((status = rlmperl_call(instance, request)) == 0) {
415                 return RLM_MODULE_OK;
416         }
417
418         return RLM_MODULE_FAIL;
419 }
420
421
422 /*
423  *      Massage the request before recording it or proxying it
424  */
425 static int perl_preacct(void *instance, REQUEST *request)
426 {
427         int status = 0;
428
429         radlog(L_INFO,"mod_perl ::  Enter PreAccounting");
430         
431         if ((status = rlmperl_call(instance, request)) == 0) {
432                 return RLM_MODULE_OK;
433         }
434
435         return RLM_MODULE_FAIL;
436 }
437
438 /*
439  *      Write accounting information to this modules database.
440  */
441
442 static int perl_accounting(void *instance, REQUEST *request)
443 {
444         int status = 0;
445
446         radlog(L_INFO,"mod_perl ::  Enter Accounting");
447         
448         if ((status = (rlmperl_call(instance, request))) == 0) {
449                 return RLM_MODULE_OK;
450         }
451
452         return RLM_MODULE_FAIL;
453 }
454
455 /*
456  * Detach a instance free all ..
457  */
458 static int perl_detach(void *instance)
459 {
460         PERL_INST *inst=instance;
461         PERL_SET_CONTEXT(inst->perl);
462         perl_destruct(inst->perl);
463         PERL_SET_CONTEXT(inst->perl);
464         perl_free(inst->perl);
465
466         free(inst->config);     
467         hv_clear(inst->env_hv);
468         hv_clear(inst->result_hv);
469         free(inst);
470         free(instance);
471         return 0;
472 }
473
474 /*
475  *      The module name should be the only globally exported symbol.
476  *      That is, everything else should be 'static'.
477  *
478  *      If the module needs to temporarily modify it's instantiation
479  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
480  *      The server will then take care of ensuring that the module
481  *      is single-threaded.
482  */
483 module_t rlm_perl = {
484         "perl",                         /* Name */
485         RLM_TYPE_THREAD_UNSAFE,         /* type */
486         perl_init,                      /* initialization */
487         perl_instantiate,               /* instantiation */
488         {
489                 perl_authenticate,
490                 perl_authorize,
491                 perl_preacct,
492                 perl_accounting
493         },
494         perl_detach,                    /* detach */
495         NULL,                           /* destroy */
496 };