Use a proper rcode for no more rows
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_db2 / rlm_sql_db2.c
index 21fe7bd..7f922b3 100644 (file)
  * by Joerg Wendland <wendland@scan-plus.de>
  */
 
-#include <freeradius-devel/ident.h>
 RCSID("$Id$")
 
 #include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
 
 #include <sys/stat.h>
 
@@ -39,320 +39,256 @@ RCSID("$Id$")
 #include <sqlcli.h>
 #include "rlm_sql.h"
 
-typedef struct rlm_sql_db2_sock {
-       SQLHANDLE hdbc;
-       SQLHANDLE henv;
+typedef struct rlm_sql_conn {
+       SQLHANDLE dbc_handle;
+       SQLHANDLE env_handle;
        SQLHANDLE stmt;
-} rlm_sql_db2_sock;
+} rlm_sql_db2_conn_t;
 
-/*************************************************************************
- *
- *     Function: sql_create_socket
- *
- *     Purpose: Establish connection to the db
- *
- *************************************************************************/
-static int sql_init_socket(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
+static int _sql_socket_destructor(rlm_sql_db2_conn_t *conn)
 {
-       SQLRETURN retval;
-       rlm_sql_db2_sock *sock;
+       DEBUG2("rlm_sql_db2: Socket destructor called, closing socket");
 
-       /* allocate socket */
-       if (!handle->conn) {
-               handle->conn = (rlm_sql_db2_sock*)rad_malloc(sizeof(rlm_sql_db2_sock));
-               if (!handle->conn) {
-                       return -1;
-               }
-       }
-       sock = handle->conn;
-       memset(sock, 0, sizeof(*sock));
-
-       /* allocate handles */
-       SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &(sock->henv));
-       SQLAllocHandle(SQL_HANDLE_DBC, sock->henv, &(sock->hdbc));
-
-       /* connect to database */
-       retval = SQLConnect(sock->hdbc,
-                       config->sql_server, SQL_NTS,
-                       config->sql_login,  SQL_NTS,
-                       config->sql_password, SQL_NTS);
-       if(retval != SQL_SUCCESS) {
-               radlog(L_ERR, "could not connect to DB2 server %s\n", config->sql_server);
-               SQLDisconnect(sock->hdbc);
-               SQLFreeHandle(SQL_HANDLE_DBC, sock->hdbc);
-               SQLFreeHandle(SQL_HANDLE_ENV, sock->henv);
-               return -1;
+       if (conn->dbc_handle) {
+               SQLDisconnect(conn->dbc_handle);
+               SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc_handle);
        }
 
-       return 0;
-}
+       if (conn->env_handle) SQLFreeHandle(SQL_HANDLE_ENV, conn->env_handle);
 
+       return RLM_SQL_OK;
+}
 
-/*************************************************************************
- *
- *      Function: sql_destroy_socket
- *
- *      Purpose: Free socket and private connection data
- *
- *************************************************************************/
-static int sql_destroy_socket(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
+static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
-       free(handle->conn);
-       handle->conn = NULL;
-       return 0;
+       SQLRETURN retval;
+       rlm_sql_db2_conn_t *conn;
+
+       MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_db2_conn_t));
+       talloc_set_destructor(conn, _sql_socket_destructor);
+
+       /* Allocate handles */
+       SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &(conn->env_handle));
+       SQLAllocHandle(SQL_HANDLE_DBC, conn->env_handle, &(conn->dbc_handle));
+
+       /*
+        *      The db2 API doesn't qualify arguments as const even when they should be.
+        */
+       {
+               SQLCHAR *server, *login, *password;
+
+               memcpy(&server, &config->sql_server, sizeof(server));
+               memcpy(&login, &config->sql_login, sizeof(login));
+               memcpy(&password, &config->sql_password, sizeof(password));
+
+               retval = SQLConnect(conn->dbc_handle,
+                                   server, SQL_NTS,
+                                   login,  SQL_NTS,
+                                   password, SQL_NTS);
+       }
+
+       if (retval != SQL_SUCCESS) {
+               ERROR("could not connect to DB2 server %s", config->sql_server);
+
+               return RLM_SQL_ERROR;
+       }
+
+       return RLM_SQL_OK;
 }
 
