2 * sql.c rlm_sql - FreeRADIUS SQL Module
3 * Main code directly taken from ICRADIUS
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Copyright 2000 The FreeRADIUS server project
22 * Copyright 2000 Mike Machado <mike@innercite.com>
23 * Copyright 2000 Alan DeKok <aland@ox.org>
27 #include <sys/types.h>
28 #include <sys/socket.h>
33 #include <netinet/in.h>
54 /*************************************************************************
56 * Function: sql_init_socket
58 * Purpose: Connect to the sql server
60 *************************************************************************/
61 int sql_init_socketpool(SQL_INST *inst) {
69 for (i = 0; i < inst->config->num_sql_socks; i++) {
70 if ((sqlsocket = sql_create_socket(inst)) == NULL) {
71 radlog(L_CONS | L_ERR, "rlm_sql: Failed to connect sqlsocket %d", i);
76 sqlsocket->semaphore = (sem_t *)malloc(sizeof(sem_t));
77 sem_init(sqlsocket->semaphore, 0, SQLSOCK_UNLOCKED);
79 sqlsocket->in_use = 0;
81 sqlsocket->next = inst->sqlpool;
82 inst->sqlpool = sqlsocket;
89 /*************************************************************************
91 * Function: sql_poolfree
93 * Purpose: Clean up and free sql pool
95 *************************************************************************/
96 void sql_poolfree(SQL_INST *inst) {
100 for (cur = inst->sqlpool; cur; cur = cur->next) {
101 sql_close_socket(cur);
104 pthread_mutex_destroy(inst->lock);
105 pthread_cond_destroy(inst->notfull);
110 /*************************************************************************
112 * Function: sql_close_socket
114 * Purpose: Close and free a sql sqlsocket
116 *************************************************************************/
117 int sql_close_socket(SQLSOCK *sqlsocket) {
119 radlog(L_DBG,"rlm_sql: Closing sqlsocket %d", sqlsocket->id);
120 sql_close(sqlsocket);
122 sem_destroy(sqlsocket->semaphore);
129 /*************************************************************************
131 * Function: sql_get_socket
133 * Purpose: Return a SQL sqlsocket from the connection pool
135 *************************************************************************/
136 SQLSOCK *sql_get_socket(SQL_INST *inst) {
142 pthread_mutex_lock(inst->lock);
144 while (inst->used == inst->config->num_sql_socks) {
145 radlog(L_DBG, "rlm_sql: Waiting for open sql socket");
147 pthread_cond_wait(inst->notfull, inst->lock);
149 /* FIXME: Subsecond sleep needed here */
154 for (cur = inst->sqlpool; cur; cur = cur->next) {
156 if (sem_trywait(cur->semaphore) == 0) {
158 if (cur->in_use == SQLSOCK_UNLOCKED) {
162 pthread_mutex_unlock(inst->lock);
164 cur->in_use = SQLSOCK_LOCKED;
166 radlog(L_DBG,"rlm_sql: Reserved sql socket id: %d", cur->id);
172 pthread_mutex_unlock(inst->lock);
175 /* Should never get here, but what the hey */
179 /*************************************************************************
181 * Function: sql_release_socket
183 * Purpose: Frees a SQL sqlsocket back to the connection pool
185 *************************************************************************/
186 int sql_release_socket(SQL_INST *inst, SQLSOCK *sqlsocket) {
189 pthread_mutex_lock(inst->lock);
193 sem_post(sqlsocket->semaphore);
195 sqlsocket->in_use = 0;
198 radlog(L_DBG,"rlm_sql: Released sql socket id: %d", sqlsocket->id);
201 pthread_mutex_unlock(inst->lock);
202 pthread_cond_signal(inst->notfull);
209 /*************************************************************************
211 * Function: sql_save_acct
213 * Purpose: Write data from the sqlrecord structure to the database
215 *************************************************************************/
217 int sql_save_acct(SQL_INST *inst, SQLSOCK *sqlsocket, SQLACCTREC *sqlrecord) {
224 #ifdef NT_DOMAIN_HACK
226 char newname[AUTH_STRING_LEN];
229 acctunique = strlen(sqlrecord->AcctUniqueId);
231 if(inst->config->sqltrace) {
232 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
233 radlog(L_ERR, "rlm_sql: Couldn't open file %s", inst->config->tracefile);
235 #if defined(F_LOCK) && !defined(BSD)
236 (void) lockf((int) sqlfile, (int) F_LOCK, (off_t) SQL_LOCK_LEN);
238 (void) flock(sqlfile, SQL_LOCK_EX);
243 #ifdef NT_DOMAIN_HACK
245 * Windows NT machines often authenticate themselves as
246 * NT_DOMAIN\username. Try to be smart about this.
248 * FIXME: should we handle this as a REALM ?
250 if ((ptr = strchr(sqlrecord->UserName, '\\')) != NULL) {
251 strncpy(newname, ptr + 1, sizeof(newname));
252 newname[sizeof(newname) - 1] = 0;
253 strcpy(sqlrecord->UserName, newname);
259 if (sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_ON ||
260 sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_OFF) {
261 radlog(L_INFO, "rlm_sql: Portmaster %s rebooted at %s", sqlrecord->NASIPAddress,
262 sqlrecord->AcctTimeStamp);
265 * The Terminal server informed us that it was rebooted
266 * * STOP all records from this NAS
270 "UPDATE %s SET AcctStopTime='%s', AcctSessionTime=unix_timestamp('%s') - unix_timestamp(AcctStartTime), AcctTerminateCause='%s', AcctStopDelay = %ld WHERE AcctSessionTime=0 AND AcctStopTime=0 AND NASIPAddress= '%s' AND AcctStartTime <= '%s'",
271 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
272 sqlrecord->AcctTimeStamp, sqlrecord->AcctTerminateCause,
273 sqlrecord->AcctDelayTime, sqlrecord->NASIPAddress,
274 sqlrecord->AcctTimeStamp);
276 if (sql_query(inst, sqlsocket, querystr) < 0)
277 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting after NAS reboot - %s",
278 sql_error(sqlsocket));
279 sql_finish_query(sqlsocket);
282 fputs(querystr, sqlfile);
283 fputs(";\n", sqlfile);
289 if (sqlrecord->AcctStatusTypeId == PW_STATUS_ALIVE) {
291 * Use acct unique session identifier if present
294 sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctUniqueId = '%s'",
295 inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
296 sqlrecord->AcctUniqueId);
299 sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress= '%s'",
300 inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
301 sqlrecord->AcctSessionId, sqlrecord->UserName,
302 sqlrecord->NASIPAddress);
305 if (sql_query(inst, sqlsocket, querystr) < 0)
306 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s",
307 sql_error(sqlsocket));
308 sql_finish_query(sqlsocket);
311 fputs(querystr, sqlfile);
312 fputs(";\n", sqlfile);
321 if (sqlrecord->AcctStatusTypeId == PW_STATUS_START) {
324 * Insert new record with blank stop time until stop record is got
326 snprintf(querystr, 2048,
327 "INSERT INTO %s VALUES (0, '%s', '%s', '%s', '%s', '%s', %ld, '%s', '%s', 0, 0, '%s', '%s', '', 0, 0, '%s', '%s', '', '%s', '%s', '%s', %ld, 0)",
328 inst->config->sql_acct_table, sqlrecord->AcctSessionId,
329 sqlrecord->AcctUniqueId, sqlrecord->UserName,
330 sqlrecord->Realm, sqlrecord->NASIPAddress,
331 sqlrecord->NASPortId, sqlrecord->NASPortType,
332 sqlrecord->AcctTimeStamp, sqlrecord->AcctAuthentic,
333 sqlrecord->ConnectInfo, sqlrecord->CalledStationId,
334 sqlrecord->CallingStationId, sqlrecord->ServiceType,
335 sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
336 sqlrecord->AcctDelayTime);
338 if (sql_query(inst, sqlsocket, querystr) < 0) {
339 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting START record - %s",
340 sql_error(sqlsocket));
343 * We failed the insert above. It's probably because
344 * the stop record came before the start. We try an
345 * update here to be sure
348 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctUniqueId = '%s'",
349 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
350 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
351 sqlrecord->AcctUniqueId);
353 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress = '%s'",
354 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
355 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
356 sqlrecord->AcctSessionId, sqlrecord->UserName,
357 sqlrecord->NASIPAddress);
359 if (sql_query(inst, sqlsocket, querystr) < 0)
360 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting START record - %s",
361 sql_error(sqlsocket));
364 sql_finish_query(sqlsocket);
372 * Set stop time on matching record with start time
375 snprintf(querystr, 2048,
376 "UPDATE %s SET AcctStopTime = '%s', AcctSessionTime = '%lu', AcctInputOctets = '%lu', AcctOutputOctets = '%lu', AcctTerminateCause = '%s', AcctStopDelay = %ld, ConnectInfo_stop = '%s' WHERE AcctUniqueId = '%s'",
377 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
378 sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
379 sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
380 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
381 sqlrecord->AcctUniqueId);
384 snprintf(querystr, 2048,
385 "UPDATE %s SET AcctStopTime = '%s', AcctSessionTime = '%lu', AcctInputOctets = '%lu', AcctOutputOctets = '%lu', AcctTerminateCause = '%s', AcctStopDelay = %ld, ConnectInfo_stop = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress = '%s'",
386 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
387 sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
388 sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
389 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
390 sqlrecord->AcctSessionId, sqlrecord->UserName,
391 sqlrecord->NASIPAddress);
395 if (sql_query(inst, sqlsocket, querystr) < 0)
396 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting STOP record - %s",
397 sql_error(sqlsocket));
398 sql_finish_query(sqlsocket);
402 * If our update above didn't match anything
403 * we assume it's because we haven't seen a
404 * matching Start record. So we have to
405 * insert this stop rather than do an update
407 num = sql_affected_rows(sqlsocket);
410 #ifdef CISCO_ACCOUNTING_HACK
412 * If stop but zero session length AND no previous
413 * session found, drop it as in invalid packet
414 * This is to fix CISCO's aaa from filling our
415 * table with bogus crap
417 if (sqlrecord->AcctSessionTime <= 0) {
418 radlog(L_ERR, "rlm_sql: Invalid STOP record. [%s] STOP record but zero session length? (nas %s)",
419 sqlrecord->UserName, sqlrecord->NASIPAddress);
425 * Insert record with no start time until matching start record comes
427 snprintf(querystr, 2048,
428 "INSERT INTO %s VALUES (0, '%s', '%s', '%s', '%s', '%s', %ld, '%s', 0, '%s', '%lu', '%s', '', '%s', '%lu', '%lu', '%s', '%s', '%s', '%s', '%s', '%s', 0, %ld)",
429 inst->config->sql_acct_table, sqlrecord->AcctSessionId,
430 sqlrecord->AcctUniqueId, sqlrecord->UserName,
431 sqlrecord->Realm, sqlrecord->NASIPAddress,
432 sqlrecord->NASPortId, sqlrecord->NASPortType,
433 sqlrecord->AcctTimeStamp, sqlrecord->AcctSessionTime,
434 sqlrecord->AcctAuthentic, sqlrecord->ConnectInfo,
435 sqlrecord->AcctInputOctets, sqlrecord->AcctOutputOctets,
436 sqlrecord->CalledStationId, sqlrecord->CallingStationId,
437 sqlrecord->AcctTerminateCause, sqlrecord->ServiceType,
438 sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
439 sqlrecord->AcctDelayTime);
441 if (sql_query(inst, sqlsocket, querystr) < 0)
442 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting STOP record - %s",
443 sql_error(sqlsocket));
444 sql_finish_query(sqlsocket);
449 fputs(querystr, sqlfile);
450 fputs(";\n", sqlfile);
460 /*************************************************************************
462 * Function: sql_userparse
464 * Purpose: Read entries from the database and fill VALUE_PAIR structures
466 *************************************************************************/
467 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int mode) {
470 VALUE_PAIR *pair, *check;
473 if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
474 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
479 * If attribute is already there, skip it because we checked usercheck first
480 * and we want user settings to over ride group settings
482 if ((check = pairfind(*first_pair, attr->attr)) != NULL &&
483 #if defined( BINARY_FILTERS )
484 attr->type != PW_TYPE_ABINARY &&
486 mode == PW_VP_GROUPDATA) return 0;
488 pair = pairmake(row[2], row[3], T_OP_CMP_EQ);
489 pairadd(first_pair, pair);
495 /*************************************************************************
497 * Function: sql_getvpdata
499 * Purpose: Get any group check or reply pairs
501 *************************************************************************/
502 int sql_getvpdata(SQL_INST *inst, SQLSOCK *sqlsocket, char *table, VALUE_PAIR ** vp, char *user, int mode)
507 char username[AUTH_STRING_LEN * 2 + 1];
512 if (strlen(user) > AUTH_STRING_LEN)
513 length = AUTH_STRING_LEN;
515 length = strlen(user);
518 * FIXME CHECK user for weird charactors!!
520 sql_escape_string(username, user, length);
522 if (mode == PW_VP_USERDATA) {
523 if (inst->config->sensitiveusername)
524 sprintf(authstr, "STRCMP(Username, '%s') = 0", username);
526 sprintf(authstr, "UserName = '%s'", username);
527 sprintf(querystr, "SELECT * FROM %s WHERE %s ORDER BY id", table,
529 } else if (mode == PW_VP_GROUPDATA) {
530 if (inst->config->sensitiveusername)
531 sprintf(authstr, "STRCMP(%s.Username, '%s') = 0",
532 inst->config->sql_usergroup_table, username);
534 sprintf(authstr, "%s.UserName = '%s'", inst->config->sql_usergroup_table,
537 "SELECT %s.* FROM %s, %s WHERE %s AND %s.GroupName = %s.GroupName ORDER BY %s.id",
538 table, table, inst->config->sql_usergroup_table, authstr,
539 inst->config->sql_usergroup_table, table, table);
540 } else if (mode == PW_VP_REALMDATA)
542 "SELECT %s.* FROM %s, %s WHERE %s.RealmName = '%s' AND %s.GroupName = %s.GroupName ORDER BY %s.id",
543 table, table, inst->config->sql_realmgroup_table,
544 inst->config->sql_realmgroup_table, username,
545 inst->config->sql_realmgroup_table, table, table);
546 sql_select_query(inst, sqlsocket, querystr);
547 rows = sql_num_rows(sqlsocket);
548 while ((row = sql_fetch_row(sqlsocket))) {
550 if (sql_userparse(vp, row, mode) != 0) {
551 radlog(L_ERR | L_CONS, "rlm_sql: Error getting data from database");
552 sql_finish_select_query(sqlsocket);
556 sql_finish_select_query(sqlsocket);
570 /*************************************************************************
572 * Function: sql_check_ts
574 * Purpose: Checks the terminal server for a spacific login entry
576 *************************************************************************/
577 static int sql_check_ts(SQL_ROW row) {
584 void (*handler) (int);
589 if ((nas = nas_find(ip_addr(row[4]))) == NULL) {
590 radlog(L_ERR, "rlm_sql: unknown NAS [%s]", row[4]);
597 handler = signal(SIGCHLD, SIG_DFL);
598 if ((pid = fork()) < 0) {
599 radlog(L_ERR, "rlm_sql: fork: %s", strerror(errno));
600 signal(SIGCHLD, handler);
606 * Parent - Wait for checkrad to terminate.
607 * We timeout in 10 seconds.
610 signal(SIGALRM, alrm_handler);
612 while ((e = waitpid(pid, &st, 0)) != pid)
613 if (e < 0 && (errno != EINTR || got_alrm))
616 signal(SIGCHLD, handler);
621 radlog(L_ERR, "rlm_sql: Check-TS: timeout waiting for checkrad");
625 radlog(L_ERR, "rlm_sql: Check-TS: unknown error in waitpid()");
628 return WEXITSTATUS(st);
632 * Child - exec checklogin with the right parameters.
634 for (n = 32; n >= 3; n--)
637 sprintf(session_id, "%.8s", row[1]);
640 execl(CHECKRAD2, "checkrad", nas->nastype, row[4], row[5],
641 row[2], session_id, NULL);
642 if (errno == ENOENT) {
644 execl(CHECKRAD1, "checklogin", nas->nastype, row[4], row[5],
645 row[2], session_id, NULL);
647 radlog(L_ERR, "rlm_sql: Check-TS: exec %s: %s", s, strerror(errno));
650 * Exit - 2 means "some error occured".
657 /*************************************************************************
659 * Function: sql_check_multi
661 * Purpose: Check radius accounting for duplicate logins
663 *************************************************************************/
664 int sql_check_multi(SQL_INST *inst, SQLSOCK *sqlsocket, char *name, VALUE_PAIR * request, int maxsimul) {
674 if (inst->config->sensitiveusername)
675 sprintf(authstr, "STRCMP(UserName, '%s') = 0", name);
677 sprintf(authstr, "UserName = '%s'", name);
678 sprintf(querystr, "SELECT COUNT(*) FROM %s WHERE %s AND AcctStopTime = 0",
679 inst->config->sql_acct_table, authstr);
680 sql_select_query(inst, sqlsocket, querystr);
681 row = sql_fetch_row(sqlsocket);
682 count = atoi(row[0]);
683 sql_finish_select_query(sqlsocket);
685 if (count < maxsimul)
689 * * Setup some stuff, like for MPP detection.
691 if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
692 ipno = htonl(fra->lvalue);
695 sprintf(querystr, "SELECT * FROM %s WHERE %s AND AcctStopTime = 0",
696 inst->config->sql_acct_table, authstr);
697 sql_select_query(inst, sqlsocket, querystr);
698 while ((row = sql_fetch_row(sqlsocket))) {
699 int check = sql_check_ts(row);
704 if (ipno && atoi(row[19]) == ipno)
707 } else if (check == 2)
708 radlog(L_ERR, "rlm_sql: Problem with checkrad [%s] (from nas %s)", name, row[4]);
711 * False record - zap it
714 if (inst->config->deletestalesessions) {
717 radlog(L_ERR, "rlm_sql: Deleteing stale session [%s] (from nas %s/%s)", row[2],
719 sqlsocket1 = sql_get_socket(inst);
720 sprintf(querystr, "DELETE FROM %s WHERE RadAcctId = '%s'",
721 inst->config->sql_acct_table, row[0]);
722 sql_query(inst, sqlsocket1, querystr);
723 sql_finish_query(sqlsocket1);
724 sql_release_socket(inst, sqlsocket1);
728 sql_finish_select_query(sqlsocket);
730 return (count < maxsimul) ? 0 : mpp;