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