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