Fixup talloc destructors
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_unixodbc / rlm_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 RCSID("$Id$")
23
24 #include <freeradius-devel/radiusd.h>
25
26 #include <sqltypes.h>
27 #include "rlm_sql.h"
28
29 typedef struct rlm_sql_unixodbc_conn {
30         SQLHENV env;
31         SQLHDBC dbc;
32         SQLHSTMT statement;
33         rlm_sql_row_t row;
34         void *conn;
35 } rlm_sql_unixodbc_conn_t;
36
37
38 USES_APPLE_DEPRECATED_API
39 #include <sql.h>
40 #include <sqlext.h>
41
42 /* Forward declarations */
43 static char const *sql_error(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
44 static int sql_state(long err_handle, rlm_sql_handle_t *handle, rlm_sql_config_t *config);
45 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
46 static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
47 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
48
49 static int _sql_socket_destructor(rlm_sql_unixodbc_conn_t *conn)
50 {
51         DEBUG2("rlm_sql_unixodbc: Socket destructor called, closing socket");
52
53         if (conn->statement) {
54                 SQLFreeStmt(conn->statement, SQL_DROP);
55                 conn->statement = NULL;
56         }
57
58         if (conn->dbc) {
59                 SQLDisconnect(conn->dbc);
60                 SQLFreeConnect(conn->dbc);
61                 conn->dbc = NULL;
62         }
63
64         if (conn->env) {
65                 SQLFreeEnv(conn->env);
66                 conn->env = NULL;
67         }
68
69         return 0;
70 }
71
72 /*************************************************************************
73  *
74  *      Function: sql_socket_init
75  *
76  *      Purpose: Establish connection to the db
77  *
78  *************************************************************************/
79 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
80         rlm_sql_unixodbc_conn_t *conn;
81         long err_handle;
82
83         MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_unixodbc_conn_t));
84         talloc_set_destructor(conn, _sql_socket_destructor);
85
86         /* 1. Allocate environment handle and register version */
87         err_handle = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &conn->env);
88         if (sql_state(err_handle, handle, config)) {
89                 ERROR("rlm_sql_unixodbc: Can't allocate environment handle");
90                 return -1;
91         }
92
93         err_handle = SQLSetEnvAttr(conn->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
94         if (sql_state(err_handle, handle, config)) {
95                 ERROR("rlm_sql_unixodbc: Can't register ODBC version");
96                 return -1;
97         }
98
99         /* 2. Allocate connection handle */
100         err_handle = SQLAllocHandle(SQL_HANDLE_DBC, conn->env, &conn->dbc);
101         if (sql_state(err_handle, handle, config)) {
102                 ERROR("rlm_sql_unixodbc: Can't allocate connection handle");
103                 return -1;
104         }
105
106         /* 3. Connect to the datasource */
107         {
108                 SQLCHAR *odbc_server, *odbc_login, *odbc_password;
109
110                 memcpy(&odbc_server, &config->sql_server, sizeof(odbc_server));
111                 memcpy(&odbc_login, &config->sql_login, sizeof(odbc_login));
112                 memcpy(&odbc_password, &config->sql_password, sizeof(odbc_password));
113                 err_handle = SQLConnect(conn->dbc,
114                                         odbc_server, strlen(config->sql_server),
115                                         odbc_login, strlen(config->sql_login),
116                                         odbc_password, strlen(config->sql_password));
117         }
118
119         if (sql_state(err_handle, handle, config)) {
120                 ERROR("rlm_sql_unixodbc: Connection failed");
121                 return -1;
122         }
123
124         /* 4. Allocate the statement */
125         err_handle = SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &conn->statement);
126         if (sql_state(err_handle, handle, config)) {
127                 ERROR("rlm_sql_unixodbc: Can't allocate the statement");
128                 return -1;
129         }
130
131     return 0;
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 sql_rcode_t sql_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) {
143         rlm_sql_unixodbc_conn_t *conn = handle->conn;
144         long err_handle;
145         int state;
146
147         /* Executing query */
148         {
149                 SQLCHAR *odbc_query;
150
151                 memcpy(&odbc_query, &query, sizeof(odbc_query));
152                 err_handle = SQLExecDirect(conn->statement, odbc_query, strlen(query));
153         }
154         if ((state = sql_state(err_handle, handle, config))) {
155                 if(state == RLM_SQL_RECONNECT) {
156                         DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
157                 }
158                 return state;
159         }
160         return 0;
161 }
162
163
164 /*************************************************************************
165  *
166  *      Function: sql_select_query
167  *
168  *      Purpose: Issue a select query to the database
169  *
170  *************************************************************************/
171 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) {
172         rlm_sql_unixodbc_conn_t *conn = handle->conn;
173         SQLINTEGER i;
174         SQLLEN len;
175         int colcount;
176         int state;
177
178         /* Only state = 0 means success */
179         if ((state = sql_query(handle, config, query))) {
180                 return state;
181         }
182
183         colcount = sql_num_fields(handle, config);
184         if (colcount < 0) {
185                 return -1;
186         }
187
188         /* Reserving memory for result */
189         conn->row = talloc_zero_array(conn, char *, colcount + 1); /* Space for pointers */
190
191         for (i = 1; i <= colcount; i++) {
192                 SQLColAttributes(conn->statement, ((SQLUSMALLINT) i), SQL_COLUMN_LENGTH, NULL, 0, NULL, &len);
193                 conn->row[i - 1] = talloc_array(conn->row, char, ++len);
194                 SQLBindCol(conn->statement, i, SQL_C_CHAR, (SQLCHAR *)conn->row[i - 1], len, NULL);
195         }
196
197         return 0;
198 }
199
200
201 /*************************************************************************
202  *
203  *      Function: sql_store_result
204  *
205  *      Purpose: database specific store_result function. Returns a result
206  *             set for the query.
207  *
208  *************************************************************************/
209 static sql_rcode_t sql_store_result(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) {
210         /* Not used */
211         return 0;
212 }
213
214
215 /*************************************************************************
216  *
217  *      Function: sql_num_fields
218  *
219  *      Purpose: database specific num_fields function. Returns number
220  *             of columns from query
221  *
222  *************************************************************************/
223 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
224         rlm_sql_unixodbc_conn_t *conn = handle->conn;
225         long err_handle;
226         SQLSMALLINT num_fields = 0;
227
228         err_handle = SQLNumResultCols(conn->statement,&num_fields);
229         if (sql_state(err_handle, handle, config)) {
230                 return -1;
231         }
232
233         return num_fields;
234 }
235
236
237 /*************************************************************************
238  *
239  *      Function: sql_num_rows
240  *
241  *      Purpose: database specific num_rows. Returns number of rows in
242  *             query
243  *
244  *************************************************************************/
245 static int sql_num_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
246         return sql_affected_rows(handle, config);
247 }
248
249
250 /*************************************************************************
251  *
252  *      Function: sql_fetch_row
253  *
254  *      Purpose: database specific fetch_row. Returns a rlm_sql_row_t struct
255  *             with all the data for the query in 'handle->row'. Returns
256  *               0 on success, -1 on failure, RLM_SQL_RECONNECT if 'database is down'.
257  *
258  *************************************************************************/
259 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
260         rlm_sql_unixodbc_conn_t *conn = handle->conn;
261         long err_handle;
262         int state;
263
264         handle->row = NULL;
265
266         err_handle = SQLFetch(conn->statement);
267         if(err_handle == SQL_NO_DATA_FOUND) {
268                 return 0;
269         }
270
271         if ((state = sql_state(err_handle, handle, config))) {
272                 if(state == RLM_SQL_RECONNECT) {
273                         DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
274                 }
275
276                 return state;
277         }
278
279         handle->row = conn->row;
280         return 0;
281 }
282
283
284 /*************************************************************************
285  *
286  *      Function: sql_finish_select_query
287  *
288  *      Purpose: End the select query, such as freeing memory or result
289  *
290  *************************************************************************/
291 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t * handle, rlm_sql_config_t *config) {
292         rlm_sql_unixodbc_conn_t *conn = handle->conn;
293
294         sql_free_result(handle, config);
295
296         /*
297          *      SQL_CLOSE - The cursor (if any) associated with the statement
298          *      handle (StatementHandle) is closed and all pending results are
299          *      discarded. The application can reopen the cursor by calling
300          *      SQLExecute() with the same or different values in the
301          *      application variables (if any) that are bound to StatementHandle.
302          *      If no cursor has been associated with the statement handle,
303          *      this option has no effect (no warning or error is generated).
304          *
305          *      So, this call does NOT free the statement at all, it merely
306          *      resets it for the next call. This is terrible terrible naming.
307          */
308         SQLFreeStmt(conn->statement, SQL_CLOSE);
309
310         return 0;
311 }
312
313 /*************************************************************************
314  *
315  *      Function: sql_finish_query
316  *
317  *      Purpose: End the query, such as freeing memory
318  *
319  *************************************************************************/
320 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) {
321         rlm_sql_unixodbc_conn_t *conn = handle->conn;
322
323         SQLFreeStmt(conn->statement, SQL_CLOSE);
324
325         return 0;
326 }
327
328 /*************************************************************************
329  *
330  *      Function: sql_free_result
331  *
332  *      Purpose: database specific free_result. Frees memory allocated
333  *             for a result set
334  *
335  *************************************************************************/
336 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) {
337         rlm_sql_unixodbc_conn_t *conn = handle->conn;
338
339         TALLOC_FREE(conn->row);
340
341         return 0;
342 }
343
344 /*************************************************************************
345  *
346  *      Function: sql_error
347  *
348  *      Purpose: database specific error. Returns error associated with
349  *             connection
350  *
351  *************************************************************************/
352 static char const *sql_error(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) {
353         SQLCHAR state[256];
354         SQLCHAR error[256];
355         SQLINTEGER errornum = 0;
356         SQLSMALLINT length = 255;
357         static char result[1024];       /* NOT thread-safe! */
358
359         rlm_sql_unixodbc_conn_t *conn = handle->conn;
360
361         error[0] = state[0] = '\0';
362
363         SQLError(conn->env, conn->dbc, conn->statement, state, &errornum,
364                  error, 256, &length);
365
366         sprintf(result, "%s %s", state, error);
367         result[sizeof(result) - 1] = '\0'; /* catch idiot thread issues */
368         return result;
369 }
370
371 /*************************************************************************
372  *
373  *      Function: sql_state
374  *
375  *      Purpose: Returns 0 for success, RLM_SQL_RECONNECT if the error was
376  *             connection related or -1 for other errors
377  *
378  *************************************************************************/
379 static sql_rcode_t sql_state(long err_handle, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) {
380         SQLCHAR state[256];
381         SQLCHAR error[256];
382         SQLINTEGER errornum = 0;
383         SQLSMALLINT length = 255;
384         int res = -1;
385
386         rlm_sql_unixodbc_conn_t *conn = handle->conn;
387
388         if(SQL_SUCCEEDED(err_handle)) {
389                 return 0;               /* on success, just return 0 */
390         }
391
392         error[0] = state[0] = '\0';
393
394         SQLError(conn->env, conn->dbc, conn->statement, state, &errornum,
395                  error, 256, &length);
396
397         if(state[0] == '0') {
398                 switch(state[1]) {
399                 /* SQLSTATE 01 class contains info and warning messages */
400                 case '1':
401                         INFO("rlm_sql_unixodbc: %s %s", state, error);
402                         /* FALL-THROUGH */
403                 case '0':               /* SQLSTATE 00 class means success */
404                         res = 0;
405                         break;
406
407                 /* SQLSTATE 08 class describes various connection errors */
408                 case '8':
409                         ERROR("rlm_sql_unixodbc: SQL down %s %s", state, error);
410                         res = RLM_SQL_RECONNECT;
411                         break;
412
413                 /* any other SQLSTATE means error */
414                 default:
415                         ERROR("rlm_sql_unixodbc: %s %s", state, error);
416                         res = -1;
417                         break;
418                 }
419         }
420
421         return res;
422 }
423
424 /*************************************************************************
425  *
426  *      Function: sql_affected_rows
427  *
428  *      Purpose: Return the number of rows affected by the query (update,
429  *             or insert)
430  *
431  *************************************************************************/
432 static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
433         rlm_sql_unixodbc_conn_t *conn = handle->conn;
434         long err_handle;
435         SQLLEN affected_rows;
436
437         err_handle = SQLRowCount(conn->statement, &affected_rows);
438         if (sql_state(err_handle, handle, config)) {
439                 return -1;
440         }
441
442         return affected_rows;
443 }
444
445
446 /* Exported to rlm_sql */
447 rlm_sql_module_t rlm_sql_unixodbc = {
448         "rlm_sql_unixodbc",
449         NULL,
450         sql_socket_init,
451         sql_query,
452         sql_select_query,
453         sql_store_result,
454         sql_num_fields,
455         sql_num_rows,
456         sql_fetch_row,
457         sql_free_result,
458         sql_error,
459         sql_finish_query,
460         sql_finish_select_query,
461         sql_affected_rows
462 };