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