The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_sql_unixodbc.c
Go to the documentation of this file.
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 RCSID("$Id: abae1800a7c68616febf6a29e227169c35e34ed0 $")
23 
24 #define LOG_PREFIX "sql - unixodbc"
25 
26 #include <freeradius-devel/server/base.h>
27 #include <freeradius-devel/util/debug.h>
28 
29 #include <sqltypes.h>
30 #include "rlm_sql.h"
31 
32 typedef struct {
33  SQLHENV env;
34  SQLHDBC dbc;
35  SQLHSTMT stmt;
37  void *conn;
39 
41 #include <sql.h>
42 #include <sqlext.h>
43 
44 /* Forward declarations */
45 static int sql_check_error(long err_handle, rlm_sql_handle_t *handle, rlm_sql_config_t const *config);
47 static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t const *config);
48 
50 {
51  DEBUG2("Socket destructor called, closing socket");
52 
53  if (conn->stmt) SQLFreeStmt(conn->stmt, SQL_DROP);
54 
55  if (conn->dbc) {
56  SQLDisconnect(conn->dbc);
57  SQLFreeConnect(conn->dbc);
58  }
59 
60  if (conn->env) SQLFreeEnv(conn->env);
61 
62  return 0;
63 }
64 
67 {
69  long err_handle;
71 
72  MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_unixodbc_conn_t));
73  talloc_set_destructor(conn, _sql_socket_destructor);
74 
75  /* 1. Allocate environment handle and register version */
76  err_handle = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &conn->env);
77  if (sql_check_error(err_handle, handle, config)) {
78  ERROR("Can't allocate environment handle");
79  return RLM_SQL_ERROR;
80  }
81 
82  err_handle = SQLSetEnvAttr(conn->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
83  if (sql_check_error(err_handle, handle, config)) {
84  ERROR("Can't register ODBC version");
85  return RLM_SQL_ERROR;
86  }
87 
88  /* 2. Allocate connection handle */
89  err_handle = SQLAllocHandle(SQL_HANDLE_DBC, conn->env, &conn->dbc);
90  if (sql_check_error(err_handle, handle, config)) {
91  ERROR("Can't allocate connection handle");
92  return RLM_SQL_ERROR;
93  }
94 
95  /* Set the connection timeout */
96  SQLSetConnectAttr(conn->dbc, SQL_ATTR_LOGIN_TIMEOUT, &timeout_ms, SQL_IS_UINTEGER);
97 
98  /* 3. Connect to the datasource */
99  err_handle = SQLConnect(conn->dbc,
100  UNCONST(SQLCHAR *, config->sql_server), strlen(config->sql_server),
101  UNCONST(SQLCHAR *, config->sql_login), strlen(config->sql_login),
102  UNCONST(SQLCHAR *, config->sql_password), strlen(config->sql_password));
103 
104  if (sql_check_error(err_handle, handle, config)) {
105  ERROR("Connection failed");
106  return RLM_SQL_ERROR;
107  }
108 
109  /* 4. Allocate the stmt */
110  err_handle = SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &conn->stmt);
111  if (sql_check_error(err_handle, handle, config)) {
112  ERROR("Can't allocate the stmt");
113  return RLM_SQL_ERROR;
114  }
115 
116  return RLM_SQL_OK;
117 }
118 
119 static unlang_action_t sql_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
120 {
121  fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
122  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
123  long err_handle;
124 
125  /* Executing query */
126  err_handle = SQLExecDirect(conn->stmt, UNCONST(SQLCHAR *, query_ctx->query_str), strlen(query_ctx->query_str));
127  if ((query_ctx->rcode = sql_check_error(err_handle, query_ctx->handle, &query_ctx->inst->config))) {
128  if(query_ctx->rcode == RLM_SQL_RECONNECT) {
129  DEBUG("rlm_sql will attempt to reconnect");
130  }
132  }
134 }
135 
136 static unlang_action_t sql_select_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
137 {
138  fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
139  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
140  SQLINTEGER i;
141  SQLLEN len;
142  int colcount;
143 
144  /* Only state = 0 means success */
145  if ((sql_query(p_result, NULL, request, query_ctx) == UNLANG_ACTION_CALCULATE_RESULT) &&
146  (query_ctx->rcode != RLM_SQL_OK)) RETURN_MODULE_FAIL;
147 
148  colcount = sql_num_fields(query_ctx->handle, &query_ctx->inst->config);
149  if (colcount < 0) {
150  query_ctx->rcode = RLM_SQL_ERROR;
152  }
153 
154  /* Reserving memory for result */
155  conn->row = talloc_zero_array(conn, char *, colcount + 1); /* Space for pointers */
156 
157  for (i = 1; i <= colcount; i++) {
158  len = 0;
159  SQLColAttributes(conn->stmt, ((SQLUSMALLINT) i), SQL_DESC_LENGTH, NULL, 0, NULL, &len);
160  conn->row[i - 1] = talloc_array(conn->row, char, ++len);
161  SQLBindCol(conn->stmt, i, SQL_C_CHAR, (SQLCHAR *)conn->row[i - 1], len, NULL);
162  }
163 
165 }
166 
168 {
169  rlm_sql_unixodbc_conn_t *conn = handle->conn;
170  long err_handle;
171  SQLSMALLINT num_fields = 0;
172 
173  err_handle = SQLNumResultCols(conn->stmt,&num_fields);
174  if (sql_check_error(err_handle, handle, config)) return -1;
175 
176  return num_fields;
177 }
178 
179 static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
180 {
181  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
182 
183  SQLSMALLINT fields, len, i;
184 
185  char const **names;
186  char field[128];
187 
188  SQLNumResultCols(conn->stmt, &fields);
189  if (fields == 0) return RLM_SQL_ERROR;
190 
191  MEM(names = talloc_array(query_ctx, char const *, fields));
192 
193  for (i = 0; i < fields; i++) {
194  char *p;
195 
196  switch (SQLColAttribute(conn->stmt, i, SQL_DESC_BASE_COLUMN_NAME,
197  field, sizeof(field), &len, NULL)) {
198  case SQL_INVALID_HANDLE:
199  case SQL_ERROR:
200  ERROR("Failed retrieving field name at index %i", i);
202  return RLM_SQL_ERROR;
203 
204  default:
205  break;
206  }
207 
208  MEM(p = talloc_array(names, char, (size_t)len + 1));
209  strlcpy(p, field, (size_t)len + 1);
210  names[i] = p;
211  }
212  *out = names;
213 
214  return RLM_SQL_OK;
215 }
216 
217 static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
218 {
219  fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
220  rlm_sql_handle_t *handle = query_ctx->handle;
221  rlm_sql_unixodbc_conn_t *conn = handle->conn;
222  long err_handle;
223 
224  query_ctx->row = NULL;
225 
226  err_handle = SQLFetch(conn->stmt);
227  if (err_handle == SQL_NO_DATA_FOUND) {
228  query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
230  }
231 
232  query_ctx->rcode = sql_check_error(err_handle, handle, &query_ctx->inst->config);
233  if (query_ctx->rcode != RLM_SQL_OK) RETURN_MODULE_FAIL;
234 
235  query_ctx->row = conn->row;
236 
237  query_ctx->rcode = RLM_SQL_OK;
239 }
240 
242 {
243  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
244 
245  sql_free_result(query_ctx, config);
246 
247  /*
248  * SQL_CLOSE - The cursor (if any) associated with the statement
249  * handle (StatementHandle) is closed and all pending results are
250  * discarded. The application can reopen the cursor by calling
251  * SQLExecute() with the same or different values in the
252  * application variables (if any) that are bound to StatementHandle.
253  * If no cursor has been associated with the statement handle,
254  * this option has no effect (no warning or error is generated).
255  *
256  * So, this call does NOT free the statement at all, it merely
257  * resets it for the next call. This is terrible terrible naming.
258  */
259  SQLFreeStmt(conn->stmt, SQL_CLOSE);
260 
261  return 0;
262 }
263 
265 {
266  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
267 
268  SQLFreeStmt(conn->stmt, SQL_CLOSE);
269 
270  return 0;
271 }
272 
274 {
275  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
276 
277  TALLOC_FREE(conn->row);
278 
279  return 0;
280 }
281 
282 /** Retrieves any errors associated with the query context
283  *
284  * @note Caller will free any memory allocated in ctx.
285  *
286  * @param ctx to allocate temporary error buffers in.
287  * @param out Array of #sql_log_entry_t to fill.
288  * @param outlen Length of out array.
289  * @param query_ctx Query context to retrieve error for.
290  * @param config rlm_sql config.
291  * @return number of errors written to the #sql_log_entry_t array.
292  */
293 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen,
294  fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
295 {
296  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
297  SQLCHAR state[256];
298  SQLCHAR errbuff[256];
299  SQLINTEGER errnum = 0;
300  SQLSMALLINT length = 255;
301 
302  fr_assert(outlen > 0);
303 
304  errbuff[0] = state[0] = '\0';
305  SQLError(conn->env, conn->dbc, conn->stmt, state, &errnum,
306  errbuff, sizeof(errbuff), &length);
307  if (errnum == 0) return 0;
308 
309  out[0].type = L_ERR;
310  out[0].msg = talloc_typed_asprintf(ctx, "%s: %s", state, errbuff);
311 
312  return 1;
313 }
314 
315 /** Checks the error code to determine if the connection needs to be re-esttablished
316  *
317  * @param error_handle Return code from a failed unixodbc call.
318  * @param handle rlm_sql connection handle.
319  * @param config rlm_sql config.
320  * @return
321  * - #RLM_SQL_OK on success.
322  * - #RLM_SQL_RECONNECT if reconnect is needed.
323  * - #RLM_SQL_ERROR on error.
324  */
325 static sql_rcode_t sql_check_error(long error_handle, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t const *config)
326 {
327  SQLCHAR state[256];
328  SQLCHAR error[256];
329  SQLINTEGER errornum = 0;
330  SQLSMALLINT length = 255;
331  int res = -1;
332 
333  rlm_sql_unixodbc_conn_t *conn = handle->conn;
334 
335  if (SQL_SUCCEEDED(error_handle)) return 0; /* on success, just return 0 */
336 
337  error[0] = state[0] = '\0';
338 
339  SQLError(conn->env, conn->dbc, conn->stmt, state, &errornum,
340  error, sizeof(error), &length);
341 
342  if (state[0] == '0') {
343  switch (state[1]) {
344  /* SQLSTATE 01 class contains info and warning messages */
345  case '1':
346  INFO("%s %s", state, error);
347  FALL_THROUGH;
348  case '0': /* SQLSTATE 00 class means success */
349  res = RLM_SQL_OK;
350  break;
351 
352  /* SQLSTATE 08 class describes various connection errors */
353  case '8':
354  ERROR("SQL down %s %s", state, error);
355  res = RLM_SQL_RECONNECT;
356  break;
357 
358  /* any other SQLSTATE means error */
359  default:
360  ERROR("%s %s", state, error);
361  res = RLM_SQL_ERROR;
362  break;
363  }
364  } else {
365  ERROR("%s %s", state, error);
366  }
367 
368  return res;
369 }
370 
371 /*************************************************************************
372  *
373  * Function: sql_affected_rows
374  *
375  * Purpose: Return the number of rows affected by the query (update,
376  * or insert)
377  *
378  *************************************************************************/
380 {
381  rlm_sql_unixodbc_conn_t *conn = query_ctx->handle->conn;
382  long error_handle;
383  SQLLEN affected_rows;
384 
385  error_handle = SQLRowCount(conn->stmt, &affected_rows);
386  if (sql_check_error(error_handle, query_ctx->handle, config)) return -1;
387 
388  return affected_rows;
389 }
390 
391 
392 /* Exported to rlm_sql */
395  .common = {
396  .magic = MODULE_MAGIC_INIT,
397  .name = "sql_unixodbc"
398  },
399  .sql_socket_init = sql_socket_init,
400  .sql_query = sql_query,
401  .sql_select_query = sql_select_query,
402  .sql_affected_rows = sql_affected_rows,
403  .sql_fields = sql_fields,
404  .sql_fetch_row = sql_fetch_row,
405  .sql_free_result = sql_free_result,
406  .sql_error = sql_error,
407  .sql_finish_query = sql_finish_query,
408  .sql_finish_select_query = sql_finish_select_query
409 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition: action.h:37
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition: build.h:165
#define USES_APPLE_DEPRECATED_API
Definition: build.h:433
#define RCSID(id)
Definition: build.h:446
#define NDEBUG_UNUSED
Definition: build.h:324
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define UNUSED
Definition: build.h:313
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#define DEBUG(fmt,...)
Definition: dhcpclient.c:39
static fr_time_delta_t timeout
Definition: dhcpclient.c:54
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:63
talloc_free(reap)
@ L_ERR
Error message.
Definition: log.h:56
unsigned int uint32_t
Definition: merged_model.c:33
static const conf_parser_t config[]
Definition: base.c:188
#define DEBUG2(fmt,...)
Definition: radclient.h:43
#define INFO(fmt,...)
Definition: radict.c:54
#define RETURN_MODULE_OK
Definition: rcode.h:57
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
Prototypes and functions for the SQL module.
void * conn
Database specific connection handle.
Definition: rlm_sql.h:114
rlm_sql_t const * inst
Module instance for this query.
Definition: rlm_sql.h:137
char const * query_str
Query string to run.
Definition: rlm_sql.h:143
sql_rcode_t
Action to take at end of an SQL query.
Definition: rlm_sql.h:44
@ RLM_SQL_RECONNECT
Stale connection, should reconnect.
Definition: rlm_sql.h:48
@ RLM_SQL_ERROR
General connection/server error.
Definition: rlm_sql.h:46
@ RLM_SQL_OK
Success.
Definition: rlm_sql.h:47
@ RLM_SQL_NO_MORE_ROWS
No more rows available.
Definition: rlm_sql.h:50
rlm_sql_handle_t * handle
Connection handle this query is being run on.
Definition: rlm_sql.h:139
char ** rlm_sql_row_t
Definition: rlm_sql.h:59
rlm_sql_row_t row
Row data from the last query.
Definition: rlm_sql.h:147
sql_rcode_t rcode
Result code.
Definition: rlm_sql.h:146
Definition: rlm_sql.h:61
static int affected_rows(PGresult *result)
Return the number of affected rows of the result as an int instead of the string that postgresql prov...
static sql_rcode_t sql_free_result(fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t const *config, fr_time_delta_t timeout)
rlm_sql_driver_t rlm_sql_unixodbc
static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static sql_rcode_t sql_finish_query(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
static sql_rcode_t sql_finish_select_query(fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
static int sql_affected_rows(fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static int _sql_socket_destructor(rlm_sql_unixodbc_conn_t *conn)
static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t const *config)
static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen, fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
Retrieves any errors associated with the query context.
static unlang_action_t sql_select_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
static USES_APPLE_DEPRECATED_API int sql_check_error(long err_handle, rlm_sql_handle_t *handle, rlm_sql_config_t const *config)
static unlang_action_t sql_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:34
module_t common
Common fields for all loadable modules.
Definition: rlm_sql.h:202
rlm_sql_config_t config
Definition: rlm_sql.h:236
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: talloc.c:492
static const char * names[8]
Definition: time.c:617
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition: time.h:637
A time delta, a difference in time measured in nanoseconds.
Definition: time.h:80
static size_t char ** out
Definition: value.h:984