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