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