PageRenderTime 21ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/modules/rlm_sql_log/rlm_sql_log.c

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