import from HEAD:
[freeradius.git] / src / modules / rlm_sql_log / rlm_sql_log.c
1 /*
2  *  rlm_sql_log.c       Append the SQL queries in a log file which
3  *                      is read later by the radsqlrelay program
4  *
5  *  Version:    $Id$
6  *
7  *  Author:     Nicolas Baradakis <nicolas.baradakis@cegetel.net>
8  *
9  *  Copyright (C) 2005 Cegetel
10  *
11  *  This program is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU General Public License
13  *  as published by the Free Software Foundation; either version 2
14  *  of the License, or (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
24  */
25
26 #include "autoconf.h"
27
28 #include <fcntl.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/stat.h>
32
33 #include "libradius.h"
34 #include "radiusd.h"
35 #include "modules.h"
36 #include "conffile.h"
37
38 static const char rcsid[] = "$Id$";
39
40 static int sql_log_instantiate(CONF_SECTION *conf, void **instance);
41 static int sql_log_detach(void *instance);
42 static int sql_log_accounting(void *instance, REQUEST *request);
43 static int sql_log_postauth(void *instance, REQUEST *request);
44
45 #define MAX_QUERY_LEN 4096
46
47 /*
48  *      Define a structure for our module configuration.
49  */
50 typedef struct rlm_sql_log_t {
51         char            *name;
52         char            *path;
53         char            *postauth_query;
54         char            *sql_user_name;
55         char            *allowed_chars;
56         CONF_SECTION    *conf_section;
57 } rlm_sql_log_t;
58
59 /*
60  *      A mapping of configuration file names to internal variables.
61  */
62 static const CONF_PARSER module_config[] = {
63         {"path", PW_TYPE_STRING_PTR,
64          offsetof(rlm_sql_log_t,path), NULL, "${radacctdir}/sql-relay"},
65         {"Post-Auth", PW_TYPE_STRING_PTR,
66          offsetof(rlm_sql_log_t,postauth_query), NULL, ""},
67         {"sql_user_name", PW_TYPE_STRING_PTR,
68          offsetof(rlm_sql_log_t,sql_user_name), NULL, ""},
69         {"safe-characters", PW_TYPE_STRING_PTR,
70          offsetof(rlm_sql_log_t,allowed_chars), NULL,
71         "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
72
73         { NULL, -1, 0, NULL, NULL }     /* end the list */
74 };
75
76 static char *allowed_chars = NULL;
77
78 /*
79  *      Do any per-module initialization that is separate to each
80  *      configured instance of the module.  e.g. set up connections
81  *      to external databases, read configuration files, set up
82  *      dictionary entries, etc.
83  *
84  *      If configuration information is given in the config section
85  *      that must be referenced in later calls, store a handle to it
86  *      in *instance otherwise put a null pointer there.
87  */
88 static int sql_log_instantiate(CONF_SECTION *conf, void **instance)
89 {
90         const char      *name;
91         rlm_sql_log_t   *inst;
92
93         /*
94          *      Set up a storage area for instance data.
95          */
96         inst = calloc(1, sizeof(rlm_sql_log_t));
97         if (inst == NULL) {
98                 radlog(L_ERR, "rlm_sql_log: Not enough memory");
99                 return -1;
100         }
101
102         /*
103          *      Get the name of the current section in the conf file.
104          */
105         name = cf_section_name2(conf);
106         if (name == NULL)
107                 name = cf_section_name1(conf);
108         if (name == NULL)
109                 name = "sql_log";
110         inst->name = strdup(name);
111
112         /*
113          *      If the configuration parameters can't be parsed,
114          *      then fail.
115          */
116         if (cf_section_parse(conf, inst, module_config) < 0) {
117                 radlog(L_ERR, "rlm_sql_log (%s): Unable to parse parameters",
118                        inst->name);
119                 sql_log_detach(inst);
120                 return -1;
121         }
122
123         inst->conf_section = conf;
124         allowed_chars = inst->allowed_chars;
125         *instance = inst;
126         return 0;
127 }
128
129 /*
130  *      Say goodbye to the cruel world.
131  */
132 static int sql_log_detach(void *instance)
133 {
134         int i;
135         char **p;
136         rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
137
138         if (inst->name) {
139                 free(inst->name);
140                 inst->name = NULL;
141         }
142
143         /*
144          *      Free up dynamically allocated string pointers.
145          */
146         for (i = 0; module_config[i].name != NULL; i++) {
147                 if (module_config[i].type != PW_TYPE_STRING_PTR) {
148                         continue;
149                 }
150
151                 /*
152                  *      Treat 'config' as an opaque array of bytes,
153                  *      and take the offset into it.  There's a
154                  *      (char*) pointer at that offset, and we want
155                  *      to point to it.
156                  */
157                 p = (char **) (((char *)inst) + module_config[i].offset);
158                 if (!*p) { /* nothing allocated */
159                         continue;
160                 }
161                 free(*p);
162                 *p = NULL;
163         }
164         allowed_chars = NULL;
165         free(inst);
166         return 0;
167 }
168
169 /*
170  *      Translate the SQL queries.
171  */
172 static int sql_escape_func(char *out, int outlen, const char *in)
173 {
174         int len = 0;
175
176         while (in[0]) {
177                 /*
178                  *      Non-printable characters get replaced with their
179                  *      mime-encoded equivalents.
180                  */
181                 if ((in[0] < 32) ||
182                     strchr(allowed_chars, *in) == NULL) {
183                         /*
184                          *      Only 3 or less bytes available.
185                          */
186                         if (outlen <= 3) {
187                                 break;
188                         }
189
190                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
191                         in++;
192                         out += 3;
193                         outlen -= 3;
194                         len += 3;
195                         continue;
196                 }
197
198                 /*
199                  *      Only one byte left.
200                  */
201                 if (outlen <= 1) {
202                         break;
203                 }
204
205                 /*
206                  *      Allowed character.
207                  */
208                 *out = *in;
209                 out++;
210                 in++;
211                 outlen--;
212                 len++;
213         }
214         *out = '\0';
215         return len;
216 }
217
218 /*
219  *      Add the 'SQL-User-Name' attribute to the packet.
220  */
221 static int sql_set_user(rlm_sql_log_t *inst, REQUEST *request, char *sqlusername, const char *username)
222 {
223         VALUE_PAIR *vp=NULL;
224         char tmpuser[MAX_STRING_LEN];
225
226         tmpuser[0] = '\0';
227         sqlusername[0] = '\0';
228
229         /* Remove any user attr we added previously */
230         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
231
232         if (username != NULL) {
233                 strNcpy(tmpuser, username, MAX_STRING_LEN);
234         } else if (inst->sql_user_name[0] != '\0') {
235                 radius_xlat(tmpuser, sizeof(tmpuser), inst->sql_user_name,
236                             request, NULL);
237         } else {
238                 return 0;
239         }
240
241         if (tmpuser[0] != '\0') {
242                 strNcpy(sqlusername, tmpuser, sizeof(tmpuser));
243                 DEBUG2("rlm_sql_log (%s): sql_set_user escaped user --> '%s'",
244                        inst->name, sqlusername);
245                 vp = pairmake("SQL-User-Name", sqlusername, 0);
246                 if (vp == NULL) {
247                         radlog(L_ERR, "%s", librad_errstr);
248                         return -1;
249                 }
250
251                 pairadd(&request->packet->vps, vp);
252                 return 0;
253         }
254         return -1;
255 }
256
257 /*
258  *      Replace %<whatever> in the query.
259  */
260 static int sql_xlat_query(rlm_sql_log_t *inst, REQUEST *request, const char *query, char *xlat_query, size_t len)
261 {
262         char    sqlusername[MAX_STRING_LEN];
263
264         /* If query is not defined, we stop here */
265         if (query[0] == '\0')
266                 return RLM_MODULE_NOOP;
267
268         /* Add attribute 'SQL-User-Name' */
269         if (sql_set_user(inst, request, sqlusername, NULL) <0) {
270                 radlog(L_ERR, "rlm_sql_log (%s): Couldn't add SQL-User-Name attribute",
271                                inst->name);
272                 return RLM_MODULE_FAIL;
273         }
274
275         /* Expand variables in the query */
276         xlat_query[0] = '\0';
277         radius_xlat(xlat_query, len, query, request, sql_escape_func);
278         if (xlat_query[0] == '\0') {
279                 radlog(L_ERR, "rlm_sql_log (%s): Couldn't xlat the query %s",
280                        inst->name, query);
281                 return RLM_MODULE_FAIL;
282         }
283
284         return RLM_MODULE_OK;
285 }
286
287 /*
288  *      The Perl version of radsqlrelay uses fcntl locks.
289  */
290 static int setlock(int fd)
291 {
292         struct flock fl;
293         memset(&fl, 0, sizeof(fl));
294         fl.l_start = 0;
295         fl.l_len = 0;
296         fl.l_type = F_WRLCK;
297         fl.l_whence = SEEK_SET;
298         return fcntl(fd, F_SETLKW, &fl);
299 }
300
301 /*
302  *      Write the line into file (with lock)
303  */
304 static int sql_log_write(rlm_sql_log_t *inst, REQUEST *request, const char *line)
305 {
306         int fd;
307         FILE *fp;
308         int locked = 0;
309         struct stat st;
310         char path[MAX_STRING_LEN];
311
312         path[0] = '\0';
313         radius_xlat(path, sizeof(path), inst->path, request, NULL);
314         if (path[0] == '\0')
315                 return RLM_MODULE_FAIL;
316
317         while (!locked) {
318                 if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
319                         radlog(L_ERR, "rlm_sql_log (%s): Couldn't open file %s: %s",
320                                inst->name, path, strerror(errno));
321                         return RLM_MODULE_FAIL;
322                 }
323                 if (setlock(fd) != 0) {
324                         radlog(L_ERR, "rlm_sql_log (%s): Couldn't lock file %s: %s",
325                                inst->name, path, strerror(errno));
326                         close(fd);
327                         return RLM_MODULE_FAIL;
328                 }
329                 if (fstat(fd, &st) != 0) {
330                         radlog(L_ERR, "rlm_sql_log (%s): Couldn't stat file %s: %s",
331                                inst->name, path, strerror(errno));
332                         close(fd);
333                         return RLM_MODULE_FAIL;
334                 }
335                 if (st.st_nlink == 0) {
336                         DEBUG("rlm_sql_log (%s): File %s removed by another program, retrying",
337                               inst->name, path);
338                         close(fd);
339                         continue;
340                 }
341                 locked = 1;
342         }
343
344         if ((fp = fdopen(fd, "a")) == NULL) {
345                 radlog(L_ERR, "rlm_sql_log (%s): Couldn't associate a stream with file %s: %s",
346                        inst->name, path, strerror(errno));
347                 close(fd);
348                 return RLM_MODULE_FAIL;
349         }
350         fputs(line, fp);
351         putc('\n', fp);
352         fclose(fp);     /* and unlock */
353         return RLM_MODULE_OK;
354 }
355
356 /*
357  *      Write accounting information to this module's database.
358  */
359 static int sql_log_accounting(void *instance, REQUEST *request)
360 {
361         int             ret;
362         char            querystr[MAX_QUERY_LEN];
363         char            *cfquery;
364         rlm_sql_log_t   *inst = (rlm_sql_log_t *)instance;
365         VALUE_PAIR      *pair;
366         DICT_VALUE      *dval;
367         CONF_PAIR       *cp;
368
369         DEBUG("rlm_sql_log (%s): Processing sql_log_accounting", inst->name);
370
371         /* Find the Acct Status Type. */
372         if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
373                 radlog(L_ERR, "rlm_sql_log (%s): Packet has no account status type",
374                        inst->name);
375                 return RLM_MODULE_INVALID;
376         }
377
378         /* Search the query in conf section of the module */
379         if ((dval = dict_valbyattr(PW_ACCT_STATUS_TYPE, pair->lvalue)) == NULL) {
380                 radlog(L_ERR, "rlm_sql_log (%s): Unsupported Acct-Status-Type = %d",
381                        inst->name, pair->lvalue);
382                 return RLM_MODULE_NOOP;
383         }
384         if ((cp = cf_pair_find(inst->conf_section, dval->name)) == NULL) {
385                 DEBUG("rlm_sql_log (%s): Couldn't find an entry %s in the config section",
386                       inst->name, dval->name);
387                 return RLM_MODULE_NOOP;
388         }
389         cfquery = cf_pair_value(cp);
390
391         /* Xlat the query */
392         ret = sql_xlat_query(inst, request, cfquery, querystr, sizeof(querystr));
393         if (ret != RLM_MODULE_OK)
394                 return ret;
395
396         /* Write query into sql-relay file */
397         return sql_log_write(inst, request, querystr);
398 }
399
400 /*
401  *      Write post-auth information to this module's database.
402  */
403 static int sql_log_postauth(void *instance, REQUEST *request)
404 {
405         int             ret;
406         char            querystr[MAX_QUERY_LEN];
407         rlm_sql_log_t   *inst = (rlm_sql_log_t *)instance;
408
409         DEBUG("rlm_sql_log (%s): Processing sql_log_postauth", inst->name);
410
411         /* Xlat the query */
412         ret = sql_xlat_query(inst, request, inst->postauth_query,
413                              querystr, sizeof(querystr));
414         if (ret != RLM_MODULE_OK)
415                 return ret;
416
417         /* Write query into sql-relay file */
418         return sql_log_write(inst, request, querystr);
419 }
420
421 /*
422  *      The module name should be the only globally exported symbol.
423  *      That is, everything else should be 'static'.
424  *
425  *      If the module needs to temporarily modify it's instantiation
426  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
427  *      The server will then take care of ensuring that the module
428  *      is single-threaded.
429  */
430 module_t rlm_sql_log = {
431         "sql_log",
432         RLM_TYPE_THREAD_SAFE,           /* type */
433         NULL,                           /* initialization */
434         sql_log_instantiate,            /* instantiation */
435         {
436                 NULL,                   /* authentication */
437                 NULL,                   /* authorization */
438                 NULL,                   /* preaccounting */
439                 sql_log_accounting,     /* accounting */
440                 NULL,                   /* checksimul */
441                 NULL,                   /* pre-proxy */
442                 NULL,                   /* post-proxy */
443                 sql_log_postauth        /* post-auth */
444         },
445         sql_log_detach,                 /* detach */
446         NULL,                           /* destroy */
447 };