Merge pull request #983 from michael-mri/v3.0.x
[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) {
62                 SQLFreeStmt(conn->stmt, SQL_DROP);
63         }
64
65         if (conn->dbc_handle) {
66                 SQLDisconnect(conn->dbc_handle);
67                 SQLFreeConnect(conn->dbc_handle);
68         }
69
70         if (conn->env_handle) {
71                 SQLFreeEnv(conn->env_handle);
72         }
73
74         return 0;
75 }
76
77 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
78 {
79
80         rlm_sql_iodbc_conn_t *conn;
81         SQLRETURN rcode;
82         sql_log_entry_t entry;
83
84         MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_iodbc_conn_t));
85         talloc_set_destructor(conn, _sql_socket_destructor);
86
87         rcode = SQLAllocEnv(&conn->env_handle);
88         if (!SQL_SUCCEEDED(rcode)) {
89                 ERROR("rlm_sql_iodbc: SQLAllocEnv failed");
90                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
91
92                 return RLM_SQL_ERROR;
93         }
94
95         rcode = SQLAllocConnect(conn->env_handle,
96                                 &conn->dbc_handle);
97         if (!SQL_SUCCEEDED(rcode)) {
98                 ERROR("rlm_sql_iodbc: SQLAllocConnect failed");
99                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
100
101                 return RLM_SQL_ERROR;
102         }
103
104         /*
105          *      The iodbc API doesn't qualify arguments as const even when they should be.
106          */
107         {
108                 SQLCHAR *server, *login, *password;
109
110                 memcpy(&server, &config->sql_server, sizeof(server));
111                 memcpy(&login, &config->sql_login, sizeof(login));
112                 memcpy(&password, &config->sql_password, sizeof(password));
113
114                 rcode = SQLConnect(conn->dbc_handle, server, SQL_NTS, login, SQL_NTS, password, SQL_NTS);
115         }
116         if (!SQL_SUCCEEDED(rcode)) {
117                 ERROR("rlm_sql_iodbc: SQLConnectfailed");
118                 if (sql_error(NULL, &entry, 1, handle, config) > 0) ERROR("rlm_sql_iodbc: %s", entry.msg);
119
120                 return RLM_SQL_ERROR;
121         }
122
123         return 0;
124 }
125
126 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
127 {
128         rlm_sql_iodbc_conn_t *conn = handle->conn;
129         SQLRETURN rcode;
130
131         rcode = SQLAllocStmt(conn->dbc_handle, &conn->stmt);
132         if (!SQL_SUCCEEDED(rcode)) return RLM_SQL_ERROR;
133
134         if (!conn->dbc_handle) {
135                 ERROR("rlm_sql_iodbc: Socket not connected");
136                 return RLM_SQL_ERROR;
137         }
138
139         {
140                 SQLCHAR *statement;
141
142                 memcpy(&statement, &query, sizeof(statement));
143                 rcode = SQLExecDirect(conn->stmt, statement, SQL_NTS);
144         }
145
146         if (!SQL_SUCCEEDED(rcode)) return RLM_SQL_ERROR;
147
148         return 0;
149 }
150
151 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
152 {
153         int numfields = 0;
154         int i = 0;
155         char **row = NULL;
156         long len = 0;
157         rlm_sql_iodbc_conn_t *conn = handle->conn;
158
159         if (sql_query(handle, config, query) < 0) return RLM_SQL_ERROR;
160
161         numfields = sql_num_fields(handle, config);
162
163         row = (char **) rad_malloc(sizeof(char *) * (numfields+1));
164         memset(row, 0, (sizeof(char *) * (numfields)));
165         row[numfields] = NULL;
166
167         for(i=1; i<=numfields; i++) {
168                 SQLColAttributes(conn->stmt, ((SQLUSMALLINT) i), SQL_COLUMN_LENGTH, NULL, 0, NULL, &len);
169                 len++;
170
171                 /*
172                  * Allocate space for each column
173                  */
174                 row[i - 1] = rad_malloc((size_t) len);
175
176                 /*
177                  * This makes me feel dirty, but, according to Microsoft, it works.
178                  * Any ODBC datatype can be converted to a 'char *' according to
179                  * the following:
180                  *
181                  * http://msdn.microsoft.com/library/psdk/dasdk/odap4o4z.htm
182                  */
183                 SQLBindCol(conn->stmt, i, SQL_C_CHAR, (SQLCHAR *)row[i-1], len, 0);
184         }
185
186         conn->row = row;
187
188         return 0;
189 }
190
191 static sql_rcode_t sql_store_result(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
192 {
193         return 0;
194 }
195
196 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
197 {
198
199         SQLSMALLINT count=0;
200         rlm_sql_iodbc_conn_t *conn = handle->conn;
201
202         SQLNumResultCols(conn->stmt, &count);
203
204         return (int)count;
205 }
206
207 static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
208 {
209         rlm_sql_iodbc_conn_t *conn = handle->conn;
210
211         SQLSMALLINT     fields, len, i;
212
213         char const      **names;
214         char            field[128];
215
216         SQLNumResultCols(conn->stmt, &fields);
217         if (fields == 0) return RLM_SQL_ERROR;
218
219         MEM(names = talloc_array(handle, char const *, fields));
220
221         for (i = 0; i < fields; i++) {
222                 char *p;
223
224                 switch (SQLColAttribute(conn->stmt, i, SQL_DESC_BASE_COLUMN_NAME,
225                                         field, sizeof(field), &len, NULL)) {
226                 case SQL_INVALID_HANDLE:
227                 case SQL_ERROR:
228                         ERROR("Failed retrieving field name at index %i", i);
229                         talloc_free(names);
230                         return RLM_SQL_ERROR;
231
232                 default:
233                         break;
234                 }
235
236                 MEM(p = talloc_array(names, char, (size_t)len + 1));
237                 strlcpy(p, field, (size_t)len + 1);
238                 names[i] = p;
239         }
240         *out = names;
241
242         return RLM_SQL_OK;
243 }
244
245 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
246 {
247         SQLRETURN rc;
248         rlm_sql_iodbc_conn_t *conn = handle->conn;
249
250         handle->row = NULL;
251
252         if((rc = SQLFetch(conn->stmt)) == SQL_NO_DATA_FOUND) {
253                 return 0;
254         }
255         /* XXX Check rc for database down, if so, return RLM_SQL_RECONNECT */
256
257         handle->row = conn->row;
258         return 0;
259 }
260
261 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
262 {
263         int i = 0;
264         rlm_sql_iodbc_conn_t *conn = handle->conn;
265
266         for (i = 0; i < sql_num_fields(handle, config); i++) free(conn->row[i]);
267         free(conn->row);
268         conn->row = NULL;
269
270         SQLFreeStmt(conn->stmt, SQL_DROP);
271
272         return 0;
273 }
274
275 /** Retrieves any errors associated with the connection handle
276  *
277  * @note Caller will free any memory allocated in ctx.
278  *
279  * @param ctx to allocate temporary error buffers in.
280  * @param out Array of sql_log_entrys to fill.
281  * @param outlen Length of out array.
282  * @param handle rlm_sql connection handle.
283  * @param config rlm_sql config.
284  * @return number of errors written to the sql_log_entry array.
285  */
286 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
287                         rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
288 {
289         rlm_sql_iodbc_conn_t    *conn = handle->conn;
290         SQLINTEGER              errornum = 0;
291         SQLSMALLINT             length = 0;
292         SQLCHAR                 state[256] = "";
293         SQLCHAR                 errbuff[IODBC_MAX_ERROR_LEN];
294
295         rad_assert(outlen > 0);
296
297         errbuff[0] = '\0';
298         SQLError(conn->env_handle, conn->dbc_handle, conn->stmt,
299                  state, &errornum, errbuff, IODBC_MAX_ERROR_LEN, &length);
300         if (errbuff[0] == '\0') return 0;
301
302         out[0].type = L_ERR;
303         out[0].msg = talloc_asprintf(ctx, "%s: %s", state, errbuff);
304
305         return 1;
306 }
307
308 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
309 {
310         return sql_free_result(handle, config);
311 }
312
313 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
314 {
315         return sql_free_result(handle, config);
316 }
317
318 static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
319 {
320         long count;
321         rlm_sql_iodbc_conn_t *conn = handle->conn;
322
323         SQLRowCount(conn->stmt, &count);
324         return (int)count;
325 }
326
327 /* Exported to rlm_sql */
328 extern rlm_sql_module_t rlm_sql_iodbc;
329 rlm_sql_module_t rlm_sql_iodbc = {
330         .name                           = "rlm_sql_iodbc",
331         .sql_socket_init                = sql_socket_init,
332         .sql_query                      = sql_query,
333         .sql_select_query               = sql_select_query,
334         .sql_store_result               = sql_store_result,
335         .sql_num_fields                 = sql_num_fields,
336         .sql_affected_rows              = sql_affected_rows,
337         .sql_fields                     = sql_fields,
338         .sql_fetch_row                  = sql_fetch_row,
339         .sql_free_result                = sql_free_result,
340         .sql_error                      = sql_error,
341         .sql_finish_query               = sql_finish_query,
342         .sql_finish_select_query        = sql_finish_select_query
343 };