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);
106 /*************************************************************************
108 * Function: sql_close_socket
110 * Purpose: Close and free a sql sqlsocket
112 *************************************************************************/
113 int sql_close_socket(SQLSOCK *sqlsocket) {
115 radlog(L_DBG,"rlm_sql: Closing sqlsocket %d", sqlsocket->id);
116 sql_close(sqlsocket);
122 /*************************************************************************
124 * Function: sql_get_socket
126 * Purpose: Return a SQL sqlsocket from the connection pool
128 *************************************************************************/
129 SQLSOCK *sql_get_socket(SQL_INST *inst) {
135 pthread_mutex_lock(inst->lock);
137 while (inst->used == inst->config->num_sql_socks) {
138 radlog(L_DBG, "rlm_sql: Waiting for open sql socket");
140 pthread_cond_wait(inst->notfull, inst->lock);
142 /* FIXME: Subsecond sleep needed here */
147 for (cur = inst->sqlpool; cur; cur = cur->next) {
149 if (sem_trywait(cur->semaphore) == 0) {
151 if (cur->in_use == SQLSOCK_UNLOCKED) {
155 pthread_mutex_unlock(inst->lock);
157 cur->in_use = SQLSOCK_LOCKED;
159 radlog(L_DBG,"rlm_sql: Reserved sql socket id: %d", cur->id);
165 pthread_mutex_unlock(inst->lock);
168 /* Should never get here, but what the hey */
172 /*************************************************************************
174 * Function: sql_release_socket
176 * Purpose: Frees a SQL sqlsocket back to the connection pool
178 *************************************************************************/
179 int sql_release_socket(SQL_INST *inst, SQLSOCK *sqlsocket) {
185 gettimeofday(&tv, NULL);
186 sprintf(buff, "%ld.%2ld", tv.tv_sec, tv.tv_usec);
187 end = strtod(buff, NULL);
188 sprintf(buff, "%ld %2.0ld", sqlsocket->tv.tv_sec, sqlsocket->tv.tv_usec);
189 start = strtod(buff, NULL);
190 radlog(L_DBG,"rlm_sql: Socket %d used for %.2f seconds", sqlsocket->id, end - start);
192 sqlsocket->tv.tv_sec = tv.tv_sec;
193 sqlsocket->tv.tv_usec = tv.tv_usec;
196 pthread_mutex_lock(inst->lock);
200 sem_post(sqlsocket->semaphore);
202 sqlsocket->in_use = 0;
205 radlog(L_DBG,"rlm_sql: Released sql socket id: %d", sqlsocket->id);
208 pthread_mutex_unlock(inst->lock);
209 pthread_cond_signal(inst->notfull);
216 /*************************************************************************
218 * Function: sql_save_acct
220 * Purpose: Write data from the sqlrecord structure to the database
222 *************************************************************************/
224 int sql_save_acct(SQL_INST *inst, SQLSOCK *sqlsocket, SQLACCTREC *sqlrecord) {
231 #ifdef NT_DOMAIN_HACK
233 char newname[AUTH_STRING_LEN];
236 acctunique = strlen(sqlrecord->AcctUniqueId);
238 if(inst->config->sqltrace) {
239 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
240 radlog(L_ERR, "rlm_sql: Couldn't open file %s", inst->config->tracefile);
242 #if defined(F_LOCK) && !defined(BSD)
243 (void) lockf((int) sqlfile, (int) F_LOCK, (off_t) SQL_LOCK_LEN);
245 (void) flock(sqlfile, SQL_LOCK_EX);
250 #ifdef NT_DOMAIN_HACK
252 * Windows NT machines often authenticate themselves as
253 * NT_DOMAIN\username. Try to be smart about this.
255 * FIXME: should we handle this as a REALM ?
257 if ((ptr = strchr(sqlrecord->UserName, '\\')) != NULL) {
258 strncpy(newname, ptr + 1, sizeof(newname));
259 newname[sizeof(newname) - 1] = 0;
260 strcpy(sqlrecord->UserName, newname);
266 if (sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_ON ||
267 sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_OFF) {
268 radlog(L_INFO, "rlm_sql: Portmaster %s rebooted at %s", sqlrecord->NASIPAddress,
269 sqlrecord->AcctTimeStamp);
272 * The Terminal server informed us that it was rebooted
273 * * STOP all records from this NAS
277 "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'",
278 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
279 sqlrecord->AcctTimeStamp, sqlrecord->AcctTerminateCause,
280 sqlrecord->AcctDelayTime, sqlrecord->NASIPAddress,
281 sqlrecord->AcctTimeStamp);
283 if (sql_query(inst, sqlsocket, querystr) < 0)
284 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting after NAS reboot - %s",
285 sql_error(sqlsocket));
286 sql_finish_query(sqlsocket);
289 fputs(querystr, sqlfile);
290 fputs(";\n", sqlfile);
296 if (sqlrecord->AcctStatusTypeId == PW_STATUS_ALIVE) {
298 * Use acct unique session identifier if present
301 sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctUniqueId = '%s'",
302 inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
303 sqlrecord->AcctUniqueId);
306 sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress= '%s'",
307 inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
308 sqlrecord->AcctSessionId, sqlrecord->UserName,
309 sqlrecord->NASIPAddress);
312 if (sql_query(inst, sqlsocket, querystr) < 0)
313 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s",
314 sql_error(sqlsocket));
315 sql_finish_query(sqlsocket);
318 fputs(querystr, sqlfile);
319 fputs(";\n", sqlfile);
328 if (sqlrecord->AcctStatusTypeId == PW_STATUS_START) {
331 * Insert new record with blank stop time until stop record is got
333 snprintf(querystr, 2048,
334 "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)",
335 inst->config->sql_acct_table, sqlrecord->AcctSessionId,
336 sqlrecord->AcctUniqueId, sqlrecord->UserName,
337 sqlrecord->Realm, sqlrecord->NASIPAddress,
338 sqlrecord->NASPortId, sqlrecord->NASPortType,
339 sqlrecord->AcctTimeStamp, sqlrecord->AcctAuthentic,
340 sqlrecord->ConnectInfo, sqlrecord->CalledStationId,
341 sqlrecord->CallingStationId, sqlrecord->ServiceType,
342 sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
343 sqlrecord->AcctDelayTime);
345 if (sql_query(inst, sqlsocket, querystr) < 0) {
346 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting START record - %s",
347 sql_error(sqlsocket));
350 * We failed the insert above. It's probably because
351 * the stop record came before the start. We try an
352 * update here to be sure
355 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctUniqueId = '%s'",
356 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
357 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
358 sqlrecord->AcctUniqueId);
360 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress = '%s'",
361 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
362 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
363 sqlrecord->AcctSessionId, sqlrecord->UserName,
364 sqlrecord->NASIPAddress);
366 if (sql_query(inst, sqlsocket, querystr) < 0)
367 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting START record - %s",
368 sql_error(sqlsocket));
371 sql_finish_query(sqlsocket);
379 * Set stop time on matching record with start time
382 snprintf(querystr, 2048,
383 "UPDATE %s SET AcctStopTime = '%s', AcctSessionTime = '%lu', AcctInputOctets = '%lu', AcctOutputOctets = '%lu', AcctTerminateCause = '%s', AcctStopDelay = %ld, ConnectInfo_stop = '%s' WHERE AcctUniqueId = '%s'",
384 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
385 sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
386 sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
387 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
388 sqlrecord->AcctUniqueId);
391 snprintf(querystr, 2048,
392 "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'",
393 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
394 sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
395 sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
396 sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
397 sqlrecord->AcctSessionId, sqlrecord->UserName,
398 sqlrecord->NASIPAddress);
402 if (sql_query(inst, sqlsocket, querystr) < 0)
403 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting STOP record - %s",
404 sql_error(sqlsocket));
405 sql_finish_query(sqlsocket);
409 * If our update above didn't match anything
410 * we assume it's because we haven't seen a
411 * matching Start record. So we have to
412 * insert this stop rather than do an update
414 num = sql_affected_rows(sqlsocket);
417 #ifdef CISCO_ACCOUNTING_HACK
419 * If stop but zero session length AND no previous
420 * session found, drop it as in invalid packet
421 * This is to fix CISCO's aaa from filling our
422 * table with bogus crap
424 if (sqlrecord->AcctSessionTime <= 0) {
425 radlog(L_ERR, "rlm_sql: Invalid STOP record. [%s] STOP record but zero session length? (nas %s)",
426 sqlrecord->UserName, sqlrecord->NASIPAddress);
432 * Insert record with no start time until matching start record comes
434 snprintf(querystr, 2048,
435 "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)",
436 inst->config->sql_acct_table, sqlrecord->AcctSessionId,
437 sqlrecord->AcctUniqueId, sqlrecord->UserName,
438 sqlrecord->Realm, sqlrecord->NASIPAddress,
439 sqlrecord->NASPortId, sqlrecord->NASPortType,
440 sqlrecord->AcctTimeStamp, sqlrecord->AcctSessionTime,
441 sqlrecord->AcctAuthentic, sqlrecord->ConnectInfo,
442 sqlrecord->AcctInputOctets, sqlrecord->AcctOutputOctets,
443 sqlrecord->CalledStationId, sqlrecord->CallingStationId,
444 sqlrecord->AcctTerminateCause, sqlrecord->ServiceType,
445 sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
446 sqlrecord->AcctDelayTime);
448 if (sql_query(inst, sqlsocket, querystr) < 0)
449 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting STOP record - %s",
450 sql_error(sqlsocket));
451 sql_finish_query(sqlsocket);
456 fputs(querystr, sqlfile);
457 fputs(";\n", sqlfile);
467 /*************************************************************************
469 * Function: sql_userparse
471 * Purpose: Read entries from the database and fill VALUE_PAIR structures
473 *************************************************************************/
474 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int mode) {
477 VALUE_PAIR *pair, *check;
480 if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
481 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
486 * If attribute is already there, skip it because we checked usercheck first
487 * and we want user settings to over ride group settings
489 if ((check = pairfind(*first_pair, attr->attr)) != NULL &&
490 #if defined( BINARY_FILTERS )
491 attr->type != PW_TYPE_ABINARY &&
493 mode == PW_VP_GROUPDATA) return 0;
495 pair = pairmake(row[2], row[3], T_OP_CMP_EQ);
496 pairadd(first_pair, pair);
502 /*************************************************************************
504 * Function: sql_getvpdata
506 * Purpose: Get any group check or reply pairs
508 *************************************************************************/
509 int sql_getvpdata(SQL_INST *inst, SQLSOCK *sqlsocket, char *table, VALUE_PAIR ** vp, char *user, int mode)
514 char username[AUTH_STRING_LEN * 2 + 1];
519 if (strlen(user) > AUTH_STRING_LEN)
520 length = AUTH_STRING_LEN;
522 length = strlen(user);
525 * FIXME CHECK user for weird charactors!!
527 sql_escape_string(username, user, length);
529 if (mode == PW_VP_USERDATA) {
530 if (inst->config->sensitiveusername)
531 sprintf(authstr, "STRCMP(Username, '%s') = 0", username);
533 sprintf(authstr, "UserName = '%s'", username);
534 sprintf(querystr, "SELECT * FROM %s WHERE %s ORDER BY id", table,
536 } else if (mode == PW_VP_GROUPDATA) {
537 if (inst->config->sensitiveusername)
538 sprintf(authstr, "STRCMP(%s.Username, '%s') = 0",
539 inst->config->sql_usergroup_table, username);
541 sprintf(authstr, "%s.UserName = '%s'", inst->config->sql_usergroup_table,
544 "SELECT %s.* FROM %s, %s WHERE %s AND %s.GroupName = %s.GroupName ORDER BY %s.id",
545 table, table, inst->config->sql_usergroup_table, authstr,
546 inst->config->sql_usergroup_table, table, table);
547 } else if (mode == PW_VP_REALMDATA)
549 "SELECT %s.* FROM %s, %s WHERE %s.RealmName = '%s' AND %s.GroupName = %s.GroupName ORDER BY %s.id",
550 table, table, inst->config->sql_realmgroup_table,
551 inst->config->sql_realmgroup_table, username,
552 inst->config->sql_realmgroup_table, table, table);
553 sql_select_query(inst, sqlsocket, querystr);
554 rows = sql_num_rows(sqlsocket);
555 while ((row = sql_fetch_row(sqlsocket))) {
557 if (sql_userparse(vp, row, mode) != 0) {
558 radlog(L_ERR | L_CONS, "rlm_sql: Error getting data from database");
559 sql_finish_select_query(sqlsocket);
563 sql_finish_select_query(sqlsocket);
577 /*************************************************************************
579 * Function: sql_check_ts
581 * Purpose: Checks the terminal server for a spacific login entry
583 *************************************************************************/
584 static int sql_check_ts(SQL_ROW row) {
591 void (*handler) (int);
596 if ((nas = nas_find(ip_addr(row[4]))) == NULL) {
597 radlog(L_ERR, "rlm_sql: unknown NAS [%s]", row[4]);
604 handler = signal(SIGCHLD, SIG_DFL);
605 if ((pid = fork()) < 0) {
606 radlog(L_ERR, "rlm_sql: fork: %s", strerror(errno));
607 signal(SIGCHLD, handler);
613 * Parent - Wait for checkrad to terminate.
614 * We timeout in 10 seconds.
617 signal(SIGALRM, alrm_handler);
619 while ((e = waitpid(pid, &st, 0)) != pid)
620 if (e < 0 && (errno != EINTR || got_alrm))
623 signal(SIGCHLD, handler);
628 radlog(L_ERR, "rlm_sql: Check-TS: timeout waiting for checkrad");
632 radlog(L_ERR, "rlm_sql: Check-TS: unknown error in waitpid()");
635 return WEXITSTATUS(st);
639 * Child - exec checklogin with the right parameters.
641 for (n = 32; n >= 3; n--)
644 sprintf(session_id, "%.8s", row[1]);
647 execl(CHECKRAD2, "checkrad", nas->nastype, row[4], row[5],
648 row[2], session_id, NULL);
649 if (errno == ENOENT) {
651 execl(CHECKRAD1, "checklogin", nas->nastype, row[4], row[5],
652 row[2], session_id, NULL);
654 radlog(L_ERR, "rlm_sql: Check-TS: exec %s: %s", s, strerror(errno));
657 * Exit - 2 means "some error occured".
664 /*************************************************************************
666 * Function: sql_check_multi
668 * Purpose: Check radius accounting for duplicate logins
670 *************************************************************************/
671 int sql_check_multi(SQL_INST *inst, SQLSOCK *sqlsocket, char *name, VALUE_PAIR * request, int maxsimul) {
681 if (inst->config->sensitiveusername)
682 sprintf(authstr, "STRCMP(UserName, '%s') = 0", name);
684 sprintf(authstr, "UserName = '%s'", name);
685 sprintf(querystr, "SELECT COUNT(*) FROM %s WHERE %s AND AcctStopTime = 0",
686 inst->config->sql_acct_table, authstr);
687 sql_select_query(inst, sqlsocket, querystr);
688 row = sql_fetch_row(sqlsocket);
689 count = atoi(row[0]);
690 sql_finish_select_query(sqlsocket);
692 if (count < maxsimul)
696 * * Setup some stuff, like for MPP detection.
698 if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
699 ipno = htonl(fra->lvalue);
702 sprintf(querystr, "SELECT * FROM %s WHERE %s AND AcctStopTime = 0",
703 inst->config->sql_acct_table, authstr);
704 sql_select_query(inst, sqlsocket, querystr);
705 while ((row = sql_fetch_row(sqlsocket))) {
706 int check = sql_check_ts(row);
711 if (ipno && atoi(row[19]) == ipno)
714 } else if (check == 2)
715 radlog(L_ERR, "rlm_sql: Problem with checkrad [%s] (from nas %s)", name, row[4]);
718 * False record - zap it
721 if (inst->config->deletestalesessions) {
724 radlog(L_ERR, "rlm_sql: Deleteing stale session [%s] (from nas %s/%s)", row[2],
726 sqlsocket1 = sql_get_socket(inst);
727 sprintf(querystr, "DELETE FROM %s WHERE RadAcctId = '%s'",
728 inst->config->sql_acct_table, row[0]);
729 sql_query(inst, sqlsocket1, querystr);
730 sql_finish_query(sqlsocket1);
731 sql_release_socket(inst, sqlsocket1);
735 sql_finish_select_query(sqlsocket);
737 return (count < maxsimul) ? 0 : mpp;