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