-/*************************************************************************
- *
- *     Function: sql_query
- *
- *     Purpose: Issue a query to the database
- *
- *************************************************************************/
-static int sql_query(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config, char *querystr)
+static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
 {
        SQLRETURN retval;
-       rlm_sql_db2_sock *sock;
+       rlm_sql_db2_conn_t *conn;
 
-       sock = handle->conn;
+       conn = handle->conn;
 
        /* allocate handle for statement */
-       SQLAllocHandle(SQL_HANDLE_STMT, sock->hdbc,
-                       &(sock->stmt));
+       SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc_handle, &(conn->stmt));
 
        /* execute query */
-       retval = SQLExecDirect(sock->stmt, querystr, SQL_NTS);
-       if(retval != SQL_SUCCESS) {
-               /* XXX Check if retval means we should return SQL_DOWN */
-               radlog(L_ERR, "could not execute statement \"%s\"\n", querystr);
-               return -1;
+       {
+               SQLCHAR *db2_query;
+               memcpy(&db2_query, &query, sizeof(query));
+
+               retval = SQLExecDirect(conn->stmt, db2_query, SQL_NTS);
+               if(retval != SQL_SUCCESS) {
+                       /* XXX Check if retval means we should return RLM_SQL_RECONNECT */
+                       ERROR("Could not execute statement \"%s\"", query);
+                       return RLM_SQL_ERROR;
+               }
        }
 
-       return 0;
+       return RLM_SQL_OK;
 }
 
-
-/*************************************************************************
- *
- *     Function: sql_select_query
- *
- *     Purpose: Issue a select query to the database
- *
- *************************************************************************/
-static int sql_select_query(rlm_sql_handle_t * handle, rlm_sql_config_t *config, char *querystr)
+static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
 {
-       return sql_query(handle, config, querystr);
+       return sql_query(handle, config, query);
 }
 
-
-/*************************************************************************
- *
- *     Function: sql_num_fields
- *
- *     Purpose: database specific num_fields function. Returns number
- *               of columns from query
- *
- *************************************************************************/
-static int sql_num_fields(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
+static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
        SQLSMALLINT c;
-       rlm_sql_db2_sock *sock;
+       rlm_sql_db2_conn_t *conn;
 
-       sock = handle->conn;
-       SQLNumResultCols(sock->stmt, &c);
+       conn = handle->conn;
+       SQLNumResultCols(conn->stmt, &c);
        return c;
 }
 
+static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
+{
+       rlm_sql_db2_conn_t *conn = handle->conn;
 
-/*************************************************************************
- *
- *     Function: sql_fetch_row
- *
- *     Purpose: database specific fetch_row. Returns a rlm_sql_row_t struct
- *               with all the data for the query in 'handle->row'. Returns
- *              0 on success, -1 on failure, SQL_DOWN if 'database is down'
- *
- *************************************************************************/
-static int sql_fetch_row(rlm_sql_handle_t * handle, rlm_sql_config_t *config)
+       SQLSMALLINT     fields, len, i;
+
+       char const      **names;
+       char            field[128];
+
+       SQLNumResultCols(conn->stmt, &fields);
+       if (fields == 0) return RLM_SQL_ERROR;
+
+       MEM(names = talloc_array(handle, char const *, fields));
+
+       for (i = 0; i < fields; i++) {
+               char *p;
+
+               switch (SQLColAttribute(conn->stmt, i, SQL_DESC_BASE_COLUMN_NAME,
+                                       field, sizeof(field), &len, NULL)) {
+               case SQL_INVALID_HANDLE:
+               case SQL_ERROR:
+                       ERROR("Failed retrieving field name at index %i", i);
+                       talloc_free(names);
+                       return RLM_SQL_ERROR;
+
+               default:
+                       break;
+               }
+
+               MEM(p = talloc_array(names, char, (size_t)len + 1));
+               strlcpy(p, field, (size_t)len + 1);
+               names[i] = p;
+       }
+       *out = names;
+
+       return RLM_SQL_OK;
+}
+
+static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
        int c, i;
        SQLINTEGER len, slen;
        rlm_sql_row_t retval;
