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
10 * Copyright 2006 The FreeRADIUS server project
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.
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.
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
27 #include <freeradius-devel/ident.h>
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/rad_assert.h>
37 static int sql_log_instantiate(CONF_SECTION *conf, void **instance);
38 static int sql_log_detach(void *instance);
39 static int sql_log_accounting(void *instance, REQUEST *request);
40 static int sql_log_postauth(void *instance, REQUEST *request);
42 #define MAX_QUERY_LEN 4096
45 * Define a structure for our module configuration.
47 typedef struct rlm_sql_log_t {
52 CONF_SECTION *conf_section;
56 * A mapping of configuration file names to internal variables.
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.-_: /"},
69 { NULL, -1, 0, NULL, NULL } /* end the list */
72 static char *allowed_chars = NULL;
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.
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.
84 static int sql_log_instantiate(CONF_SECTION *conf, void **instance)
89 * Set up a storage area for instance data.
91 inst = calloc(1, sizeof(rlm_sql_log_t));
93 radlog(L_ERR, "rlm_sql_log: Not enough memory");
98 * If the configuration parameters can't be parsed,
101 if (cf_section_parse(conf, inst, module_config) < 0) {
102 radlog(L_ERR, "rlm_sql_log: Unable to parse parameters");
103 sql_log_detach(inst);
107 inst->conf_section = conf;
108 allowed_chars = inst->allowed_chars;
114 * Say goodbye to the cruel world.
116 static int sql_log_detach(void *instance)
120 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
123 * Free up dynamically allocated string pointers.
125 for (i = 0; module_config[i].name != NULL; i++) {
126 if (module_config[i].type != PW_TYPE_STRING_PTR) {
131 * Treat 'config' as an opaque array of bytes,
132 * and take the offset into it. There's a
133 * (char*) pointer at that offset, and we want
136 p = (char **) (((char *)inst) + module_config[i].offset);
137 if (!*p) { /* nothing allocated */
143 allowed_chars = NULL;
149 * Translate the SQL queries.
151 static size_t sql_escape_func(char *out, size_t outlen, const char *in)
157 * Non-printable characters get replaced with their
158 * mime-encoded equivalents.
161 strchr(allowed_chars, *in) == NULL) {
163 * Only 3 or less bytes available.
169 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
178 * Only one byte left.
198 * Add the 'SQL-User-Name' attribute to the packet.
200 static int sql_set_user(rlm_sql_log_t *inst, REQUEST *request, char *sqlusername, const char *username)
203 char tmpuser[MAX_STRING_LEN];
206 sqlusername[0] = '\0';
208 rad_assert(request != NULL);
209 rad_assert(request->packet != NULL);
211 /* Remove any user attr we added previously */
212 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
214 if (username != NULL) {
215 strlcpy(tmpuser, username, MAX_STRING_LEN);
216 } else if (inst->sql_user_name[0] != '\0') {
217 radius_xlat(tmpuser, sizeof(tmpuser), inst->sql_user_name,
223 if (tmpuser[0] != '\0') {
224 strlcpy(sqlusername, tmpuser, sizeof(tmpuser));
225 RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
226 vp = pairmake("SQL-User-Name", sqlusername, 0);
228 radlog(L_ERR, "%s", fr_strerror());
232 pairadd(&request->packet->vps, vp);
239 * Replace %<whatever> in the query.
241 static int sql_xlat_query(rlm_sql_log_t *inst, REQUEST *request, const char *query, char *xlat_query, size_t len)
243 char sqlusername[MAX_STRING_LEN];
245 /* If query is not defined, we stop here */
246 if (query[0] == '\0')
247 return RLM_MODULE_NOOP;
249 /* Add attribute 'SQL-User-Name' */
250 if (sql_set_user(inst, request, sqlusername, NULL) <0) {
251 radlog_request(L_ERR, 0, request,
252 "Couldn't add SQL-User-Name attribute");
253 return RLM_MODULE_FAIL;
256 /* Expand variables in the query */
257 xlat_query[0] = '\0';
258 radius_xlat(xlat_query, len, query, request, sql_escape_func);
259 if (xlat_query[0] == '\0') {
260 radlog_request(L_ERR, 0, request, "Couldn't xlat the query %s",
262 return RLM_MODULE_FAIL;
265 return RLM_MODULE_OK;
269 * The Perl version of radsqlrelay uses fcntl locks.
271 static int setlock(int fd)
274 memset(&fl, 0, sizeof(fl));
278 fl.l_whence = SEEK_SET;
279 return fcntl(fd, F_SETLKW, &fl);
283 * Write the line into file (with lock)
285 static int sql_log_write(rlm_sql_log_t *inst, REQUEST *request, const char *line)
291 char path[MAX_STRING_LEN];
294 radius_xlat(path, sizeof(path), inst->path, request, NULL);
295 if (path[0] == '\0') {
296 return RLM_MODULE_FAIL;
300 if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
301 radlog_request(L_ERR, 0, request, "Couldn't open file %s: %s",
302 path, strerror(errno));
303 return RLM_MODULE_FAIL;
305 if (setlock(fd) != 0) {
306 radlog_request(L_ERR, 0, request, "Couldn't lock file %s: %s",
307 path, strerror(errno));
309 return RLM_MODULE_FAIL;
311 if (fstat(fd, &st) != 0) {
312 radlog_request(L_ERR, 0, request, "Couldn't stat file %s: %s",
313 path, strerror(errno));
315 return RLM_MODULE_FAIL;
317 if (st.st_nlink == 0) {
318 RDEBUG("File %s removed by another program, retrying",
326 if ((fp = fdopen(fd, "a")) == NULL) {
327 radlog_request(L_ERR, 0, request, "Couldn't associate a stream with file %s: %s",
328 path, strerror(errno));
330 return RLM_MODULE_FAIL;
334 fclose(fp); /* and unlock */
335 return RLM_MODULE_OK;
339 * Write accounting information to this module's database.
341 static int sql_log_accounting(void *instance, REQUEST *request)
344 char querystr[MAX_QUERY_LEN];
346 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
351 rad_assert(request != NULL);
352 rad_assert(request->packet != NULL);
354 RDEBUG("Processing sql_log_accounting");
356 /* Find the Acct Status Type. */
357 if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
358 radlog_request(L_ERR, 0, request, "Packet has no account status type");
359 return RLM_MODULE_INVALID;
362 /* Search the query in conf section of the module */
363 if ((dval = dict_valbyattr(PW_ACCT_STATUS_TYPE, pair->vp_integer)) == NULL) {
364 radlog_request(L_ERR, 0, request, "Unsupported Acct-Status-Type = %d",
366 return RLM_MODULE_NOOP;
368 if ((cp = cf_pair_find(inst->conf_section, dval->name)) == NULL) {
369 RDEBUG("Couldn't find an entry %s in the config section",
371 return RLM_MODULE_NOOP;
373 cfquery = cf_pair_value(cp);
376 ret = sql_xlat_query(inst, request, cfquery, querystr, sizeof(querystr));
377 if (ret != RLM_MODULE_OK)
380 /* Write query into sql-relay file */
381 return sql_log_write(inst, request, querystr);
385 * Write post-auth information to this module's database.
387 static int sql_log_postauth(void *instance, REQUEST *request)
390 char querystr[MAX_QUERY_LEN];
391 rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
393 rad_assert(request != NULL);
395 RDEBUG("Processing sql_log_postauth");
398 ret = sql_xlat_query(inst, request, inst->postauth_query,
399 querystr, sizeof(querystr));
400 if (ret != RLM_MODULE_OK)
403 /* Write query into sql-relay file */
404 return sql_log_write(inst, request, querystr);
408 * The module name should be the only globally exported symbol.
409 * That is, everything else should be 'static'.
411 * If the module needs to temporarily modify it's instantiation
412 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
413 * The server will then take care of ensuring that the module
414 * is single-threaded.
416 module_t rlm_sql_log = {
419 RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
420 sql_log_instantiate, /* instantiation */
421 sql_log_detach, /* detach */
423 NULL, /* authentication */
424 NULL, /* authorization */
425 NULL, /* preaccounting */
426 sql_log_accounting, /* accounting */
427 NULL, /* checksimul */
428 NULL, /* pre-proxy */
429 NULL, /* post-proxy */
430 sql_log_postauth /* post-auth */