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: 248b55e8faf9bec0fb13a75fd37814651e5f01d5 $")
23
24 #include <freeradius-devel/radiusd.h>
25 #include <freeradius-devel/rad_assert.h>
26
27 #include <sqltypes.h>
28 #include "rlm_sql.h"
29
30 typedef struct rlm_sql_unixodbc_conn {
31 SQLHENV env;
32 SQLHDBC dbc;
33 SQLHSTMT stmt;
34 rlm_sql_row_t row;
35 void *conn;
36 } rlm_sql_unixodbc_conn_t;
37
38 USES_APPLE_DEPRECATED_API
39 #include <sql.h>
40 #include <sqlext.h>
41
42 /* Forward declarations */
43 static int sql_check_error(long err_handle, rlm_sql_handle_t *handle, rlm_sql_config_t *config);
44 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
45 static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
46 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config);
47
_sql_socket_destructor(rlm_sql_unixodbc_conn_t * conn)48 static int _sql_socket_destructor(rlm_sql_unixodbc_conn_t *conn)
49 {
50 DEBUG2("rlm_sql_unixodbc: Socket destructor called, closing socket");
51
52 if (conn->stmt) SQLFreeStmt(conn->stmt, SQL_DROP);
53
54 if (conn->dbc) {
55 SQLDisconnect(conn->dbc);
56 SQLFreeConnect(conn->dbc);
57 }
58
59 if (conn->env) SQLFreeEnv(conn->env);
60
61 return 0;
62 }
63
sql_socket_init(rlm_sql_handle_t * handle,rlm_sql_config_t * config)64 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
65 {
66 rlm_sql_unixodbc_conn_t *conn;
67 long err_handle;
68
69 MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_unixodbc_conn_t));
70 talloc_set_destructor(conn, _sql_socket_destructor);
71
72 /* 1. Allocate environment handle and register version */
73 err_handle = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &conn->env);
74 if (sql_check_error(err_handle, handle, config)) {
75 ERROR("rlm_sql_unixodbc: Can't allocate environment handle");
76 return RLM_SQL_ERROR;
77 }
78
79 err_handle = SQLSetEnvAttr(conn->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
80 if (sql_check_error(err_handle, handle, config)) {
81 ERROR("rlm_sql_unixodbc: Can't register ODBC version");
82 return RLM_SQL_ERROR;
83 }
84
85 /* 2. Allocate connection handle */
86 err_handle = SQLAllocHandle(SQL_HANDLE_DBC, conn->env, &conn->dbc);
87 if (sql_check_error(err_handle, handle, config)) {
88 ERROR("rlm_sql_unixodbc: Can't allocate connection handle");
89 return RLM_SQL_ERROR;
90 }
91
92 /* 3. Connect to the datasource */
93 {
94 SQLCHAR *odbc_server, *odbc_login, *odbc_password;
95
96 memcpy(&odbc_server, &config->sql_server, sizeof(odbc_server));
97 memcpy(&odbc_login, &config->sql_login, sizeof(odbc_login));
98 memcpy(&odbc_password, &config->sql_password, sizeof(odbc_password));
99 err_handle = SQLConnect(conn->dbc,
100 odbc_server, strlen(config->sql_server),
101 odbc_login, strlen(config->sql_login),
102 odbc_password, strlen(config->sql_password));
103 }
104
105 if (sql_check_error(err_handle, handle, config)) {
106 ERROR("rlm_sql_unixodbc: Connection failed");
107 return RLM_SQL_ERROR;
108 }
109
110 /* 4. Allocate the stmt */
111 err_handle = SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &conn->stmt);
112 if (sql_check_error(err_handle, handle, config)) {
113 ERROR("rlm_sql_unixodbc: Can't allocate the stmt");
114 return RLM_SQL_ERROR;
115 }
116
117 return RLM_SQL_OK;
118 }
119
sql_query(rlm_sql_handle_t * handle,rlm_sql_config_t * config,char const * query)120 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
121 {
122 rlm_sql_unixodbc_conn_t *conn = handle->conn;
123 long err_handle;
124 int state;
125
126 /* Executing query */
127 {
128 SQLCHAR *odbc_query;
129
130 memcpy(&odbc_query, &query, sizeof(odbc_query));
131 err_handle = SQLExecDirect(conn->stmt, odbc_query, strlen(query));
132 }
133 if ((state = sql_check_error(err_handle, handle, config))) {
134 if(state == RLM_SQL_RECONNECT) {
135 DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
136 }
137 return state;
138 }
139 return 0;
140 }
141
sql_select_query(rlm_sql_handle_t * handle,rlm_sql_config_t * config,char const * query)142 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
143 {
144 rlm_sql_unixodbc_conn_t *conn = handle->conn;
145 SQLINTEGER i;
146 SQLLEN len;
147 int colcount;
148 int state;
149
150 /* Only state = 0 means success */
151 if ((state = sql_query(handle, config, query))) {
152 return state;
153 }
154
155 colcount = sql_num_fields(handle, config);
156 if (colcount < 0) {
157 return RLM_SQL_ERROR;
158 }
159
160 /* Reserving memory for result */
161 conn->row = talloc_zero_array(conn, char *, colcount + 1); /* Space for pointers */
162
163 for (i = 1; i <= colcount; i++) {
164 len = 0;
165 SQLColAttributes(conn->stmt, ((SQLUSMALLINT) i), SQL_DESC_LENGTH, NULL, 0, NULL, &len);
166 conn->row[i - 1] = talloc_array(conn->row, char, ++len);
167 SQLBindCol(conn->stmt, i, SQL_C_CHAR, (SQLCHAR *)conn->row[i - 1], len, NULL);
168 }
169
170 return RLM_SQL_OK;
171 }
172
sql_num_fields(rlm_sql_handle_t * handle,rlm_sql_config_t * config)173 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
174 {
175 rlm_sql_unixodbc_conn_t *conn = handle->conn;
176 long err_handle;
177 SQLSMALLINT num_fields = 0;
178
179 err_handle = SQLNumResultCols(conn->stmt,&num_fields);
180 if (sql_check_error(err_handle, handle, config)) return -1;
181
182 return num_fields;
183 }
184
sql_fetch_row(rlm_sql_handle_t * handle,rlm_sql_config_t * config)185 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
186 {
187 rlm_sql_unixodbc_conn_t *conn = handle->conn;
188 long err_handle;
189 int state;
190
191 handle->row = NULL;
192
193 err_handle = SQLFetch(conn->stmt);
194 if (err_handle == SQL_NO_DATA_FOUND) return RLM_SQL_NO_MORE_ROWS;
195
196 if ((state = sql_check_error(err_handle, handle, config))) return state;
197
198 handle->row = conn->row;
199 return RLM_SQL_OK;
200 }
201
sql_finish_select_query(rlm_sql_handle_t * handle,rlm_sql_config_t * config)202 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t * handle, rlm_sql_config_t *config)
203 {
204 rlm_sql_unixodbc_conn_t *conn = handle->conn;
205
206 sql_free_result(handle, config);
207
208 /*
209 * SQL_CLOSE - The cursor (if any) associated with the statement
210 * handle (StatementHandle) is closed and all pending results are
211 * discarded. The application can reopen the cursor by calling
212 * SQLExecute() with the same or different values in the
213 * application variables (if any) that are bound to StatementHandle.
214 * If no cursor has been associated with the statement handle,
215 * this option has no effect (no warning or error is generated).
216 *
217 * So, this call does NOT free the statement at all, it merely
218 * resets it for the next call. This is terrible terrible naming.
219 */
220 SQLFreeStmt(conn->stmt, SQL_CLOSE);
221
222 return 0;
223 }
224
sql_finish_query(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)225 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
226 {
227 rlm_sql_unixodbc_conn_t *conn = handle->conn;
228
229 SQLFreeStmt(conn->stmt, SQL_CLOSE);
230
231 return 0;
232 }
233
sql_free_result(rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)234 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
235 {
236 rlm_sql_unixodbc_conn_t *conn = handle->conn;
237
238 TALLOC_FREE(conn->row);
239
240 return 0;
241 }
242
243 /** Retrieves any errors associated with the connection handle
244 *
245 * @note Caller will free any memory allocated in ctx.
246 *
247 * @param ctx to allocate temporary error buffers in.
248 * @param out Array of sql_log_entrys to fill.
249 * @param outlen Length of out array.
250 * @param handle rlm_sql connection handle.
251 * @param config rlm_sql config.
252 * @return number of errors written to the sql_log_entry array.
253 */
sql_error(TALLOC_CTX * ctx,sql_log_entry_t out[],size_t outlen,rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)254 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
255 rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
256 {
257 rlm_sql_unixodbc_conn_t *conn = handle->conn;
258 SQLCHAR state[256];
259 SQLCHAR errbuff[256];
260 SQLINTEGER errnum = 0;
261 SQLSMALLINT length = 255;
262
263 rad_assert(outlen > 0);
264
265 errbuff[0] = state[0] = '\0';
266 SQLError(conn->env, conn->dbc, conn->stmt, state, &errnum,
267 errbuff, sizeof(errbuff), &length);
268 if (errnum == 0) return 0;
269
270 out[0].type = L_ERR;
271 out[0].msg = talloc_asprintf(ctx, "%s: %s", state, errbuff);
272
273 return 1;
274 }
275
276 /** Checks the error code to determine if the connection needs to be re-esttablished
277 *
278 * @param error_handle Return code from a failed unixodbc call.
279 * @param handle rlm_sql connection handle.
280 * @param config rlm_sql config.
281 * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if reconnect is needed, or RLM_SQL_ERROR on error.
282 */
sql_check_error(long error_handle,rlm_sql_handle_t * handle,UNUSED rlm_sql_config_t * config)283 static sql_rcode_t sql_check_error(long error_handle, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
284 {
285 SQLCHAR state[256];
286 SQLCHAR error[256];
287 SQLINTEGER errornum = 0;
288 SQLSMALLINT length = 255;
289 int res = -1;
290
291 rlm_sql_unixodbc_conn_t *conn = handle->conn;
292
293 if (SQL_SUCCEEDED(error_handle)) return 0; /* on success, just return 0 */
294
295 error[0] = state[0] = '\0';
296
297 SQLError(conn->env, conn->dbc, conn->stmt, state, &errornum,
298 error, sizeof(error), &length);
299
300 if (state[0] == '0') {
301 switch (state[1]) {
302 /* SQLSTATE 01 class contains info and warning messages */
303 case '1':
304 INFO("rlm_sql_unixodbc: %s %s", state, error);
305 /* FALL-THROUGH */
306 case '0': /* SQLSTATE 00 class means success */
307 res = RLM_SQL_OK;
308 break;
309
310 /* SQLSTATE 08 class describes various connection errors */
311 case '8':
312 ERROR("rlm_sql_unixodbc: SQL down %s %s", state, error);
313 res = RLM_SQL_RECONNECT;
314 break;
315
316 /* any other SQLSTATE means error */
317 default:
318 ERROR("rlm_sql_unixodbc: %s %s", state, error);
319 res = RLM_SQL_ERROR;
320 break;
321 }
322 } else {
323 ERROR("rlm_sql_unixodbc: %s %s", state, error);
324 }
325
326 return res;
327 }
328
329 /*************************************************************************
330 *
331 * Function: sql_affected_rows
332 *
333 * Purpose: Return the number of rows affected by the query (update,
334 * or insert)
335 *
336 *************************************************************************/
sql_affected_rows(rlm_sql_handle_t * handle,rlm_sql_config_t * config)337 static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
338 {
339 rlm_sql_unixodbc_conn_t *conn = handle->conn;
340 long error_handle;
341 SQLLEN affected_rows;
342
343 error_handle = SQLRowCount(conn->stmt, &affected_rows);
344 if (sql_check_error(error_handle, handle, config)) return -1;
345
346 return affected_rows;
347 }
348
349
350 /* Exported to rlm_sql */
351 extern rlm_sql_module_t rlm_sql_unixodbc;
352 rlm_sql_module_t rlm_sql_unixodbc = {
353 .name = "rlm_sql_unixodbc",
354 .sql_socket_init = sql_socket_init,
355 .sql_query = sql_query,
356 .sql_select_query = sql_select_query,
357 .sql_num_fields = sql_num_fields,
358 .sql_affected_rows = sql_affected_rows,
359 .sql_fetch_row = sql_fetch_row,
360 .sql_free_result = sql_free_result,
361 .sql_error = sql_error,
362 .sql_finish_query = sql_finish_query,
363 .sql_finish_select_query = sql_finish_select_query
364 };
365