Change '=' in comparison to '=='
[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         radlog(L_DBG, "rlm_sql:  Attempting to connect #%d", sqlsocket->id);
64         if ((inst->module->sql_init_socket)(sqlsocket, inst->config) < 0) {
65                 radlog(L_CONS | L_ERR, "rlm_sql:  Failed to connect DB handle #%d", sqlsocket->id);
66                 inst->connect_after = time(NULL) + inst->config->connect_failure_retry_delay;
67                 sqlsocket->state = sockunconnected;
68                 return(-1);
69         } else {
70                 radlog(L_DBG, "rlm_sql:  Connected new DB handle, #%d", sqlsocket->id);
71                 sqlsocket->state = sockconnected;
72                 return(0);
73         }
74 }
75
76
77 /*************************************************************************
78  *
79  *      Function: sql_init_socketpool
80  *
81  *      Purpose: Connect to the sql server, if possible
82  *
83  *************************************************************************/
84 int sql_init_socketpool(SQL_INST * inst) {
85
86         SQLSOCK *sqlsocket;
87         int     i;
88
89         inst->connect_after = 0;
90         inst->used = 0;
91         inst->sqlpool = NULL;
92         inst->socknr = 0;
93
94         for (i = 0; i < inst->config->num_sql_socks; i++) {
95                 radlog(L_DBG, "rlm_sql: starting %d", i);
96
97                 sqlsocket = rad_malloc(sizeof(SQLSOCK));
98                 if (sqlsocket == NULL) {
99                         return -1;
100                 }
101                 sqlsocket->conn = NULL;
102                 sqlsocket->id = i;
103                 sqlsocket->state = sockunconnected;
104
105 #if HAVE_SEMAPHORE_H
106                 /*
107                  *  FIXME! Check return codes!
108                  */
109                 sqlsocket->semaphore = (sem_t *) rad_malloc(sizeof(sem_t));
110                 sem_init(sqlsocket->semaphore, 0, SQLSOCK_UNLOCKED);
111 #else
112                 sqlsocket->in_use = SQLSOCK_UNLOCKED;
113 #endif
114
115                 if (time(NULL) > inst->connect_after) {
116                         /* this sets the sqlsocket->state, and possibly sets inst->connect_after */
117                         /* FIXME! check return code */
118                         connect_single_socket(sqlsocket, inst);
119                 }
120
121                 /* Add this socket to the list of sockets */
122                 sqlsocket->next = inst->sqlpool;
123                 inst->sqlpool = sqlsocket;
124         }
125
126 #if HAVE_PTHREAD_H
127         pthread_mutex_init(&inst->mutex, NULL);
128 #endif
129
130         return 1;
131 }
132
133 /*************************************************************************
134  *
135  *     Function: sql_poolfree
136  *
137  *     Purpose: Clean up and free sql pool
138  *
139  *************************************************************************/
140 void sql_poolfree(SQL_INST * inst) {
141
142         SQLSOCK *cur;
143
144         for (cur = inst->sqlpool; cur; cur = cur->next) {
145                 sql_close_socket(inst, cur);
146         }
147 #if HAVE_PTHREAD_H
148         pthread_mutex_destroy(&inst->mutex);
149 #endif
150 }
151
152
153 /*************************************************************************
154  *
155  *      Function: sql_close_socket
156  *
157  *      Purpose: Close and free a sql sqlsocket
158  *
159  *************************************************************************/
160 int sql_close_socket(SQL_INST *inst, SQLSOCK * sqlsocket) {
161
162         radlog(L_DBG, "rlm_sql: Closing sqlsocket %d", sqlsocket->id);
163         (inst->module->sql_close)(sqlsocket, inst->config);
164 #if HAVE_SEMAPHORE_H
165         sem_destroy(sqlsocket->semaphore);
166 #endif
167         free(sqlsocket);
168         return 1;
169 }
170
171
172 /*************************************************************************
173  *
174  *      Function: sql_get_socket
175  *
176  *      Purpose: Return a SQL sqlsocket from the connection pool           
177  *
178  *************************************************************************/
179 SQLSOCK * sql_get_socket(SQL_INST * inst) {
180         SQLSOCK *cur, *cur2;
181         int tried_to_connect = 0;
182
183         while (inst->used == inst->config->num_sql_socks) {
184                 radlog(L_ERR, "rlm_sql: All sockets are being used! Please increase the maximum number of sockets!");
185                 return NULL;
186         }
187
188         /*
189          * Rotating the socket so that all get used and none get closed due to
190          * inactivity from the SQL server ( such as mySQL ).
191          */
192 #if HAVE_PTHREAD_H
193         pthread_mutex_lock(&inst->mutex);
194 #endif
195
196         if(inst->socknr == 0) {
197                 inst->socknr = inst->config->num_sql_socks;
198         }
199         inst->socknr--;
200         cur2 = inst->sqlpool;
201         while (inst->socknr != cur2->id) {
202                 cur2 = cur2->next;
203         }
204 #if HAVE_PTHREAD_H
205         pthread_mutex_unlock(&inst->mutex);
206 #endif
207
208         for (cur = cur2; cur; cur = cur->next) {
209
210                 /* if we happen upon an unconnected socket, and this instance's grace 
211                  * period on (re)connecting has expired, then try to connect it.  This 
212                  * should be really rare.  - chad
213                  */
214                 if ((cur->state == sockunconnected) && (time(NULL) > inst->connect_after)) {
215                         tried_to_connect = 1;
216                         radlog(L_INFO, "rlm_sql: Trying to (re)connect an unconnected handle...");
217                         connect_single_socket(cur, inst);
218                 }
219
220                 /* if we still aren't connected, ignore this handle */
221                 if (cur->state == sockunconnected) {
222                         radlog(L_DBG, "rlm_sql: Ignoring unconnected handle");
223                         continue;
224                 }
225
226 #if HAVE_SEMAPHORE_H
227                 if (sem_trywait(cur->semaphore) == 0) {
228 #else
229                 if (cur->in_use == SQLSOCK_UNLOCKED) {
230 #endif
231                         (inst->used)++;
232 #ifndef HAVE_SEMAPHORE_H
233                         cur->in_use = SQLSOCK_LOCKED;
234 #endif
235                         radlog(L_DBG, "rlm_sql: Reserving sql socket id: %d", cur->id);
236                         return cur;
237                 }
238         }
239
240         /* We get here if every DB handle is unconnected and unconnectABLE */
241         radlog((tried_to_connect == 0) ? (L_DBG) : (L_CONS | L_ERR), "rlm_sql:  There are no DB handles to use!");
242         return NULL;
243 }
244
245 /*************************************************************************
246  *
247  *      Function: sql_release_socket
248  *
249  *      Purpose: Frees a SQL sqlsocket back to the connection pool           
250  *
251  *************************************************************************/
252 int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket) {
253
254         (inst->used)--;
255 #if HAVE_SEMAPHORE_H
256         sem_post(sqlsocket->semaphore);
257 #else
258         sqlsocket->in_use = SQLSOCK_UNLOCKED;
259 #endif
260
261         radlog(L_DBG, "rlm_sql: Released sql socket id: %d", sqlsocket->id);
262
263         return 0;
264 }
265
266
267 /*************************************************************************
268  *
269  *      Function: sql_userparse
270  *
271  *      Purpose: Read entries from the database and fill VALUE_PAIR structures
272  *
273  *************************************************************************/
274 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int querymode) {
275
276         DICT_ATTR *attr;
277         VALUE_PAIR *pair, *check;
278         char *ptr;
279         char buf[128];
280         int pairmode = T_EOL;
281
282         if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
283                 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
284                 return (-1);
285         }
286
287         if (row[4] != NULL && strlen(row[4]) > 0) {
288                 ptr = row[4];
289                 pairmode = gettoken(&ptr, buf, sizeof(buf));
290         }
291         if (pairmode <= T_EOL) pairmode = T_OP_CMP_EQ;
292
293         /*
294          * If attribute is already there, skip it because we checked usercheck first 
295          * and we want user settings to over ride group settings 
296          */
297         if (pairmode != T_OP_ADD && (check = pairfind(*first_pair, attr->attr)) != NULL &&
298 #ifdef ASCEND_BINARY
299                         attr->type != PW_TYPE_ABINARY &&
300 #endif
301                         querymode == PW_VP_GROUPDATA)
302                 return 0;
303
304         pair = pairmake(row[2], row[3], pairmode);
305         pairadd(first_pair, pair);
306
307         return 0;
308 }
309
310
311 /*************************************************************************
312  *
313  *      Function: rlm_sql_fetch_row
314  *
315  *      Purpose: call the module's sql_fetch_row and implement re-connect
316  *
317  *************************************************************************/
318 int rlm_sql_fetch_row(SQLSOCK *sqlsocket, SQL_INST *inst) {
319         int ret;
320
321         ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
322
323         if (ret == SQL_DOWN) {
324                 if (connect_single_socket(sqlsocket, inst) < 0) {
325                         radlog(L_ERR, "rlm_sql: reconnect failed, database down?");
326                         return -1;
327                 }
328
329                 ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
330
331                 if (ret) {
332                         radlog(L_ERR, "rlm_sql: failed after re-connect");
333                         return -1;
334                 }
335         }
336
337         return ret;
338 }
339
340 /*************************************************************************
341  *
342  *      Function: rlm_sql_query
343  *
344  *      Purpose: call the module's sql_query and implement re-connect
345  *
346  *************************************************************************/
347 int rlm_sql_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query) {
348         int ret;
349
350         ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
351
352         if (ret == SQL_DOWN) {
353                 if (connect_single_socket(sqlsocket, inst) < 0) {
354                         radlog(L_ERR, "rlm_sql: reconnect failed, database down?");
355                         return -1;
356                 }
357
358                 ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
359
360                 if (ret) {
361                         radlog(L_ERR, "rlm_sql: failed after re-connect");
362                         return -1;
363                 }
364         }
365
366         return ret;
367 }
368
369 /*************************************************************************
370  *
371  *      Function: rlm_sql_select_query
372  *
373  *      Purpose: call the module's sql_select_query and implement re-connect
374  *
375  *************************************************************************/
376 int rlm_sql_select_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query) {
377         int ret;
378
379         ret = (inst->module->sql_select_query)(sqlsocket, inst->config, query);
380
381         if (ret == SQL_DOWN) {
382                 if (connect_single_socket(sqlsocket, inst) < 0) {
383                         radlog(L_ERR, "rlm_sql: reconnect failed, database down?");
384                         return -1;
385                 }
386
387                 ret = (inst->module->sql_select_query)(sqlsocket, inst->config, query);
388
389                 if (ret) {
390                         radlog(L_ERR, "rlm_sql: failed after re-connect");
391                         return -1;
392                 }
393         }
394
395         return ret;
396 }
397
398
399 /*************************************************************************
400  *
401  *      Function: sql_getvpdata
402  *
403  *      Purpose: Get any group check or reply pairs
404  *
405  *************************************************************************/
406 int sql_getvpdata(SQL_INST * inst, SQLSOCK * sqlsocket, VALUE_PAIR **pair, char *query, int mode) {
407
408         SQL_ROW row;
409         int     rows = 0;
410
411         if (rlm_sql_select_query(sqlsocket, inst, query)) {
412                 radlog(L_ERR, "rlm_sql_getvpdata: database query error");
413                 return -1;
414         }
415         while (rlm_sql_fetch_row(sqlsocket, inst)==0) {
416                 row = sqlsocket->row;
417                 if (!row)
418                         break;
419                 if (sql_userparse(pair, row, mode) != 0) {
420                         radlog(L_ERR | L_CONS, "rlm_sql:  Error getting data from database");
421                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
422                         return -1;
423                 }
424                 rows++;
425         }
426         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
427
428         return rows;
429 }
430
431
432 static int got_alrm;
433 static void
434 alrm_handler(int i) {
435         got_alrm = 1;
436 }
437
438 void query_log(SQL_INST * inst, char *querystr) {
439         FILE   *sqlfile = NULL;
440
441         if (inst->config->sqltrace) {
442                 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
443                         radlog(L_ERR, "rlm_sql: Couldn't open file %s",
444                                         inst->config->tracefile);
445                 } else {
446                         int fd = fileno(sqlfile);
447                         
448                         rad_lockfd(fd, MAX_QUERY_LEN);
449                         fputs(querystr, sqlfile);
450                         fputs(";\n", sqlfile);
451                         fclose(sqlfile); /* and release the lock */
452                 }
453         }
454 }