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 2001 The FreeRADIUS server project
22 * Copyright 2000 Mike Machado <mike@innercite.com>
23 * Copyright 2000 Alan DeKok <aland@ox.org>
24 * Copyright 2001 Chad Miller <cmiller@surfsouth.com>
28 #include <sys/types.h>
29 #include <sys/socket.h>
34 #include <netinet/in.h>
57 * Connect to a server. If error, set this socket's state to be "sockunconnected"
58 * and set a grace period, during which we won't try connecting again (to prevent unduly
59 * lagging the server and being impolite to a DB server that may be having other
60 * issues). If successful in connecting, set state to sockconnected. - chad
62 static int connect_single_socket(SQLSOCK *sqlsocket, SQL_INST *inst) {
63 if ((inst->module->sql_init_socket)(sqlsocket, inst->config) < 0) {
64 radlog(L_CONS | L_ERR, "rlm_sql: Failed to connect DB handle #%d", sqlsocket->id);
65 inst->connect_after = time(NULL) + inst->config->connect_failure_retry_delay;
66 sqlsocket->state = sockunconnected;
69 radlog(L_DBG, "rlm_sql: Connected new DB handle, #%d", sqlsocket->id);
70 sqlsocket->state = sockconnected;
76 /*************************************************************************
78 * Function: sql_init_socketpool
80 * Purpose: Connect to the sql server, if possible
82 *************************************************************************/
83 int sql_init_socketpool(SQL_INST * inst) {
88 inst->connect_after = 0;
92 for (i = 0; i < inst->config->num_sql_socks; i++) {
94 sqlsocket = rad_malloc(sizeof(SQLSOCK));
95 if (sqlsocket == NULL) {
98 sqlsocket->conn = NULL;
100 sqlsocket->state = sockunconnected;
103 sqlsocket->semaphore = (sem_t *) rad_malloc(sizeof(sem_t));
104 sem_init(sqlsocket->semaphore, 0, SQLSOCK_UNLOCKED);
106 sqlsocket->in_use = 0;
109 if (time(NULL) > inst->connect_after) {
110 /* this sets the sqlsocket->state, and possibly sets inst->connect_after */
111 connect_single_socket(sqlsocket, inst);
114 /* Add this socket to the list of sockets */
115 sqlsocket->next = inst->sqlpool;
116 inst->sqlpool = sqlsocket;
122 /*************************************************************************
124 * Function: sql_poolfree
126 * Purpose: Clean up and free sql pool
128 *************************************************************************/
129 void sql_poolfree(SQL_INST * inst) {
133 for (cur = inst->sqlpool; cur; cur = cur->next) {
134 sql_close_socket(inst, cur);
137 pthread_mutex_destroy(inst->lock);
138 pthread_cond_destroy(inst->notfull);
143 /*************************************************************************
145 * Function: sql_close_socket
147 * Purpose: Close and free a sql sqlsocket
149 *************************************************************************/
150 int sql_close_socket(SQL_INST *inst, SQLSOCK * sqlsocket) {
152 radlog(L_DBG, "rlm_sql: Closing sqlsocket %d", sqlsocket->id);
153 (inst->module->sql_close)(sqlsocket, inst->config);
155 sem_destroy(sqlsocket->semaphore);
162 /*************************************************************************
164 * Function: sql_get_socket
166 * Purpose: Return a SQL sqlsocket from the connection pool
168 *************************************************************************/
169 SQLSOCK * sql_get_socket(SQL_INST * inst) {
171 struct timeval timeout;
172 int tried_to_connect = 0;
175 pthread_mutex_lock(inst->lock);
177 while (inst->used == inst->config->num_sql_socks) {
178 radlog(L_DBG, "rlm_sql: Waiting for open sql socket");
180 pthread_cond_wait(inst->notfull, inst->lock);
182 /* this should be portable... */
184 timeout.tv_usec = 200;
185 select(0, NULL, NULL, NULL, &timeout)
189 for (cur = inst->sqlpool; cur; cur = cur->next) {
191 /* if we happen upon an unconnected socket, and this instance's grace
192 * period on (re)connecting has expired, then try to connect it. This
193 * should be really rare. - chad
195 if ((cur->state == sockunconnected) && (time(NULL) > inst->connect_after)) {
196 tried_to_connect = 1;
197 radlog(L_INFO, "rlm_sql: Trying to (re)connect an unconnected handle...");
198 connect_single_socket(cur, inst);
201 /* if we still aren't connected, ignore this handle */
202 if (cur->state == sockunconnected) {
203 radlog(L_DBG, "rlm_sql: Ignoring unconnected handle");
208 if (sem_trywait(cur->semaphore) == 0) {
210 if (cur->in_use == SQLSOCK_UNLOCKED) {
214 pthread_mutex_unlock(inst->lock);
216 cur->in_use = SQLSOCK_LOCKED;
218 radlog(L_DBG, "rlm_sql: Reserving sql socket id: %d", cur->id);
224 pthread_mutex_unlock(inst->lock);
227 /* We get here if every DB handle is unconnected and unconnectABLE */
228 radlog((tried_to_connect = 0) ? (L_DBG) : (L_CONS | L_ERR), "rlm_sql: There are no DB handles to use!");
232 /*************************************************************************
234 * Function: sql_release_socket
236 * Purpose: Frees a SQL sqlsocket back to the connection pool
238 *************************************************************************/
239 int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket) {
242 pthread_mutex_lock(inst->lock);
246 sem_post(sqlsocket->semaphore);
248 sqlsocket->in_use = 0;
251 radlog(L_DBG, "rlm_sql: Released sql socket id: %d", sqlsocket->id);
254 pthread_mutex_unlock(inst->lock);
255 pthread_cond_signal(inst->notfull);
262 /*************************************************************************
264 * Function: sql_userparse
266 * Purpose: Read entries from the database and fill VALUE_PAIR structures
268 *************************************************************************/
269 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int mode) {
272 VALUE_PAIR *pair, *check;
274 if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
275 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
280 * If attribute is already there, skip it because we checked usercheck first
281 * and we want user settings to over ride group settings
283 if ((check = pairfind(*first_pair, attr->attr)) != NULL &&
284 #if defined( BINARY_FILTERS )
285 attr->type != PW_TYPE_ABINARY &&
287 mode == PW_VP_GROUPDATA)
290 pair = pairmake(row[2], row[3], T_OP_CMP_EQ);
291 pairadd(first_pair, pair);
293 vp_printlist(stderr, *first_pair);
299 /*************************************************************************
301 * Function: sql_getvpdata
303 * Purpose: Get any group check or reply pairs
305 *************************************************************************/
306 int sql_getvpdata(SQL_INST * inst, SQLSOCK * sqlsocket, VALUE_PAIR **pair, char *query, int mode) {
311 if ((inst->module->sql_select_query)(sqlsocket, inst->config, query) < 0) {
312 radlog(L_ERR, "rlm_sql_getvpdata: database query error");
315 while ((row = (inst->module->sql_fetch_row)(sqlsocket, inst->config))) {
316 if (sql_userparse(pair, row, mode) != 0) {
317 radlog(L_ERR | L_CONS, "rlm_sql: Error getting data from database");
318 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
323 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
335 /*************************************************************************
337 * Function: sql_check_ts
339 * Purpose: Checks the terminal server for a spacific login entry
341 *************************************************************************/
342 static int sql_check_ts(SQL_ROW row) {
349 void (*handler) (int);
354 if ((nas = nas_find(ip_addr(row[4]))) == NULL) {
355 radlog(L_ERR, "rlm_sql: unknown NAS [%s]", row[4]);
362 handler = signal(SIGCHLD, SIG_DFL);
363 if ((pid = fork()) < 0) {
364 radlog(L_ERR, "rlm_sql: fork: %s", strerror(errno));
365 signal(SIGCHLD, handler);
371 * Parent - Wait for checkrad to terminate.
372 * We timeout in 10 seconds.
375 signal(SIGALRM, alrm_handler);
377 while ((e = waitpid(pid, &st, 0)) != pid)
378 if (e < 0 && (errno != EINTR || got_alrm))
381 signal(SIGCHLD, handler);
386 radlog(L_ERR, "rlm_sql: Check-TS: timeout waiting for checkrad");
390 radlog(L_ERR, "rlm_sql: Check-TS: unknown error in waitpid()");
393 return WEXITSTATUS(st);
397 * Child - exec checklogin with the right parameters.
399 for (n = 32; n >= 3; n--)
402 sprintf(session_id, "%.8s", row[1]);
405 execl(CHECKRAD2, "checkrad", nas->nastype, row[4], row[5],
406 row[2], session_id, NULL);
407 if (errno == ENOENT) {
409 execl(CHECKRAD1, "checklogin", nas->nastype, row[4], row[5],
410 row[2], session_id, NULL);
412 radlog(L_ERR, "rlm_sql: Check-TS: exec %s: %s", s, strerror(errno));
415 * Exit - 2 means "some error occured".
422 /*************************************************************************
424 * Function: sql_check_multi
426 * Purpose: Check radius accounting for duplicate logins
428 *************************************************************************/
429 int sql_check_multi(SQL_INST * inst, SQLSOCK * sqlsocket, char *name, VALUE_PAIR * request, int maxsimul) {
431 char querystr[MAX_QUERY_LEN];
439 sprintf(authstr, "UserName = '%s'", name);
440 sprintf(querystr, "SELECT COUNT(*) FROM %s WHERE %s AND AcctStopTime = 0", inst->config->sql_acct_table, authstr);
441 if ((inst->module->sql_select_query)(sqlsocket, inst->config, querystr) < 0) {
442 radlog(L_ERR, "sql_check_multi: database query error");
446 row = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
447 count = atoi(row[0]);
448 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
450 if (count < maxsimul)
454 * * Setup some stuff, like for MPP detection.
456 if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
457 ipno = htonl(fra->lvalue);
460 sprintf(querystr, "SELECT * FROM %s WHERE %s AND AcctStopTime = 0", inst->config->sql_acct_table, authstr);
461 if ((inst->module->sql_select_query)(sqlsocket, inst->config, querystr) < 0) {
462 radlog(L_ERR, "sql_check_multi: database query error");
465 while ((row = (inst->module->sql_fetch_row)(sqlsocket, inst->config))) {
466 int check = sql_check_ts(row);
471 if (ipno && atoi(row[19]) == ipno)
474 } else if (check == 2)
475 radlog(L_ERR, "rlm_sql: Problem with checkrad [%s] (from nas %s)", name, row[4]);
478 * False record - zap it
481 if (inst->config->deletestalesessions) {
484 radlog(L_ERR, "rlm_sql: Deleteing stale session [%s] (from nas %s/%s)", row[2], row[4], row[5]);
485 sqlsocket1 = sql_get_socket(inst);
486 sprintf(querystr, "DELETE FROM %s WHERE RadAcctId = '%s'", inst->config->sql_acct_table, row[0]);
487 (inst->module->sql_query)(sqlsocket1, inst->config, querystr);
488 (inst->module->sql_finish_query)(sqlsocket1, inst->config);
489 sql_release_socket(inst, sqlsocket1);
493 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
495 return (count < maxsimul) ? 0 : mpp;
498 void query_log(SQL_INST * inst, char *querystr) {
501 if (inst->config->sqltrace) {
502 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
503 radlog(L_ERR, "rlm_sql: Couldn't open file %s",
504 inst->config->tracefile);
506 #if defined(F_LOCK) && !defined(BSD)
507 (void) lockf((int) sqlfile, (int) F_LOCK, (off_t) MAX_QUERY_LEN);
509 (void) flock(sqlfile, SQL_LOCK_EX);
511 fputs(querystr, sqlfile);
512 fputs(";\n", sqlfile);
518 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, char *username) {
520 char tmpuser[MAX_STRING_LEN];
525 /* Remove any user attr we added previously */
526 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
529 strNcpy(tmpuser, username, MAX_STRING_LEN);
530 } else if(strlen(inst->config->query_user)) {
531 radius_xlat(tmpuser, MAX_STRING_LEN, inst->config->query_user, request, NULL);
536 if(strlen(tmpuser)) {
537 sql_escape_string(sqlusername, tmpuser, MAX_STRING_LEN);
538 DEBUG2("sql_set_user: escaped user --> '%s'", sqlusername);
539 vp = pairmake("SQL-User-Name", sqlusername, 0);
541 radlog(L_ERR, "%s", librad_errstr);
545 pairadd(&request->packet->vps, vp);
552 * Purpose: Esacpe "'" and any other wierd charactors
554 int sql_escape_string(char *to, char *from, int length) {
557 DEBUG2("sql_escape in: '%s'", from);
559 for(x=0, y=0; (x < length) && (from[x]!='\0'); x++) {
589 /* Ascii file separator */
600 DEBUG2("sql_escape out: '%s'", to);