2
* rlm_sql_log.c Append the SQL queries in a log file which
3
* is read later by the radsqlrelay program
5
* Version: $Id: rlm_sql_log.c,v 1.3.2.2 2005/12/12 17:44:35 nbk Exp $
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: rlm_sql_log.c,v 1.3.2.2 2005/12/12 17:44:35 nbk Exp $";
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 */