-       rlm_sql_db2_sock *sock;
+       rlm_sql_db2_conn_t *conn;
 
-       sock = handle->conn;
+       conn = handle->conn;
 
        c = sql_num_fields(handle, config);
        retval = (rlm_sql_row_t)rad_malloc(c*sizeof(char*)+1);
        memset(retval, 0, c*sizeof(char*)+1);
 
        /* advance cursor */
-       if(SQLFetch(sock->stmt) == SQL_NO_DATA_FOUND) {
+       if (SQLFetch(conn->stmt) == SQL_NO_DATA_FOUND) {
                handle->row = NULL;
-               goto error;
+               for (i = 0; i < c; i++) free(retval[i]);
+               free(retval);
+               return RLM_SQL_NO_MORE_ROWS;
        }
 
-       for(i = 0; i < c; i++) {
+       for (i = 0; i < c; i++) {
                /* get column length */
-               SQLColAttribute(sock->stmt,
-                               i+1, SQL_DESC_DISPLAY_SIZE,
-                               NULL, 0, NULL, &len);
-               retval[i] = (char*)rad_malloc(len+1);
+               SQLColAttribute(conn->stmt, i+1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &len);
+
+               retval[i] = rad_malloc(len+1);
+
                /* get the actual column */
-               SQLGetData(sock->stmt,
-                               i+1, SQL_C_CHAR, retval[i], len+1, &slen);
-               if(slen == SQL_NULL_DATA)
+               SQLGetData(conn->stmt, i + 1, SQL_C_CHAR, retval[i], len+1, &slen);
+               if(slen == SQL_NULL_DATA) {
                        retval[i][0] = '\0';
+               }
        }
 
        handle->row = retval;
-       return 0;
-
-error:
-       for(i = 0; i < c; i++) {
-               free(retval[i]);
-       }
-       free(retval);
-       return -1;
+       return RLM_SQL_OK;
 }
 
-/*************************************************************************
- *
- *     Function: sql_free_result
- *
- *     Purpose: database specific free_result. Frees memory allocated
- *               for a result set
- *
- *************************************************************************/
-static int sql_free_result(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
+static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
-       rlm_sql_db2_sock *sock;
-       sock = handle->conn;
-       SQLFreeHandle(SQL_HANDLE_STMT, sock->stmt);
-       return 0;
-}
-
+       rlm_sql_db2_conn_t *conn;
+       conn = handle->conn;
+       SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
 
+       return RLM_SQL_OK;
+}
 
-/*************************************************************************
- *
- *     Function: sql_error
+/** Retrieves any errors associated with the connection handle
  *
- *     Purpose: database specific error. Returns error associated with
- *               connection
+ * @note Caller will free any memory allocated in ctx.
  *
- *************************************************************************/
-static const char *sql_error(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
+ * @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, UNUSED rlm_sql_config_t *config)
 {
-       /* this should really be enough, if not, you still got the sqlstate */
-#define MSGLEN 512
-       char sqlstate[6];
-       char msg[MSGLEN];
-       char *retval;
-       SQLINTEGER err;
-       SQLSMALLINT rl;
-       rlm_sql_db2_sock *sock;
-
-       sock = handle->conn;
-
-       SQLGetDiagRec(SQL_HANDLE_STMT, sock->stmt,
-                       1, sqlstate, &err, msg, MSGLEN, &rl);
-       retval = (char*)rad_malloc(strlen(msg)+20);
-       sprintf(retval, "SQLSTATE %s: %s", sqlstate, msg);
-       return retval;
-}
+       char                    state[6];
+       char                    errbuff[1024];
+       SQLINTEGER              err;
+       SQLSMALLINT             rl;
+       rlm_sql_db2_conn_t      *conn = handle->conn;
 
