2 * rlm_sql_log.c Append the SQL queries in a log file which
3 * is read later by the radsqlrelay program
7 * Author: Nicolas Baradakis <nicolas.baradakis@cegetel.net>
9 * Copyright (C) 2005 Cegetel
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.
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.
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
33 #include "libradius.h"
38 static const char rcsid[] = "$Id$";
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);
45 #define MAX_QUERY_LEN 4096
48 * Define a structure for our module configuration.
50 typedef struct rlm_sql_log_t {
56 CONF_SECTION *conf_section;
60 * A mapping of configuration file names to internal variables.
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.-_: /"},
73 { NULL, -1, 0, NULL, NULL } /* end the list */
76 static char *allowed_chars = NULL;
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.
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.
88 static int sql_log_instantiate(CONF_SECTION *conf, void **instance)
94 * Set up a storage area for instance data.
96 inst = calloc(1, sizeof(rlm_sql_log_t));
98 radlog(L_ERR, "rlm_sql_log: Not enough memory");
103 * Get the name of the current section in the conf file.
105 name = cf_section_name2(conf);
107 name = cf_section_name1(conf);
110 inst->name = strdup(name);
113 * If the configuration parameters can't be parsed,
116 if (cf_section_parse(conf, inst, module_config) < 0) {
117 radlog(L_ERR, "rlm_sql_log (%s): Unable to parse parameters",
119 sql_log_detach(inst);
123 inst->conf_section = conf;
124 allowed_chars = inst->allowed_chars;
130 * Say goodbye to the cruel world.
132 static int sql_log_detach(void *instance)
136 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
144 * Free up dynamically allocated string pointers.
146 for (i = 0; module_config[i].name != NULL; i++) {
147 if (module_config[i].type != PW_TYPE_STRING_PTR) {
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
157 p = (char **) (((char *)inst) + module_config[i].offset);
158 if (!*p) { /* nothing allocated */
164 allowed_chars = NULL;
170 * Translate the SQL queries.
172 static int sql_escape_func(char *out, int outlen, const char *in)
178 * Non-printable characters get replaced with their
179 * mime-encoded equivalents.
182 strchr(allowed_chars, *in) == NULL) {
184 * Only 3 or less bytes available.
190 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
199 * Only one byte left.
219 * Add the 'SQL-User-Name' attribute to the packet.
221 static int sql_set_user(rlm_sql_log_t *inst, REQUEST *request, char *sqlusername, const char *username)
224 char tmpuser[MAX_STRING_LEN];
227 sqlusername[0] = '\0';
229 /* Remove any user attr we added previously */
230 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
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,
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);
247 radlog(L_ERR, "%s", librad_errstr);
251 pairadd(&request->packet->vps, vp);
258 * Replace %<whatever> in the query.
260 static int sql_xlat_query(rlm_sql_log_t *inst, REQUEST *request, const char *query, char *xlat_query, size_t len)
262 char sqlusername[MAX_STRING_LEN];
264 /* If query is not defined, we stop here */
265 if (query[0] == '\0')
266 return RLM_MODULE_NOOP;
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",
272 return RLM_MODULE_FAIL;
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",
281 return RLM_MODULE_FAIL;
284 return RLM_MODULE_OK;
288 * The Perl version of radsqlrelay uses fcntl locks.
290 static int setlock(int fd)
293 memset(&fl, 0, sizeof(fl));
297 fl.l_whence = SEEK_SET;
298 return fcntl(fd, F_SETLKW, &fl);
302 * Write the line into file (with lock)
304 static int sql_log_write(rlm_sql_log_t *inst, REQUEST *request, const char *line)
310 char path[MAX_STRING_LEN];
313 radius_xlat(path, sizeof(path), inst->path, request, NULL);
315 return RLM_MODULE_FAIL;
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;
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));
327 return RLM_MODULE_FAIL;
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));
333 return RLM_MODULE_FAIL;
335 if (st.st_nlink == 0) {
336 DEBUG("rlm_sql_log (%s): File %s removed by another program, retrying",
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));
348 return RLM_MODULE_FAIL;
352 fclose(fp); /* and unlock */
353 return RLM_MODULE_OK;
357 * Write accounting information to this module's database.
359 static int sql_log_accounting(void *instance, REQUEST *request)
362 char querystr[MAX_QUERY_LEN];
364 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
369 DEBUG("rlm_sql_log (%s): Processing sql_log_accounting", inst->name);
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",
375 return RLM_MODULE_INVALID;
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;
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;
389 cfquery = cf_pair_value(cp);
392 ret = sql_xlat_query(inst, request, cfquery, querystr, sizeof(querystr));
393 if (ret != RLM_MODULE_OK)
396 /* Write query into sql-relay file */
397 return sql_log_write(inst, request, querystr);
401 * Write post-auth information to this module's database.
403 static int sql_log_postauth(void *instance, REQUEST *request)
406 char querystr[MAX_QUERY_LEN];
407 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
409 DEBUG("rlm_sql_log (%s): Processing sql_log_postauth", inst->name);
412 ret = sql_xlat_query(inst, request, inst->postauth_query,
413 querystr, sizeof(querystr));
414 if (ret != RLM_MODULE_OK)
417 /* Write query into sql-relay file */
418 return sql_log_write(inst, request, querystr);
422 * The module name should be the only globally exported symbol.
423 * That is, everything else should be 'static'.
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.
430 module_t rlm_sql_log = {
432 RLM_TYPE_THREAD_SAFE, /* type */
433 NULL, /* initialization */
434 sql_log_instantiate, /* instantiation */
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 */
445 sql_log_detach, /* detach */