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