diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c | |
parent | Initial commit. (diff) | |
download | freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.tar.xz freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c')
-rw-r--r-- | src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c b/src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c new file mode 100644 index 0000000..9fe8a19 --- /dev/null +++ b/src/modules/rlm_sql/drivers/rlm_sql_oracle/rlm_sql_oracle.c @@ -0,0 +1,482 @@ +/* + * sql_oracle.c Oracle (OCI) routines for rlm_sql + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + * Copyright 2000 David Kerry <davidk@snti.com> + */ + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/rad_assert.h> + +#include <sys/stat.h> + +/* + * There are typos in the Oracle Instaclient where the definition controlling prototype + * format is _STDC_ (not __STDC__). + * + * There are still cases where the oracle headers do not declare ANSI C function types + * but this at least cuts down the errors. + * + * -Wno-strict-prototypes does the rest. + */ +DIAG_OFF(unused-macros) +#if defined(__STDC__) && __STDC__ +# define _STDC_ +#endif + +#include <oci.h> +DIAG_ON(unused-macros) + +#include "rlm_sql.h" + +typedef struct rlm_sql_oracle_conn_t { + OCIEnv *env; + OCIStmt *query; + OCIError *error; + OCISvcCtx *ctx; + sb2 *ind; + char **row; + int id; + int col_count; //!< Number of columns associated with the result set + struct timeval tv; +} rlm_sql_oracle_conn_t; + +#define MAX_DATASTR_LEN 64 + +/** Write the last Oracle error out to a buffer + * + * @param out Where to write the error (should be at least 512 bytes). + * @param outlen The length of the error buffer. + * @param handle sql handle. + * @param config Instance config. + * @return 0 on success, -1 if there was no error. + */ +static int sql_prints_error(char *out, size_t outlen, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + sb4 errcode = 0; + rlm_sql_oracle_conn_t *conn = handle->conn; + + rad_assert(conn); + + out[0] = '\0'; + + OCIErrorGet((dvoid *) conn->error, 1, (OraText *) NULL, &errcode, (OraText *) out, + outlen, OCI_HTYPE_ERROR); + if (!errcode) return -1; + + return 0; +} + +/** Retrieves any errors associated with the connection handle + * + * @note Caller will free any memory allocated in ctx. + * + * @param ctx to allocate temporary error buffers in. + * @param out Array of sql_log_entrys to fill. + * @param outlen Length of out array. + * @param handle rlm_sql connection handle. + * @param config rlm_sql config. + * @return number of errors written to the sql_log_entry array. + */ +static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, + rlm_sql_handle_t *handle, rlm_sql_config_t *config) +{ + char errbuff[512]; + int ret; + + rad_assert(outlen > 0); + + ret = sql_prints_error(errbuff, sizeof(errbuff), handle, config); + if (ret < 0) return 0; + + out[0].type = L_ERR; + out[0].msg = talloc_strdup(ctx, errbuff); + + return 1; +} + +static int sql_check_error(rlm_sql_handle_t *handle, rlm_sql_config_t *config) +{ + char errbuff[512]; + + if (sql_prints_error(errbuff, sizeof(errbuff), handle, config) < 0) goto unknown; + + if (strstr(errbuff, "ORA-03113") || strstr(errbuff, "ORA-03114")) { + ERROR("rlm_sql_oracle: OCI_SERVER_NOT_CONNECTED"); + return RLM_SQL_RECONNECT; + } + +unknown: + ERROR("rlm_sql_oracle: OCI_SERVER_NORMAL"); + return -1; +} + +static int _sql_socket_destructor(rlm_sql_oracle_conn_t *conn) +{ + if (conn->ctx) OCILogoff(conn->ctx, conn->error); + if (conn->query) OCIHandleFree((dvoid *)conn->query, OCI_HTYPE_STMT); + if (conn->error) OCIHandleFree((dvoid *)conn->error, OCI_HTYPE_ERROR); + if (conn->env) OCIHandleFree((dvoid *)conn->env, OCI_HTYPE_ENV); + + return 0; +} + +static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config) +{ + char errbuff[512]; + + rlm_sql_oracle_conn_t *conn; + + MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_oracle_conn_t)); + talloc_set_destructor(conn, _sql_socket_destructor); + + /* + * Initialises the oracle environment + */ + if (OCIEnvCreate(&conn->env, OCI_DEFAULT | OCI_THREADED, NULL, NULL, NULL, NULL, 0, NULL)) { + ERROR("rlm_sql_oracle: Couldn't init Oracle OCI environment (OCIEnvCreate())"); + + return RLM_SQL_ERROR; + } + + /* + * Allocates an error handle + */ + if (OCIHandleAlloc((dvoid *)conn->env, (dvoid **)&conn->error, OCI_HTYPE_ERROR, 0, NULL)) { + ERROR("rlm_sql_oracle: Couldn't init Oracle ERROR handle (OCIHandleAlloc())"); + + return RLM_SQL_ERROR; + } + + /* + * Allocate handles for select and update queries + */ + if (OCIHandleAlloc((dvoid *)conn->env, (dvoid **)&conn->query, OCI_HTYPE_STMT, 0, NULL)) { + ERROR("rlm_sql_oracle: Couldn't init Oracle query handles: %s", + (sql_prints_error(errbuff, sizeof(errbuff), handle, config) == 0) ? errbuff : "unknown"); + + return RLM_SQL_ERROR; + } + + /* + * Login to the oracle server + */ + if (OCILogon(conn->env, conn->error, &conn->ctx, + (OraText const *)config->sql_login, strlen(config->sql_login), + (OraText const *)config->sql_password, strlen(config->sql_password), + (OraText const *)config->sql_db, strlen(config->sql_db))) { + ERROR("rlm_sql_oracle: Oracle logon failed: '%s'", + (sql_prints_error(errbuff, sizeof(errbuff), handle, config) == 0) ? errbuff : "unknown"); + + return RLM_SQL_ERROR; + } + + return RLM_SQL_OK; +} + +static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + int count; + rlm_sql_oracle_conn_t *conn = handle->conn; + + /* get the number of columns in the select list */ + if (OCIAttrGet((dvoid *)conn->query, OCI_HTYPE_STMT, (dvoid *)&count, NULL, OCI_ATTR_PARAM_COUNT, + conn->error)) return -1; + + return count; +} + +static sql_rcode_t sql_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) +{ + int status; + rlm_sql_oracle_conn_t *conn = handle->conn; + + OraText *oracle_query; + + memcpy(&oracle_query, &query, sizeof(oracle_query)); + + if (!conn->ctx) { + ERROR("rlm_sql_oracle: Socket not connected"); + + return RLM_SQL_RECONNECT; + } + + if (OCIStmtPrepare(conn->query, conn->error, oracle_query, strlen(query), + OCI_NTV_SYNTAX, OCI_DEFAULT)) { + ERROR("rlm_sql_oracle: prepare failed in sql_query"); + + return RLM_SQL_ERROR; + } + + status = OCIStmtExecute(conn->ctx, conn->query, conn->error, 1, 0, + NULL, NULL, OCI_COMMIT_ON_SUCCESS); + + if (status == OCI_SUCCESS) return RLM_SQL_OK; + if (status == OCI_ERROR) { + ERROR("rlm_sql_oracle: execute query failed in sql_query"); + + return sql_check_error(handle, config); + } + + return RLM_SQL_ERROR; +} + +static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) +{ + int status; + char **row; + + int i; + OCIParam *param; + OCIDefine *define; + + ub2 dtype; + ub2 dsize; + + sb2 *ind; + + OraText *oracle_query; + + rlm_sql_oracle_conn_t *conn = handle->conn; + + memcpy(&oracle_query, &query, sizeof(oracle_query)); + + if (OCIStmtPrepare(conn->query, conn->error, oracle_query, strlen(query), OCI_NTV_SYNTAX, + OCI_DEFAULT)) { + ERROR("rlm_sql_oracle: prepare failed in sql_select_query"); + + return RLM_SQL_ERROR; + } + + /* + * Retrieve a single row + */ + status = OCIStmtExecute(conn->ctx, conn->query, conn->error, 0, 0, NULL, NULL, OCI_DEFAULT); + if (status == OCI_NO_DATA) return RLM_SQL_OK; + if (status != OCI_SUCCESS) { + ERROR("rlm_sql_oracle: query failed in sql_select_query"); + + return sql_check_error(handle, config); + } + + /* + * We only need to do this once per result set, because + * the number of columns won't change. + */ + if (conn->col_count == 0) { + conn->col_count = sql_num_fields(handle, config); + + if (conn->col_count == 0) return RLM_SQL_ERROR; + } + + MEM(row = talloc_zero_array(conn, char*, conn->col_count + 1)); + MEM(ind = talloc_zero_array(row, sb2, conn->col_count + 1)); + + for (i = 0; i < conn->col_count; i++) { + status = OCIParamGet(conn->query, OCI_HTYPE_STMT, conn->error, (dvoid **)¶m, i + 1); + if (status != OCI_SUCCESS) { + ERROR("rlm_sql_oracle: OCIParamGet() failed in sql_select_query"); + + goto error; + } + + status = OCIAttrGet((dvoid*)param, OCI_DTYPE_PARAM, (dvoid*)&dtype, NULL, OCI_ATTR_DATA_TYPE, + conn->error); + if (status != OCI_SUCCESS) { + ERROR("rlm_sql_oracle: OCIAttrGet() failed in sql_select_query"); + + goto error; + } + + dsize = MAX_DATASTR_LEN; + + /* + * Use the retrieved length of dname to allocate an output buffer, and then define the output + * variable (but only for char/string type columns). + */ + switch (dtype) { +#ifdef SQLT_AFC + case SQLT_AFC: /* ansii fixed char */ +#endif +#ifdef SQLT_AFV + case SQLT_AFV: /* ansii var char */ +#endif + case SQLT_VCS: /* var char */ + case SQLT_CHR: /* char */ + case SQLT_STR: /* string */ + status = OCIAttrGet((dvoid *)param, OCI_DTYPE_PARAM, (dvoid *)&dsize, NULL, + OCI_ATTR_DATA_SIZE, conn->error); + if (status != OCI_SUCCESS) { + ERROR("rlm_sql_oracle: OCIAttrGet() failed in sql_select_query"); + + goto error; + } + + MEM(row[i] = talloc_zero_array(row, char, dsize + 1)); + + break; + case SQLT_DAT: + case SQLT_INT: + case SQLT_UIN: + case SQLT_FLT: + case SQLT_PDN: + case SQLT_BIN: + case SQLT_NUM: + MEM(row[i] = talloc_zero_array(row, char, dsize + 1)); + + break; + default: + dsize = 0; + row[i] = NULL; + break; + } + + ind[i] = 0; + + /* + * Grab the actual row value and write it to the buffer we allocated. + */ + status = OCIDefineByPos(conn->query, &define, conn->error, i + 1, (ub1 *)row[i], dsize + 1, SQLT_STR, + (dvoid *)&ind[i], NULL, NULL, OCI_DEFAULT); + + if (status != OCI_SUCCESS) { + ERROR("rlm_sql_oracle: OCIDefineByPos() failed in sql_select_query"); + goto error; + } + } + + conn->row = row; + conn->ind = ind; + + return RLM_SQL_OK; + + error: + talloc_free(row); + + return RLM_SQL_ERROR; +} + +static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + rlm_sql_oracle_conn_t *conn = handle->conn; + ub4 rows = 0; + ub4 size = sizeof(ub4); + + OCIAttrGet((CONST dvoid *)conn->query, OCI_HTYPE_STMT, (dvoid *)&rows, &size, OCI_ATTR_ROW_COUNT, conn->error); + + return rows; +} + +static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config) +{ + int status; + rlm_sql_oracle_conn_t *conn = handle->conn; + + if (!conn->ctx) { + ERROR("rlm_sql_oracle: Socket not connected"); + + return RLM_SQL_RECONNECT; + } + + handle->row = NULL; + + status = OCIStmtFetch(conn->query, conn->error, 1, OCI_FETCH_NEXT, OCI_DEFAULT); + if (status == OCI_SUCCESS) { + handle->row = conn->row; + + return RLM_SQL_OK; + } + + if (status == OCI_NO_DATA) { + handle->row = 0; + + return RLM_SQL_NO_MORE_ROWS; + } + + if (status == OCI_ERROR) { + ERROR("rlm_sql_oracle: fetch failed in sql_fetch_row"); + return sql_check_error(handle, config); + } + + return RLM_SQL_ERROR; +} + +static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + rlm_sql_oracle_conn_t *conn = handle->conn; + + /* Cancel the cursor first */ + (void) OCIStmtFetch(conn->query, conn->error, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + + TALLOC_FREE(conn->row); + conn->ind = NULL; /* ind is a child of row */ + conn->col_count = 0; + + return RLM_SQL_OK; +} + +static sql_rcode_t sql_finish_query(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + rlm_sql_oracle_conn_t *conn = handle->conn; + + if (OCIStmtRelease(conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) { + ERROR("OCI release failed in sql_finish_query"); + return RLM_SQL_ERROR; + } + + return 0; +} + +static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config) +{ + rlm_sql_oracle_conn_t *conn = handle->conn; + + TALLOC_FREE(conn->row); + conn->ind = NULL; /* ind is a child of row */ + conn->col_count = 0; + + if (OCIStmtRelease (conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) { + ERROR("OCI release failed in sql_finish_query"); + return RLM_SQL_ERROR; + } + + return 0; +} + +static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config) +{ + return sql_num_rows(handle, config); +} + +/* Exported to rlm_sql */ +extern rlm_sql_module_t rlm_sql_oracle; +rlm_sql_module_t rlm_sql_oracle = { + .name = "rlm_sql_oracle", + .sql_socket_init = sql_socket_init, + .sql_query = sql_query, + .sql_select_query = sql_select_query, + .sql_num_fields = sql_num_fields, + .sql_num_rows = sql_num_rows, + .sql_affected_rows = sql_affected_rows, + .sql_fetch_row = sql_fetch_row, + .sql_free_result = sql_free_result, + .sql_error = sql_error, + .sql_finish_query = sql_finish_query, + .sql_finish_select_query = sql_finish_select_query +}; |