document rlm_otp fd leak fix
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_unixodbc / sql_unixodbc.c
1 /*
2  * sql_unixodbc.c       unixODBC rlm_sql driver
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the Free Software
16  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  * Copyright 2000  The FreeRADIUS server project
19  * Copyright 2000  Dmitri Ageev <d_ageev@ortcc.ru>
20  */
21
22 #include <freeradius-devel/autoconf.h>
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <freeradius-devel/radiusd.h>
27
28 #include <sqltypes.h>
29 #include "rlm_sql.h"
30
31 typedef struct rlm_sql_unixodbc_sock {
32         SQLHENV env_handle;
33         SQLHDBC dbc_handle;
34         SQLHSTMT stmt_handle;
35         SQL_ROW row;
36         void *conn;
37 } rlm_sql_unixodbc_sock;
38
39
40 #include <sql.h>
41 #include <sqlext.h>
42
43 /* Forward declarations */
44 static char *sql_error(SQLSOCK *sqlsocket, SQL_CONFIG *config);
45 static int sql_state(long err_handle, SQLSOCK *sqlsocket, SQL_CONFIG *config);
46 static int sql_free_result(SQLSOCK *sqlsocket, SQL_CONFIG *config);
47 static int sql_affected_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config);
48 static int sql_num_fields(SQLSOCK *sqlsocket, SQL_CONFIG *config);
49
50
51 /*************************************************************************
52  *
53  *      Function: sql_init_socket
54  *
55  *      Purpose: Establish connection to the db
56  *
57  *************************************************************************/
58 static int sql_init_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
59     rlm_sql_unixodbc_sock *unixodbc_sock;
60     long err_handle;
61
62         if (!sqlsocket->conn) {
63                 sqlsocket->conn = (rlm_sql_unixodbc_sock *)rad_malloc(sizeof(rlm_sql_unixodbc_sock));
64                 if (!sqlsocket->conn) {
65                         return -1;
66                 }
67         }
68         unixodbc_sock = sqlsocket->conn;
69         memset(unixodbc_sock, 0, sizeof(*unixodbc_sock));
70
71     /* 1. Allocate environment handle and register version */
72     err_handle = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&unixodbc_sock->env_handle);
73     if (sql_state(err_handle, sqlsocket, config))
74     {
75         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate environment handle\n");
76         return -1;
77     }
78     err_handle = SQLSetEnvAttr(unixodbc_sock->env_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
79     if (sql_state(err_handle, sqlsocket, config))
80     {
81         radlog(L_ERR, "rlm_sql_unixodbc: Can't register ODBC version\n");
82         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
83         return -1;
84     }
85     /* 2. Allocate connection handle */
86     err_handle = SQLAllocHandle(SQL_HANDLE_DBC, unixodbc_sock->env_handle, &unixodbc_sock->dbc_handle);
87     if (sql_state(err_handle, sqlsocket, config))
88     {
89         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate connection handle\n");
90         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
91         return -1;
92     }
93
94     /* 3. Connect to the datasource */
95     err_handle = SQLConnect(unixodbc_sock->dbc_handle,
96         (SQLCHAR*) config->sql_server, strlen(config->sql_server),
97         (SQLCHAR*) config->sql_login, strlen(config->sql_login),
98         (SQLCHAR*) config->sql_password, strlen(config->sql_password));
99     if (sql_state(err_handle, sqlsocket, config))
100     {
101         radlog(L_ERR, "rlm_sql_unixodbc: Connection failed\n");
102         SQLFreeHandle(SQL_HANDLE_DBC, unixodbc_sock->dbc_handle);
103         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
104         return -1;
105     }
106
107     /* 4. Allocate the statement */
108     err_handle = SQLAllocStmt(unixodbc_sock->dbc_handle, &unixodbc_sock->stmt_handle);
109     if (sql_state(err_handle, sqlsocket, config))
110     {
111         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate the statement\n");
112         return -1;
113     }
114
115     return 0;
116 }
117
118
119 /*************************************************************************
120  *
121  *      Function: sql_destroy_socket
122  *
123  *      Purpose: Free socket and private connection data
124  *
125  *************************************************************************/
126 static int sql_destroy_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config)
127 {
128         free(sqlsocket->conn);
129         sqlsocket->conn = NULL;
130         return 0;
131 }
132
133
134 /*************************************************************************
135  *
136  *      Function: sql_query
137  *
138  *      Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
139  *               the database.
140  *
141  *************************************************************************/
142 static int sql_query(SQLSOCK *sqlsocket, SQL_CONFIG *config, char *querystr) {
143     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
144     long err_handle;
145     int state;
146
147     if (config->sqltrace)
148         radlog(L_DBG, "query:  %s", querystr);
149
150     /* Executing query */
151     err_handle = SQLExecDirect(unixodbc_sock->stmt_handle, (SQLCHAR *)querystr, strlen(querystr));
152     if ((state = sql_state(err_handle, sqlsocket, config))) {
153         if(state == SQL_DOWN)
154             radlog(L_INFO, "rlm_sql_unixodbc: rlm_sql will attempt to reconnect\n");
155         return state;
156     }
157     return 0;
158 }
159
160
161 /*************************************************************************
162  *
163  *      Function: sql_select_query
164  *
165  *      Purpose: Issue a select query to the database
166  *
167  *************************************************************************/
168 static int sql_select_query(SQLSOCK *sqlsocket, SQL_CONFIG *config, char *querystr) {
169     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
170     SQLINTEGER column, len;
171     int numfields;
172     int state;
173
174     /* Only state = 0 means success */
175     if((state = sql_query(sqlsocket, config, querystr)))
176         return state;
177
178     numfields=sql_num_fields(sqlsocket, config);
179     if(numfields < 0)
180         return -1;
181
182     /* Reserving memory for result */
183     unixodbc_sock->row = (char **) rad_malloc((numfields+1)*sizeof(char *));
184     unixodbc_sock->row[numfields] = NULL;
185
186     for(column=1; column<=numfields; column++) {
187         SQLColAttributes(unixodbc_sock->stmt_handle,((SQLUSMALLINT) column),SQL_COLUMN_LENGTH,NULL,0,NULL,&len);
188         unixodbc_sock->row[column-1] = (SQLCHAR*)rad_malloc((int)++len);
189         SQLBindCol(unixodbc_sock->stmt_handle, column, SQL_C_CHAR, (SQLCHAR *)unixodbc_sock->row[column-1], len, NULL);
190     }
191         return 0;
192 }
193
194
195 /*************************************************************************
196  *
197  *      Function: sql_store_result
198  *
199  *      Purpose: database specific store_result function. Returns a result
200  *               set for the query.
201  *
202  *************************************************************************/
203 static int sql_store_result(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
204   /* Not used */
205     return 0;
206 }
207
208
209 /*************************************************************************
210  *
211  *      Function: sql_num_fields
212  *
213  *      Purpose: database specific num_fields function. Returns number
214  *               of columns from query
215  *
216  *************************************************************************/
217 static int sql_num_fields(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
218     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
219     long err_handle;
220     int num_fields = 0;
221
222     err_handle = SQLNumResultCols(unixodbc_sock->stmt_handle,(SQLSMALLINT *)&num_fields);
223     if (sql_state(err_handle, sqlsocket, config))
224         return -1;
225
226     return num_fields;
227 }
228
229
230 /*************************************************************************
231  *
232  *      Function: sql_num_rows
233  *
234  *      Purpose: database specific num_rows. Returns number of rows in
235  *               query
236  *
237  *************************************************************************/
238 static int sql_num_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
239     return sql_affected_rows(sqlsocket, config);
240 }
241
242
243 /*************************************************************************
244  *
245  *      Function: sql_fetch_row
246  *
247  *      Purpose: database specific fetch_row. Returns a SQL_ROW struct
248  *               with all the data for the query in 'sqlsocket->row'. Returns
249  *               0 on success, -1 on failure, SQL_DOWN if 'database is down'.
250  *
251  *************************************************************************/
252 static int sql_fetch_row(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
253     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
254     long err_handle;
255     int state;
256
257     sqlsocket->row = NULL;
258
259     err_handle = SQLFetch(unixodbc_sock->stmt_handle);
260     if(err_handle == SQL_NO_DATA_FOUND)
261         return 0;
262     if ((state = sql_state(err_handle, sqlsocket, config))) {
263         if(state == SQL_DOWN)
264             radlog(L_INFO, "rlm_sql_unixodbc: rlm_sql will attempt to reconnect\n");
265         return state;
266     }
267     sqlsocket->row = unixodbc_sock->row;
268     return 0;
269 }
270
271
272 /*************************************************************************
273  *
274  *      Function: sql_finish_select_query
275  *
276  *      Purpose: End the select query, such as freeing memory or result
277  *
278  *************************************************************************/
279 static int sql_finish_select_query(SQLSOCK * sqlsocket, SQL_CONFIG *config) {
280     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
281
282     sql_free_result(sqlsocket, config);
283     SQLFreeStmt(unixodbc_sock->stmt_handle, SQL_CLOSE);
284     return 0;
285 }
286
287 /*************************************************************************
288  *
289  *      Function: sql_finish_query
290  *
291  *      Purpose: End the query, such as freeing memory
292  *
293  *************************************************************************/
294 static int sql_finish_query(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
295     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
296
297     SQLFreeStmt(unixodbc_sock->stmt_handle, SQL_CLOSE);
298     return 0;
299 }
300
301 /*************************************************************************
302  *
303  *      Function: sql_free_result
304  *
305  *      Purpose: database specific free_result. Frees memory allocated
306  *               for a result set
307  *
308  *************************************************************************/
309 static int sql_free_result(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
310     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
311     int column, numfileds=sql_num_fields(sqlsocket, config);
312
313     /* Freeing reserved memory */
314     if(unixodbc_sock->row != NULL) {
315         for(column=0; column<numfileds; column++) {
316             if(unixodbc_sock->row[column] != NULL) {
317                 free(unixodbc_sock->row[column]);
318                 unixodbc_sock->row[column] = NULL;
319             }
320         }
321         free(unixodbc_sock->row);
322         unixodbc_sock->row = NULL;
323     }
324     return 0;
325 }
326
327 /*************************************************************************
328  *
329  *      Function: sql_close
330  *
331  *      Purpose: database specific close. Closes an open database
332  *               connection and cleans up any open handles.
333  *
334  *************************************************************************/
335 static int sql_close(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
336     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
337
338     SQLFreeStmt(unixodbc_sock->stmt_handle, SQL_DROP);
339     SQLDisconnect(unixodbc_sock->dbc_handle);
340     SQLFreeConnect(unixodbc_sock->dbc_handle);
341     SQLFreeEnv(unixodbc_sock->env_handle);
342
343     return 0;
344 }
345
346 /*************************************************************************
347  *
348  *      Function: sql_error
349  *
350  *      Purpose: database specific error. Returns error associated with
351  *               connection
352  *
353  *************************************************************************/
354 static char *sql_error(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
355     SQLCHAR state[256];
356     SQLCHAR error[256];
357     SQLINTEGER errornum = 0;
358     SQLSMALLINT length = 255;
359     static char result[1024];   /* NOT thread-safe! */
360                            
361     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
362
363     error[0] = state[0] = '\0';
364                                
365     SQLError(
366         unixodbc_sock->env_handle,
367         unixodbc_sock->dbc_handle,
368         unixodbc_sock->stmt_handle,
369         state,
370         &errornum,
371         error,
372         256,
373         &length);
374
375     sprintf(result, "%s %s", state, error);
376     result[sizeof(result) - 1] = '\0'; /* catch idiot thread issues */
377     return result;
378 }
379
380 /*************************************************************************
381  *
382  *      Function: sql_state
383  *
384  *      Purpose: Returns 0 for success, SQL_DOWN if the error was
385  *               connection related or -1 for other errors
386  *
387  *************************************************************************/
388 static int sql_state(long err_handle, SQLSOCK *sqlsocket, SQL_CONFIG *config) {
389     SQLCHAR state[256];
390     SQLCHAR error[256];
391     SQLINTEGER errornum = 0;
392     SQLSMALLINT length = 255;
393     int res = -1;
394
395     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
396
397     if(SQL_SUCCEEDED(err_handle))       
398         return 0;               /* on success, just return 0 */
399
400     error[0] = state[0] = '\0';
401
402     SQLError(
403         unixodbc_sock->env_handle,
404         unixodbc_sock->dbc_handle,
405         unixodbc_sock->stmt_handle,
406         state,
407         &errornum,
408         error,
409         256,
410         &length);
411
412     if(state[0] == '0') {
413         switch(state[1]) {
414         case '1':               /* SQLSTATE 01 class contains info and warning messages */
415             radlog(L_INFO, "rlm_sql_unixodbc: %s %s\n", state, error);
416         case '0':               /* SQLSTATE 00 class means success */
417             res = 0;
418             break;
419         case '8':               /* SQLSTATE 08 class describes various connection errors */
420             radlog(L_ERR, "rlm_sql_unixodbc: SQL down %s %s\n", state, error);
421             res = SQL_DOWN;
422             break;
423         default:                /* any other SQLSTATE means error */
424             radlog(L_ERR, "rlm_sql_unixodbc: %s %s\n", state, error);
425             res = -1;
426             break;
427         }
428     }
429
430     return res;
431 }
432
433 /*************************************************************************
434  *
435  *      Function: sql_affected_rows
436  *
437  *      Purpose: Return the number of rows affected by the query (update,
438  *               or insert)
439  *
440  *************************************************************************/
441 static int sql_affected_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
442     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
443     long err_handle;
444     int affected_rows;
445
446     err_handle = SQLRowCount(unixodbc_sock->stmt_handle, (SQLINTEGER *)&affected_rows);
447     if (sql_state(err_handle, sqlsocket, config))
448         return -1;
449
450     return affected_rows;
451 }
452
453
454 /* Exported to rlm_sql */
455 rlm_sql_module_t rlm_sql_unixodbc = {
456         "rlm_sql_unixodbc",
457         sql_init_socket,
458         sql_destroy_socket,
459         sql_query,
460         sql_select_query,
461         sql_store_result,
462         sql_num_fields,
463         sql_num_rows,
464         sql_fetch_row,
465         sql_free_result,
466         sql_error,
467         sql_close,
468         sql_finish_query,
469         sql_finish_select_query,
470         sql_affected_rows
471 };