+       rad_assert(conn);
+       rad_assert(outlen > 0);
 
-/*************************************************************************
- *
- *     Function: sql_close
- *
- *     Purpose: database specific close. Closes an open database
- *               connection
- *
- *************************************************************************/
-static int sql_close(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
-{
-       rlm_sql_db2_sock *sock;
+       errbuff[0] = '\0';
+       SQLGetDiagRec(SQL_HANDLE_STMT, conn->stmt, 1, (SQLCHAR *) state, &err,
+                     (SQLCHAR *) errbuff, sizeof(errbuff), &rl);
+       if (errbuff[0] == '\0') return 0;
 
-       sock = handle->conn;
+       out[0].type = L_ERR;
+       out[0].msg = talloc_asprintf(ctx, "%s: %s", state, errbuff);
 
-       SQLFreeHandle(SQL_HANDLE_DBC, sock->hdbc);
-       SQLFreeHandle(SQL_HANDLE_ENV, sock->henv);
-       return 0;
+       return 1;
 }
 
-
-/*************************************************************************
- *
- *     Function: sql_finish_query
- *
- *     Purpose: End the query, such as freeing memory
- *
- *************************************************************************/
-static int sql_finish_query(UNUSED rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
+static sql_rcode_t sql_finish_query(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
-       return 0;
+       return RLM_SQL_OK;
 }
 
-
-
-/*************************************************************************
- *
- *     Function: sql_finish_select_query
- *
- *     Purpose: End the select query, such as freeing memory or result
- *
- *************************************************************************/
-static int sql_finish_select_query(rlm_sql_handle_t * handle, rlm_sql_config_t *config)
+static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
        return sql_finish_query(handle, config);
 }
 
-
-/*************************************************************************
- *
- *     Function: sql_affected_rows
- *
- *     Purpose: Return the number of rows affected by the last query.
- *
- *************************************************************************/
-static int sql_affected_rows(rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
+static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
        SQLINTEGER c;
-       rlm_sql_db2_sock *sock;
+       rlm_sql_db2_conn_t *conn = handle->conn;
 
-       sock = handle->conn;
+       SQLRowCount(conn->stmt, &c);
 
-       SQLRowCount(sock->stmt, &c);
        return c;
 }
 
-
-static int
-not_implemented(UNUSED rlm_sql_handle_t * handle, UNUSED rlm_sql_config_t *config)
-{
-       radlog(L_ERR, "sql_db2: calling unimplemented function");
-       exit(1);
-}
-
-
 /* Exported to rlm_sql */
+extern rlm_sql_module_t rlm_sql_db2;
 rlm_sql_module_t rlm_sql_db2 = {
-       "rlm_sql_db2",
-       NULL,
-       sql_init_socket,
-       sql_destroy_socket, /* sql_destroy_socket*/
-       sql_query,
-       sql_select_query,
-       not_implemented, /* sql_store_result */
-       sql_num_fields,
-       not_implemented, /* sql_num_rows */
-       sql_fetch_row,
-       sql_free_result,
-       sql_error,
-       sql_close,
-       sql_finish_query,
-       sql_finish_select_query,
-       sql_affected_rows,
+       .name                           = "rlm_sql_db2",
+       .sql_socket_init                = sql_socket_init,
+       .sql_query                      = sql_query,
+       .sql_select_query               = sql_select_query,
+       .sql_num_fields                 = sql_num_fields,
+       .sql_affected_rows              = sql_affected_rows,
+       .sql_fields                     = sql_fields,
+       .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
 };