Turn session caching in EAP back on in preparation for 3.0.14 upgrade
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_iodbc / rlm_sql_iodbc.c
1 /*
2  * sql_iodbc.c  iODBC support for FreeRadius
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000,2006  The FreeRADIUS server project
21  * Copyright 2000  Jeff Carneal <jeff@apex.net>
22  */
23
24 RCSID("$Id$")
25 USES_APPLE_DEPRECATED_API
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 #include <sys/stat.h>
31
32 #include <isql.h>
33 #include <isqlext.h>
34 #include <sqltypes.h>
35
36 #include "rlm_sql.h"
37
38 #define IODBC_MAX_ERROR_LEN 256
39
40 typedef struct rlm_sql_iodbc_conn {
41         HENV    env_handle;
42         HDBC    dbc_handle;
43         HSTMT   stmt;
44         int     id;
45
46         rlm_sql_row_t row;
47
48         struct sql_socket *next;
49
50         void    *conn;
51 } rlm_sql_iodbc_conn_t;
52
53 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
54                         rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config);
55 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
56
57 static int _sql_socket_destructor(rlm_sql_iodbc_conn_t *conn)
58 {
59         DEBUG2("rlm_sql_iodbc: Socket destructor called, closing socket");
60
61         if (conn->stmt) SQLFreeStmt(conn->stmt, SQL_DROP);
62
63         if (conn->dbc_handle) {
64                 SQLDisconnect(conn->dbc_handle);
65                 SQLFreeConnect(conn->dbc_handle);
66         }
67
68         if (conn->env_handle) SQLFreeEnv(conn->env_handle);
69
70         return 0;
71 }
72
73 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
74 {
75
76         rlm_sql_iodbc_conn_t *conn;
77         SQLRETURN rcode;
78         sql_log_entry_t entry;
79
80         MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_iodbc_conn_t));
81         talloc_set_destructor(conn, _sql_socket_destructor);
82
83         rcode = SQLAllocEnv(&conn->env_handle);
84         if (!SQL_SUCCEEDED(rcode)) {
85                 ERROR("rlm_sql_iodbc: SQLAllocEnv failed");
86                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
87
88                 return RLM_SQL_ERROR;
89         }
90
91         rcode = SQLAllocConnect(conn->env_handle,
92                                 &conn->dbc_handle);
93         if (!SQL_SUCCEEDED(rcode)) {
94                 ERROR("rlm_sql_iodbc: SQLAllocConnect failed");
95                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
96
97                 return RLM_SQL_ERROR;
98         }
99
100         /*
101          *      The iodbc API doesn't qualify arguments as const even when they should be.
102          */
103         {
104                 SQLCHAR *server, *login, *password;
105
106                 memcpy(&server, &config->sql_server, sizeof(server));
107                 memcpy(&login, &config->sql_login, sizeof(login));
108                 memcpy(&password, &config->sql_password, sizeof(password));
109
110                 rcode = SQLConnect(conn->dbc_handle, server, SQL_NTS, login, SQL_NTS, password, SQL_NTS);
111         }
112         if (!SQL_SUCCEEDED(rcode)) {
113                 ERROR("rlm_sql_iodbc: SQLConnectfailed");
114                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
115
116                 return RLM_SQL_ERROR;
117         }
118
119         return 0;
120 }
121
122 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
123 {
124         rlm_sql_iodbc_conn_t *conn = handle->conn;
125         SQLRETURN rcode;
126
127         rcode = SQLAllocStmt(conn->dbc_handle, &conn->stmt);
128         if (!SQL_SUCCEEDED(rcode)) return RLM_SQL_ERROR;
129
130         if (!conn->dbc_handle) {
131                 ERROR("rlm_sql_iodbc: Socket not connected");
132                 return RLM_SQL_ERROR;
133         }
134
135         {
136                 SQLCHAR *statement;
137
138                 memcpy(&statement, &query, sizeof(statement));
139                 rcode = SQLExecDirect(conn->stmt, statement, SQL_NTS);
140         }
141
142         if (!SQL_SUCCEEDED(rcode)) return RLM_SQL_ERROR;
143
144         return 0;
145 }
146
147 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
148 {
149         int numfields = 0;
150         int i = 0;
151         char **row = NULL;
152         long len = 0;
153         rlm_sql_iodbc_conn_t *conn = handle->conn;
154
155         if (sql_query(handle, config, query) < 0) return RLM_SQL_ERROR;
156
157         numfields = sql_num_fields(handle, config);
158
159         row = (char **) rad_malloc(sizeof(char *) * (numfields+1));
160         memset(row, 0, (sizeof(char *) * (numfields)));
161         row[numfields] = NULL;
162
163         for(i=1; i<=numfields; i++) {
164                 SQLColAttributes(conn->stmt, ((SQLUSMALLINT) i), SQL_COLUMN_LENGTH, NULL, 0, NULL, &len);
165                 len++;
166
167                 /*
168                  * Allocate space for each column
169                  */
170                 row[i - 1] = rad_malloc((size_t) len);
171
172                 /*
173                  * This makes me feel dirty, but, according to Microsoft, it works.
174                  * Any ODBC datatype can be converted to a 'char *' according to
175                  * the following:
176                  *
177                  * http://msdn.microsoft.com/library/psdk/dasdk/odap4o4z.htm
178                  */
179                 SQLBindCol(conn->stmt, i, SQL_C_CHAR, (SQLCHAR *)row[i-1], len, 0);
180         }
181
182         conn->row = row;
183
184         return 0;
185 }
186
187 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
188 {
189
190         SQLSMALLINT count=0;
191         rlm_sql_iodbc_conn_t *conn = handle->conn;
192
193         SQLNumResultCols(conn->stmt, &count);
194
195         return (int)count;
196 }
197
198 static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
199 {
200         rlm_sql_iodbc_conn_t *conn = handle->conn;
201
202         SQLSMALLINT     fields, len, i;
203
204         char const      **names;
205         char            field[128];
206
207         SQLNumResultCols(conn->stmt, &fields);
208         if (fields == 0) return RLM_SQL_ERROR;
209
210         MEM(names = talloc_array(handle, char const *, fields));
211
212         for (i = 0; i < fields; i++) {
213                 char *p;
214
215                 switch (SQLColAttribute(conn->stmt, i, SQL_DESC_BASE_COLUMN_NAME,
216                                         field, sizeof(field), &len, NULL)) {
217                 case SQL_INVALID_HANDLE:
218                 case SQL_ERROR:
219                         ERROR("Failed retrieving field name at index %i", i);
220                         talloc_free(names);
221                         return RLM_SQL_ERROR;
222
223                 default:
224                         break;
225                 }
226
227                 MEM(p = talloc_array(names, char, (size_t)len + 1));
228                 strlcpy(p, field, (size_t)len + 1);
229                 names[i] = p;
230         }
231         *out = names;
232
233         return RLM_SQL_OK;
234 }
235
236 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
237 {
238         SQLRETURN rc;
239         rlm_sql_iodbc_conn_t *conn = handle->conn;
240
241         handle->row = NULL;
242
243         rc = SQLFetch(conn->stmt);
244         if (rc == SQL_NO_DATA_FOUND) return RLM_SQL_NO_MORE_ROWS;
245
246         /* XXX Check rc for database down, if so, return RLM_SQL_RECONNECT */
247
248         handle->row = conn->row;
249         return 0;
250 }
251
252 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
253 {
254         int i = 0;
255         rlm_sql_iodbc_conn_t *conn = handle->conn;
256
257         for (i = 0; i < sql_num_fields(handle, config); i++) free(conn->row[i]);
258         free(conn->row);
259         conn->row = NULL;
260
261         SQLFreeStmt(conn->stmt, SQL_DROP);
262
263         return 0;
264 }
265
266 /** Retrieves any errors associated with the connection handle
267  *
268  * @note Caller will free any memory allocated in ctx.
269  *
270  * @param ctx to allocate temporary error buffers in.
271  * @param out Array of sql_log_entrys to fill.
272  * @param outlen Length of out array.
273  * @param handle rlm_sql connection handle.
274  * @param config rlm_sql config.
275  * @return number of errors written to the sql_log_entry array.
276  */
277 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
278                         rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
279 {
280         rlm_sql_iodbc_conn_t    *conn = handle->conn;
281         SQLINTEGER              errornum = 0;
282         SQLSMALLINT             length = 0;
283         SQLCHAR                 state[256] = "";
284         SQLCHAR                 errbuff[IODBC_MAX_ERROR_LEN];
285
286         rad_assert(outlen > 0);
287
288         errbuff[0] = '\0';
289         SQLError(conn->env_handle, conn->dbc_handle, conn->stmt,
290                  state, &errornum, errbuff, IODBC_MAX_ERROR_LEN, &length);
291         if (errbuff[0] == '\0') return 0;
292
293         out[0].type = L_ERR;
294         out[0].msg = talloc_asprintf(ctx, "%s: %s", state, errbuff);
295
296         return 1;
297 }
298
299 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
300 {
301         return sql_free_result(handle, config);
302 }
303
304 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
305 {
306         return sql_free_result(handle, config);
307 }
308
309 static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
310 {
311         long count;
312         rlm_sql_iodbc_conn_t *conn = handle->conn;
313
314         SQLRowCount(conn->stmt, &count);
315         return (int)count;
316 }
317
318 /* Exported to rlm_sql */
319 extern rlm_sql_module_t rlm_sql_iodbc;
320 rlm_sql_module_t rlm_sql_iodbc = {
321         .name                           = "rlm_sql_iodbc",
322         .sql_socket_init                = sql_socket_init,
323         .sql_query                      = sql_query,
324         .sql_select_query               = sql_select_query,
325         .sql_num_fields                 = sql_num_fields,
326         .sql_affected_rows              = sql_affected_rows,
327         .sql_fields                     = sql_fields,
328         .sql_fetch_row                  = sql_fetch_row,
329         .sql_free_result                = sql_free_result,
330         .sql_error                      = sql_error,
331         .sql_finish_query               = sql_finish_query,
332         .sql_finish_select_query        = sql_finish_select_query
333 };