Merge pull request #11 from amne/master
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * Copyright 2001,2006  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 #include <freeradius-devel/ident.h>
28 RCSID("$Id$")
29
30 #include        <freeradius-devel/radiusd.h>
31
32 #include        <sys/file.h>
33 #include        <sys/stat.h>
34
35 #include        <ctype.h>
36
37 #include        "rlm_sql.h"
38
39 #ifdef HAVE_PTHREAD_H
40 #endif
41
42
43 static void *sql_conn_create(void *ctx)
44 {
45         int rcode;
46         SQL_INST *inst = ctx;
47         SQLSOCK *sqlsocket;
48
49         sqlsocket = rad_malloc(sizeof(*sqlsocket));
50         memset(sqlsocket, 0, sizeof(*sqlsocket));
51
52         rcode = (inst->module->sql_init_socket)(sqlsocket, inst->config);
53         if (rcode == 0) {
54                 exec_trigger(NULL, inst->cs, "modules.sql.open");
55                 return sqlsocket;
56         }
57
58         free(sqlsocket);
59         return NULL;
60 }
61
62
63 static int sql_conn_delete(void *ctx, void *connection)
64 {
65         SQL_INST *inst = ctx;
66         SQLSOCK *sqlsocket = connection;
67
68         exec_trigger(NULL, inst->cs, "modules.sql.close");
69
70         if (sqlsocket->conn) {
71                 (inst->module->sql_close)(sqlsocket, inst->config);
72         }
73         if (inst->module->sql_destroy_socket) {
74                 (inst->module->sql_destroy_socket)(sqlsocket, inst->config);
75         }
76         free(sqlsocket);
77
78         return 0;
79 }
80
81
82 /*************************************************************************
83  *
84  *      Function: sql_init_socketpool
85  *
86  *      Purpose: Connect to the sql server, if possible
87  *
88  *************************************************************************/
89 int sql_init_socketpool(SQL_INST * inst)
90 {
91         inst->pool = fr_connection_pool_init(inst->cs, inst,
92                                              sql_conn_create,
93                                              NULL,
94                                              sql_conn_delete);
95         if (!inst->pool) return -1;
96
97         return 1;
98 }
99
100 /*************************************************************************
101  *
102  *     Function: sql_poolfree
103  *
104  *     Purpose: Clean up and free sql pool
105  *
106  *************************************************************************/
107 void sql_poolfree(SQL_INST * inst)
108 {
109         fr_connection_pool_delete(inst->pool);
110 }
111
112
113 /*************************************************************************
114  *
115  *      Function: sql_get_socket
116  *
117  *      Purpose: Return a SQL sqlsocket from the connection pool
118  *
119  *************************************************************************/
120 SQLSOCK * sql_get_socket(SQL_INST * inst)
121 {
122         return fr_connection_get(inst->pool);
123 }
124
125 /*************************************************************************
126  *
127  *      Function: sql_release_socket
128  *
129  *      Purpose: Frees a SQL sqlsocket back to the connection pool
130  *
131  *************************************************************************/
132 int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket)
133 {
134         fr_connection_release(inst->pool, sqlsocket);
135         return 0;
136 }
137
138
139 /*************************************************************************
140  *
141  *      Function: sql_userparse
142  *
143  *      Purpose: Read entries from the database and fill VALUE_PAIR structures
144  *
145  *************************************************************************/
146 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row)
147 {
148         VALUE_PAIR *pair;
149         const char *ptr, *value;
150         char buf[MAX_STRING_LEN];
151         char do_xlat = 0;
152         FR_TOKEN token, operator = T_EOL;
153
154         /*
155          *      Verify the 'Attribute' field
156          */
157         if (row[2] == NULL || row[2][0] == '\0') {
158                 radlog(L_ERR, "rlm_sql: The 'Attribute' field is empty or NULL, skipping the entire row.");
159                 return -1;
160         }
161
162         /*
163          *      Verify the 'op' field
164          */
165         if (row[4] != NULL && row[4][0] != '\0') {
166                 ptr = row[4];
167                 operator = gettoken(&ptr, buf, sizeof(buf));
168                 if ((operator < T_OP_ADD) ||
169                     (operator > T_OP_CMP_EQ)) {
170                         radlog(L_ERR, "rlm_sql: Invalid operator \"%s\" for attribute %s", row[4], row[2]);
171                         return -1;
172                 }
173
174         } else {
175                 /*
176                  *  Complain about empty or invalid 'op' field
177                  */
178                 operator = T_OP_CMP_EQ;
179                 radlog(L_ERR, "rlm_sql: The 'op' field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
180                 radlog(L_ERR, "rlm_sql: You MUST FIX THIS if you want the configuration to behave as you expect.");
181         }
182
183         /*
184          *      The 'Value' field may be empty or NULL
185          */
186         value = row[3];
187         /*
188          *      If we have a new-style quoted string, where the
189          *      *entire* string is quoted, do xlat's.
190          */
191         if (row[3] != NULL &&
192            ((row[3][0] == '\'') || (row[3][0] == '`') || (row[3][0] == '"')) &&
193            (row[3][0] == row[3][strlen(row[3])-1])) {
194
195                 token = gettoken(&value, buf, sizeof(buf));
196                 switch (token) {
197                         /*
198                          *      Take the unquoted string.
199                          */
200                 case T_SINGLE_QUOTED_STRING:
201                 case T_DOUBLE_QUOTED_STRING:
202                         value = buf;
203                         break;
204
205                         /*
206                          *      Mark the pair to be allocated later.
207                          */
208                 case T_BACK_QUOTED_STRING:
209                         value = NULL;
210                         do_xlat = 1;
211                         break;
212
213                         /*
214                          *      Keep the original string.
215                          */
216                 default:
217                         value = row[3];
218                         break;
219                 }
220         }
221
222         /*
223          *      Create the pair
224          */
225         pair = pairmake(row[2], value, operator);
226         if (pair == NULL) {
227                 radlog(L_ERR, "rlm_sql: Failed to create the pair: %s", fr_strerror());
228                 return -1;
229         }
230         if (do_xlat) {
231                 pair->flags.do_xlat = 1;
232                 strlcpy(pair->vp_strvalue, buf, sizeof(pair->vp_strvalue));
233                 pair->length = 0;
234         }
235
236         /*
237          *      Add the pair into the packet
238          */
239         pairadd(first_pair, pair);
240         return 0;
241 }
242
243
244 /*************************************************************************
245  *
246  *      Function: rlm_sql_fetch_row
247  *
248  *      Purpose: call the module's sql_fetch_row and implement re-connect
249  *
250  *************************************************************************/
251 int rlm_sql_fetch_row(SQLSOCK *sqlsocket, SQL_INST *inst)
252 {
253         int ret;
254
255         if (sqlsocket->conn) {
256                 ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
257         } else {
258                 ret = SQL_DOWN;
259         }
260
261         if (ret == SQL_DOWN) {
262                 sqlsocket = fr_connection_reconnect(inst->pool, sqlsocket);
263                 if (!sqlsocket) return -1;
264
265                 /* retry the query on the newly connected socket */
266                 ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
267
268                 if (ret) {
269                         radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
270                                inst->config->xlat_name);
271                         return -1;
272                 }
273         }
274
275         return ret;
276 }
277
278 /*************************************************************************
279  *
280  *      Function: rlm_sql_query
281  *
282  *      Purpose: call the module's sql_query and implement re-connect
283  *
284  *************************************************************************/
285 int rlm_sql_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query)
286 {
287         int ret;
288
289         /*
290          *      If there's no query, return an error.
291          */
292         if (!query || !*query) {
293                 return -1;
294         }
295
296         if (sqlsocket->conn) {
297                 ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
298         } else {
299                 ret = SQL_DOWN;
300         }
301
302         if (ret == SQL_DOWN) {
303                 sqlsocket = fr_connection_reconnect(inst->pool, sqlsocket);
304                 if (!sqlsocket) return -1;
305
306                 /* retry the query on the newly connected socket */
307                 ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
308
309                 if (ret) {
310                         radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
311                                inst->config->xlat_name);
312                         return -1;
313                 }
314         }
315
316         return ret;
317 }
318
319 /*************************************************************************
320  *
321  *      Function: rlm_sql_select_query
322  *
323  *      Purpose: call the module's sql_select_query and implement re-connect
324  *
325  *************************************************************************/
326 int rlm_sql_select_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query)
327 {
328         int ret;
329
330         /*
331          *      If there's no query, return an error.
332          */
333         if (!query || !*query) {
334                 return -1;
335         }
336
337         if (sqlsocket->conn) {
338                 ret = (inst->module->sql_select_query)(sqlsocket, inst->config,
339                                                        query);
340         } else {
341                 ret = SQL_DOWN;
342         }
343
344         if (ret == SQL_DOWN) {
345                 sqlsocket = fr_connection_reconnect(inst->pool, sqlsocket);
346                 if (!sqlsocket) return -1;
347
348                 /* retry the query on the newly connected socket */
349                 ret = (inst->module->sql_select_query)(sqlsocket, inst->config, query);
350
351                 if (ret) {
352                         radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
353                                inst->config->xlat_name);
354                         return -1;
355                 }
356         }
357
358         return ret;
359 }
360
361
362 /*************************************************************************
363  *
364  *      Function: sql_getvpdata
365  *
366  *      Purpose: Get any group check or reply pairs
367  *
368  *************************************************************************/
369 int sql_getvpdata(SQL_INST * inst, SQLSOCK * sqlsocket, VALUE_PAIR **pair, char *query)
370 {
371         SQL_ROW row;
372         int     rows = 0;
373
374         if (rlm_sql_select_query(sqlsocket, inst, query)) {
375                 radlog(L_ERR, "rlm_sql_getvpdata: database query error");
376                 return -1;
377         }
378         while (rlm_sql_fetch_row(sqlsocket, inst)==0) {
379                 row = sqlsocket->row;
380                 if (!row)
381                         break;
382                 if (sql_userparse(pair, row) != 0) {
383                         radlog(L_ERR | L_CONS, "rlm_sql (%s): Error getting data from database", inst->config->xlat_name);
384                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
385                         return -1;
386                 }
387                 rows++;
388         }
389         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
390
391         return rows;
392 }
393
394 void query_log(REQUEST *request, SQL_INST *inst, char *querystr)
395 {
396         FILE   *sqlfile = NULL;
397
398         if (inst->config->sqltrace) {
399                 char buffer[8192];
400
401                 if (!radius_xlat(buffer, sizeof(buffer),
402                                  inst->config->tracefile, request, NULL)) {
403                   radlog(L_ERR, "rlm_sql (%s): xlat failed.",
404                          inst->config->xlat_name);
405                   return;
406                 }
407
408                 if ((sqlfile = fopen(buffer, "a")) == (FILE *) NULL) {
409                         radlog(L_ERR, "rlm_sql (%s): Couldn't open file %s",
410                                inst->config->xlat_name,
411                                buffer);
412                 } else {
413                         int fd = fileno(sqlfile);
414
415                         rad_lockfd(fd, MAX_QUERY_LEN);
416                         fputs(querystr, sqlfile);
417                         fputs(";\n", sqlfile);
418                         fclose(sqlfile); /* and release the lock */
419                 }
420         }
421 }