signal handlers take an int arg
[freeradius.git] / src / modules / rlm_sql / sql.c
1 /*
2  *  sql.c               rlm_sql - FreeRADIUS SQL Module
3  *              Main code directly taken from ICRADIUS
4  *
5  * Version:     $Id$
6  *
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.
11  *
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.
16  *
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
20  *
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>
25  */
26
27
28 #include        <sys/types.h>
29 #include        <sys/socket.h>
30 #include        <sys/time.h>
31 #include        <sys/file.h>
32 #include        <string.h>
33 #include        <sys/stat.h>
34 #include        <netinet/in.h>
35
36 #include        <stdio.h>
37 #include        <stdlib.h>
38 #include        <netdb.h>
39 #include        <pwd.h>
40 #include        <time.h>
41 #include        <ctype.h>
42 #include        <unistd.h>
43 #include        <signal.h>
44 #include        <errno.h>
45 #include        <sys/wait.h>
46
47 #if HAVE_PTHREAD_H
48 #include        <pthread.h>
49 #endif
50
51 #include        "radiusd.h"
52 #include        "conffile.h"
53 #include        "rlm_sql.h"
54
55
56 /*
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
61  */
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;
67                 return(-1);
68         } else {
69                 radlog(L_DBG, "rlm_sql:  Connected new DB handle, #%d", sqlsocket->id);
70                 sqlsocket->state = sockconnected;
71                 return(0);
72         }
73 }
74
75
76 /*************************************************************************
77  *
78  *      Function: sql_init_socketpool
79  *
80  *      Purpose: Connect to the sql server, if possible
81  *
82  *************************************************************************/
83 int sql_init_socketpool(SQL_INST * inst) {
84
85         SQLSOCK *sqlsocket;
86         int     i;
87
88         inst->connect_after = 0;
89         inst->used = 0;
90         inst->sqlpool = NULL;
91
92         for (i = 0; i < inst->config->num_sql_socks; i++) {
93
94                 sqlsocket = rad_malloc(sizeof(SQLSOCK));
95                 if (sqlsocket == NULL) {
96                         return -1;
97                 }
98                 sqlsocket->conn = NULL;
99                 sqlsocket->id = i;
100                 sqlsocket->state = sockunconnected;
101
102 #if HAVE_SEMAPHORE_H
103                 /*
104                  *  FIXME! Check return codes!
105                  */
106                 sqlsocket->semaphore = (sem_t *) rad_malloc(sizeof(sem_t));
107                 sem_init(sqlsocket->semaphore, 0, SQLSOCK_UNLOCKED);
108 #else
109                 sqlsocket->in_use = SQLSOCK_UNLOCKED;
110 #endif
111
112                 if (time(NULL) > inst->connect_after) {
113                         /* this sets the sqlsocket->state, and possibly sets inst->connect_after */
114                         connect_single_socket(sqlsocket, inst);
115                 }
116
117                 /* Add this socket to the list of sockets */
118                 sqlsocket->next = inst->sqlpool;
119                 inst->sqlpool = sqlsocket;
120         }
121
122         return 1;
123 }
124
125 /*************************************************************************
126  *
127  *     Function: sql_poolfree
128  *
129  *     Purpose: Clean up and free sql pool
130  *
131  *************************************************************************/
132 void sql_poolfree(SQL_INST * inst) {
133
134         SQLSOCK *cur;
135
136         for (cur = inst->sqlpool; cur; cur = cur->next) {
137                 sql_close_socket(inst, cur);
138         }
139 }
140
141
142 /*************************************************************************
143  *
144  *      Function: sql_close_socket
145  *
146  *      Purpose: Close and free a sql sqlsocket
147  *
148  *************************************************************************/
149 int sql_close_socket(SQL_INST *inst, SQLSOCK * sqlsocket) {
150
151         radlog(L_DBG, "rlm_sql: Closing sqlsocket %d", sqlsocket->id);
152         (inst->module->sql_close)(sqlsocket, inst->config);
153 #if HAVE_SEMAPHORE_H
154         sem_destroy(sqlsocket->semaphore);
155 #endif
156         free(sqlsocket);
157         return 1;
158 }
159
160
161 /*************************************************************************
162  *
163  *      Function: sql_get_socket
164  *
165  *      Purpose: Return a SQL sqlsocket from the connection pool           
166  *
167  *************************************************************************/
168 SQLSOCK * sql_get_socket(SQL_INST * inst) {
169         SQLSOCK *cur;
170         int tried_to_connect = 0;
171
172         while (inst->used == inst->config->num_sql_socks) {
173                 radlog(L_ERR, "rlm_sql: All sockets are being used! Please increase the maximum number of sockets!");
174                 return NULL;
175         }
176
177         for (cur = inst->sqlpool; cur; cur = cur->next) {
178
179                 /* if we happen upon an unconnected socket, and this instance's grace 
180                  * period on (re)connecting has expired, then try to connect it.  This 
181                  * should be really rare.  - chad
182                  */
183                 if ((cur->state == sockunconnected) && (time(NULL) > inst->connect_after)) {
184                         tried_to_connect = 1;
185                         radlog(L_INFO, "rlm_sql: Trying to (re)connect an unconnected handle...");
186                         connect_single_socket(cur, inst);
187                 }
188
189                 /* if we still aren't connected, ignore this handle */
190                 if (cur->state == sockunconnected) {
191                         radlog(L_DBG, "rlm_sql: Ignoring unconnected handle");
192                         continue;
193                 }
194
195 #if HAVE_SEMAPHORE_H
196                 if (sem_trywait(cur->semaphore) == 0) {
197 #else
198                 if (cur->in_use == SQLSOCK_UNLOCKED) {
199 #endif
200                         (inst->used)++;
201 #ifndef HAVE_SEMAPHORE_H
202                         cur->in_use = SQLSOCK_LOCKED;
203 #endif
204                         radlog(L_DBG, "rlm_sql: Reserving sql socket id: %d", cur->id);
205                         return cur;
206                 }
207         }
208
209         /* We get here if every DB handle is unconnected and unconnectABLE */
210         radlog((tried_to_connect = 0) ? (L_DBG) : (L_CONS | L_ERR), "rlm_sql:  There are no DB handles to use!");
211         return NULL;
212 }
213
214 /*************************************************************************
215  *
216  *      Function: sql_release_socket
217  *
218  *      Purpose: Frees a SQL sqlsocket back to the connection pool           
219  *
220  *************************************************************************/
221 int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket) {
222
223         (inst->used)--;
224 #if HAVE_SEMAPHORE_H
225         sem_post(sqlsocket->semaphore);
226 #else
227         sqlsocket->in_use = SQLSOCK_UNLOCKED;
228 #endif
229
230         radlog(L_DBG, "rlm_sql: Released sql socket id: %d", sqlsocket->id);
231
232         return 1;
233 }
234
235
236 /*************************************************************************
237  *
238  *      Function: sql_userparse
239  *
240  *      Purpose: Read entries from the database and fill VALUE_PAIR structures
241  *
242  *************************************************************************/
243 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int querymode) {
244
245         DICT_ATTR *attr;
246         VALUE_PAIR *pair, *check;
247         char *ptr;
248         char buf[128];
249         int pairmode = T_EOL;
250
251         if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
252                 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
253                 return (-1);
254         }
255
256         if (row[4] != NULL && strlen(row[4]) > 0) {
257                 ptr = row[4];
258                 pairmode = gettoken(&ptr, buf, sizeof(buf));
259         }
260         if (pairmode <= T_EOL) pairmode = T_OP_CMP_EQ;
261
262         /*
263          * If attribute is already there, skip it because we checked usercheck first 
264          * and we want user settings to over ride group settings 
265          */
266         if (pairmode != T_OP_ADD && (check = pairfind(*first_pair, attr->attr)) != NULL &&
267 #ifdef ASCEND_BINARY
268                         attr->type != PW_TYPE_ABINARY &&
269 #endif
270                         querymode == PW_VP_GROUPDATA)
271                 return 0;
272
273         pair = pairmake(row[2], row[3], pairmode);
274         pairadd(first_pair, pair);
275
276         return 0;
277 }
278
279
280 /*************************************************************************
281  *
282  *      Function: sql_getvpdata
283  *
284  *      Purpose: Get any group check or reply pairs
285  *
286  *************************************************************************/
287 int sql_getvpdata(SQL_INST * inst, SQLSOCK * sqlsocket, VALUE_PAIR **pair, char *query, int mode) {
288
289         SQL_ROW row;
290         int     rows = 0;
291
292         if ((inst->module->sql_select_query)(sqlsocket, inst->config, query) < 0) {
293                 radlog(L_ERR, "rlm_sql_getvpdata: database query error");
294                 return -1;
295         }
296         while ((row = (inst->module->sql_fetch_row)(sqlsocket, inst->config))) {
297                 if (sql_userparse(pair, row, mode) != 0) {
298                         radlog(L_ERR | L_CONS, "rlm_sql:  Error getting data from database");
299                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
300                         return -1;
301                 }
302                 rows++;
303         }
304         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
305
306         return rows;
307 }
308
309
310 static int got_alrm;
311 static void
312 alrm_handler(int i) {
313         got_alrm = 1;
314 }
315
316 /*************************************************************************
317  *
318  *      Function: sql_check_ts
319  *
320  *      Purpose: Checks the terminal server for a spacific login entry
321  *
322  *************************************************************************/
323 static int sql_check_ts(SQL_ROW row) {
324
325         int     pid, st, e;
326         int     n;
327         NAS    *nas;
328         char    session_id[12];
329         char   *s;
330         void    (*handler) (int);
331
332         /*
333          *      Find NAS type.
334          */
335         if ((nas = nas_find(ip_addr(row[4]))) == NULL) {
336                 radlog(L_ERR, "rlm_sql:  unknown NAS [%s]", row[4]);
337                 return -1;
338         }
339
340         /*
341          *      Fork.
342          */
343         handler = signal(SIGCHLD, SIG_DFL);
344         if ((pid = fork()) < 0) {
345                 radlog(L_ERR, "rlm_sql: fork: %s", strerror(errno));
346                 signal(SIGCHLD, handler);
347                 return -1;
348         }
349
350         if (pid > 0) {
351                 /*
352                  *      Parent - Wait for checkrad to terminate.
353                  *      We timeout in 10 seconds.
354                  */
355                 got_alrm = 0;
356                 signal(SIGALRM, alrm_handler);
357                 alarm(10);
358                 while ((e = waitpid(pid, &st, 0)) != pid)
359                         if (e < 0 && (errno != EINTR || got_alrm))
360                                 break;
361                 alarm(0);
362                 signal(SIGCHLD, handler);
363                 if (got_alrm) {
364                         kill(pid, SIGTERM);
365                         sleep(1);
366                         kill(pid, SIGKILL);
367                         radlog(L_ERR, "rlm_sql:  Check-TS: timeout waiting for checkrad");
368                         return 2;
369                 }
370                 if (e < 0) {
371                         radlog(L_ERR, "rlm_sql:  Check-TS: unknown error in waitpid()");
372                         return 2;
373                 }
374                 return WEXITSTATUS(st);
375         }
376
377         /*
378          *      Child - exec checklogin with the right parameters.
379          */
380         for (n = 32; n >= 3; n--)
381                 close(n);
382
383         sprintf(session_id, "%.8s", row[1]);
384
385         s = CHECKRAD2;
386         execl(CHECKRAD2, "checkrad", nas->nastype, row[4], row[5],
387                         row[2], session_id, NULL);
388         if (errno == ENOENT) {
389                 s = CHECKRAD1;
390                 execl(CHECKRAD1, "checklogin", nas->nastype, row[4], row[5],
391                                 row[2], session_id, NULL);
392         }
393         radlog(L_ERR, "rlm_sql:  Check-TS: exec %s: %s", s, strerror(errno));
394
395         /*
396          *      Exit - 2 means "some error occured".
397          */
398         exit(2);
399         return -1;
400 }
401
402
403 /*************************************************************************
404  *
405  *      Function: sql_check_multi
406  *
407  *      Purpose: Check radius accounting for duplicate logins
408  *
409  *************************************************************************/
410 int sql_check_multi(SQL_INST * inst, SQLSOCK * sqlsocket, char *name, VALUE_PAIR * request, int maxsimul) {
411
412         char    querystr[MAX_QUERY_LEN];
413         char    authstr[256];
414         VALUE_PAIR *fra;
415         SQL_ROW row;
416         int     count = 0;
417         uint32_t ipno = 0;
418         int     mpp = 1;
419
420         sprintf(authstr, "UserName = '%s'", name);
421         sprintf(querystr, "SELECT COUNT(*) FROM %s WHERE %s AND AcctStopTime = 0", inst->config->sql_acct_table, authstr);
422         if ((inst->module->sql_select_query)(sqlsocket, inst->config, querystr) < 0) {
423                 radlog(L_ERR, "sql_check_multi: database query error");
424                 return -1;
425         }
426
427         row = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
428         count = atoi(row[0]);
429         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
430
431         if (count < maxsimul)
432                 return 0;
433
434         /*
435          * *      Setup some stuff, like for MPP detection.
436          */
437         if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
438                 ipno = htonl(fra->lvalue);
439
440         count = 0;
441         sprintf(querystr, "SELECT * FROM %s WHERE %s AND AcctStopTime = 0", inst->config->sql_acct_table, authstr);
442         if ((inst->module->sql_select_query)(sqlsocket, inst->config, querystr) < 0) {
443                 radlog(L_ERR, "sql_check_multi: database query error");
444                 return -1;
445         }
446         while ((row = (inst->module->sql_fetch_row)(sqlsocket, inst->config))) {
447                 int     check = sql_check_ts(row);
448
449                 if (check == 1) {
450                         count++;
451
452                         if (ipno && atoi(row[19]) == ipno)
453                                 mpp = 2;
454
455                 } else if (check == 2)
456                         radlog(L_ERR, "rlm_sql:  Problem with checkrad [%s] (from nas %s)", name, row[4]);
457                 else {
458                         /*
459                          *      False record - zap it
460                          */
461
462                         if (inst->config->deletestalesessions) {
463                                 SQLSOCK *sqlsocket1;
464
465                                 radlog(L_ERR, "rlm_sql:  Deleteing stale session [%s] (from nas %s/%s)", row[2], row[4], row[5]);
466                                 sqlsocket1 = sql_get_socket(inst);
467                                 sprintf(querystr, "DELETE FROM %s WHERE RadAcctId = '%s'", inst->config->sql_acct_table, row[0]);
468                                 (inst->module->sql_query)(sqlsocket1, inst->config, querystr);
469                                 (inst->module->sql_finish_query)(sqlsocket1, inst->config);
470                                 sql_release_socket(inst, sqlsocket1);
471                         }
472                 }
473         }
474         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
475
476         return (count < maxsimul) ? 0 : mpp;
477 }
478
479 void query_log(SQL_INST * inst, char *querystr) {
480         FILE   *sqlfile = NULL;
481
482         if (inst->config->sqltrace) {
483                 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
484                         radlog(L_ERR, "rlm_sql: Couldn't open file %s",
485                                         inst->config->tracefile);
486                 } else {
487                         int fd = fileno(sqlfile);
488                         
489                         rad_lockfd(fd, MAX_QUERY_LEN);
490                         fputs(querystr, sqlfile);
491                         fputs(";\n", sqlfile);
492                         fclose(sqlfile); /* and release the lock */
493                 }
494         }
495 }