Use a proper rcode for no more rows
[freeradius.git] / src / modules / rlm_sql / sql.c
index d210993..51ba11d 100644 (file)
@@ -39,16 +39,20 @@ RCSID("$Id$")
 #ifdef HAVE_PTHREAD_H
 #endif
 
-static int _mod_conn_free(rlm_sql_handle_t *conn)
-{
-       rlm_sql_t *inst = conn->inst;
-
-       rad_assert(inst);
-
-       exec_trigger(NULL, inst->cs, "modules.sql.close", false);
+/*
+ *     Translate rlm_sql rcodes to humanly
+ *     readable reason strings.
+ */
+const FR_NAME_NUMBER sql_rcode_table[] = {
+       { "success",            RLM_SQL_OK              },
+       { "need alt query",     RLM_SQL_ALT_QUERY       },
+       { "server error",       RLM_SQL_ERROR           },
+       { "query invalid",      RLM_SQL_QUERY_INVALID   },
+       { "no connection",      RLM_SQL_RECONNECT       },
+       { "no more rows",       RLM_SQL_NO_MORE_ROWS    },
+       { NULL, 0 }
+};
 
-       return 0;
-}
 
 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
 {
@@ -75,19 +79,9 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
         */
        handle->inst = inst;
 
-       /*
-        *      When something frees this handle the destructor set by
-        *      the driver will be called first, closing any open sockets.
-        *      Then we call our destructor to trigger an modules.sql.close
-        *      event, then all the memory is freed.
-        */
-       talloc_set_destructor(handle, _mod_conn_free);
-
        rcode = (inst->module->sql_socket_init)(handle, inst->config);
        if (rcode != 0) {
        fail:
-               exec_trigger(NULL, inst->cs, "modules.sql.fail", true);
-
                /*
                 *      Destroy any half opened connections.
                 */
@@ -96,36 +90,33 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
        }
 
        if (inst->config->connect_query) {
-               if (rlm_sql_select_query(inst, NULL, &handle, inst->config->connect_query)) {
-                       goto fail;
-               }
+               if (rlm_sql_select_query(inst, NULL, &handle, inst->config->connect_query) != RLM_SQL_OK) goto fail;
                (inst->module->sql_finish_select_query)(handle, inst->config);
        }
 
-       exec_trigger(NULL, inst->cs, "modules.sql.open", false);
        return handle;
 }
 
 /*************************************************************************
  *
- *     Function: sql_userparse
+ *     Function: sql_fr_pair_list_afrom_str
  *
  *     Purpose: Read entries from the database and fill VALUE_PAIR structures
  *
  *************************************************************************/
-int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_row_t row)
+int sql_fr_pair_list_afrom_str(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_row_t row)
 {
        VALUE_PAIR *vp;
        char const *ptr, *value;
        char buf[MAX_STRING_LEN];
        char do_xlat = 0;
-       FR_TOKEN token, operator = T_EOL;
+       FR_TOKEN token, op = T_EOL;
 
        /*
         *      Verify the 'Attribute' field
         */
        if (!row[2] || row[2][0] == '\0') {
-               REDEBUG("The 'Attribute' field is empty or NULL, skipping the entire row");
+               REDEBUG("Attribute field is empty or NULL, skipping the entire row");
                return -1;
        }
 
@@ -134,10 +125,9 @@ int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_
         */
        if (row[4] != NULL && row[4][0] != '\0') {
                ptr = row[4];
-               operator = gettoken(&ptr, buf, sizeof(buf), false);
-               if ((operator < T_OP_ADD) ||
-                   (operator > T_OP_CMP_EQ)) {
-                       REDEBUG("Invalid operator \"%s\" for attribute %s", row[4], row[2]);
+               op = gettoken(&ptr, buf, sizeof(buf), false);
+               if (!fr_assignment_op[op] && !fr_equality_op[op]) {
+                       REDEBUG("Invalid op \"%s\" for attribute %s", row[4], row[2]);
                        return -1;
                }
 
@@ -145,15 +135,21 @@ int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_
                /*
                 *  Complain about empty or invalid 'op' field
                 */
-               operator = T_OP_CMP_EQ;
-               REDEBUG("The 'op' field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
+               op = T_OP_CMP_EQ;
+               REDEBUG("The op field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
                REDEBUG("You MUST FIX THIS if you want the configuration to behave as you expect");
        }
 
        /*
         *      The 'Value' field may be empty or NULL
         */
+       if (!row[3]) {
+               REDEBUG("Value field is empty or NULL, skipping the entire row");
+               return -1;
+       }
+
        value = row[3];
+
        /*
         *      If we have a new-style quoted string, where the
         *      *entire* string is quoted, do xlat's.
@@ -176,9 +172,9 @@ int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_
                 *      Mark the pair to be allocated later.
                 */
                case T_BACK_QUOTED_STRING:
-                       value = NULL;
                        do_xlat = 1;
-                       break;
+
+                       /* FALL-THROUGH */
 
                /*
                 *      Keep the original string.
@@ -192,21 +188,21 @@ int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_
        /*
         *      Create the pair
         */
-       vp = pairmake(ctx, NULL, row[2], NULL, operator);
+       vp = fr_pair_make(ctx, NULL, row[2], NULL, op);
        if (!vp) {
                REDEBUG("Failed to create the pair: %s", fr_strerror());
                return -1;
        }
 
        if (do_xlat) {
-               if (pairmark_xlat(vp, value) < 0) {
-                       REDEBUG("Error marking pair for xlat");
+               if (fr_pair_mark_xlat(vp, value) < 0) {
+                       REDEBUG("Error marking pair for xlat: %s", fr_strerror());
 
                        talloc_free(vp);
                        return -1;
                }
        } else {
-               if (pairparsevalue(vp, value, -1) < 0) {
+               if (fr_pair_value_from_str(vp, value, -1) < 0) {
                        REDEBUG("Error parsing value: %s", fr_strerror());
 
                        talloc_free(vp);
@@ -217,7 +213,7 @@ int sql_userparse(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_
        /*
         *      Add the pair into the packet
         */
-       pairadd(head, vp);
+       fr_pair_add(head, vp);
        return 0;
 }
 
@@ -244,7 +240,7 @@ sql_rcode_t rlm_sql_fetch_row(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_
         */
        ret = (inst->module->sql_fetch_row)(*handle, inst->config);
        if (ret < 0) {
-               ROPTIONAL(RERROR, ERROR, "Error fetching row");
+               MOD_ROPTIONAL(RERROR, ERROR, "Error fetching row");
 
                rlm_sql_print_error(inst, request, *handle, false);
        }
@@ -270,7 +266,7 @@ void rlm_sql_print_error(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *ha
 
        num = (inst->module->sql_error)(handle->log_ctx, log, (sizeof(log) / sizeof(*log)), handle, inst->config);
        if (num == 0) {
-               ROPTIONAL(RERROR, ERROR, "Unknown error");
+               MOD_ROPTIONAL(RERROR, ERROR, "Unknown error");
                return;
        }
 
@@ -281,21 +277,21 @@ void rlm_sql_print_error(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *ha
 
                switch (log[i].type) {
                case L_ERR:
-                       ROPTIONAL(RERROR, ERROR, "%s: %s", driver, log[i].msg);
+                       MOD_ROPTIONAL(RERROR, ERROR, "%s: %s", driver, log[i].msg);
                        break;
 
                case L_WARN:
-                       ROPTIONAL(RWARN, WARN, "%s: %s", driver, log[i].msg);
+                       MOD_ROPTIONAL(RWARN, WARN, "%s: %s", driver, log[i].msg);
                        break;
 
                case L_INFO:
-                       ROPTIONAL(RINFO, INFO, "%s: %s", driver, log[i].msg);
+                       MOD_ROPTIONAL(RINFO, INFO, "%s: %s", driver, log[i].msg);
                        break;
 
                case L_DBG:
                default:
                debug:
-                       ROPTIONAL(RDEBUG, DEBUG, "%s: %s", driver, log[i].msg);
+                       MOD_ROPTIONAL(RDEBUG, DEBUG, "%s: %s", driver, log[i].msg);
                        break;
                }
        }
@@ -305,39 +301,43 @@ void rlm_sql_print_error(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *ha
 
 /** Call the driver's sql_query method, reconnecting if necessary.
  *
+ * @note Caller must call (inst->module->sql_finish_query)(handle, inst->config);
+ *     after they're done with the result.
+ *
  * @param handle to query the database with. *handle should not be NULL, as this indicates
- *       previous reconnection attempt has failed.
+ *     previous reconnection attempt has failed.
+ * @param request Current request.
  * @param inst rlm_sql instance data.
  * @param query to execute. Should not be zero length.
- * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required (also sets *handle = NULL),
- *         RLM_SQL_QUERY_ERROR/RLM_SQL_ERROR on invalid query or connection error, RLM_SQL_DUPLICATE on constraints
- *         violation.
+ * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required
+ *     (also sets *handle = NULL), RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or
+ *     connection error, RLM_SQL_ALT_QUERY on constraints violation.
  */
 sql_rcode_t rlm_sql_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, char const *query)
 {
        int ret = RLM_SQL_ERROR;
        int i, count;
 
+       /* Caller should check they have a valid handle */
+       rad_assert(*handle);
+
        /* There's no query to run, return an error */
        if (query[0] == '\0') {
                if (request) REDEBUG("Zero length query");
-               return RLM_SQL_QUERY_ERROR;
+               return RLM_SQL_QUERY_INVALID;
        }
 
-       /* There's no handle, we need a new one */
-       if (!*handle) return RLM_SQL_RECONNECT;
-
        /*
         *  inst->pool may be NULL is this function is called by mod_conn_create.
         */
-       count = inst->pool ? fr_connection_get_num(inst->pool) : 0;
+       count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
 
        /*
         *  Here we try with each of the existing connections, then try to create
         *  a new connection, then give up.
         */
        for (i = 0; i < (count + 1); i++) {
-               ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query);
+               MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query);
 
                ret = (inst->module->sql_query)(*handle, inst->config, query);
                switch (ret) {
@@ -355,13 +355,38 @@ sql_rcode_t rlm_sql_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **
                        /* Reconnection succeeded, try again with the new handle */
                        continue;
 
-               case RLM_SQL_QUERY_ERROR:
-               case RLM_SQL_ERROR:
+               /*
+                *      These are bad and should make rlm_sql return invalid
+                */
+               case RLM_SQL_QUERY_INVALID:
                        rlm_sql_print_error(inst, request, *handle, false);
+                       (inst->module->sql_finish_query)(*handle, inst->config);
                        break;
 
-               case RLM_SQL_DUPLICATE:
+               /*
+                *      Server or client errors.
+                *
+                *      If the driver claims to be able to distinguish between
+                *      duplicate row errors and other errors, and we hit a
+                *      general error treat it as a failure.
+                *
+                *      Otherwise rewrite it to RLM_SQL_ALT_QUERY.
+                */
+               case RLM_SQL_ERROR:
+                       if (inst->module->flags & RLM_SQL_RCODE_FLAGS_ALT_QUERY) {
+                               rlm_sql_print_error(inst, request, *handle, false);
+                               (inst->module->sql_finish_query)(*handle, inst->config);
+                               break;
+                       }
+                       ret = RLM_SQL_ALT_QUERY;
+                       /* FALL-THROUGH */
+
+               /*
+                *      Driver suggested using an alternative query
+                */
+               case RLM_SQL_ALT_QUERY:
                        rlm_sql_print_error(inst, request, *handle, true);
+                       (inst->module->sql_finish_query)(*handle, inst->config);
                        break;
 
                }
@@ -369,43 +394,49 @@ sql_rcode_t rlm_sql_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **
                return ret;
        }
 
-       ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
+       MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
 
        return RLM_SQL_ERROR;
 }
 
 /** Call the driver's sql_select_query method, reconnecting if necessary.
  *
+ * @note Caller must call (inst->module->sql_finish_select_query)(handle, inst->config);
+ *     after they're done with the result.
+ *
  * @param inst rlm_sql instance data.
  * @param request Current request.
  * @param handle to query the database with. *handle should not be NULL, as this indicates
  *       previous reconnection attempt has failed.
  * @param query to execute. Should not be zero length.
  * @return RLM_SQL_OK on success, RLM_SQL_RECONNECT if a new handle is required (also sets *handle = NULL),
- *         RLM_SQL_QUERY_ERROR/RLM_SQL_ERROR on invalid query or connection error.
+ *         RLM_SQL_QUERY_INVALID/RLM_SQL_ERROR on invalid query or connection error.
  */
 sql_rcode_t rlm_sql_select_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,  char const *query)
 {
        int ret = RLM_SQL_ERROR;
        int i, count;
 
+       /* Caller should check they have a valid handle */
+       rad_assert(*handle);
+
        /* There's no query to run, return an error */
        if (query[0] == '\0') {
                if (request) REDEBUG("Zero length query");
 
-               return RLM_SQL_QUERY_ERROR;
+               return RLM_SQL_QUERY_INVALID;
        }
 
        /*
         *  inst->pool may be NULL is this function is called by mod_conn_create.
         */
-       count = inst->pool ? fr_connection_get_num(inst->pool) : 0;
+       count = inst->pool ? fr_connection_pool_get_num(inst->pool) : 0;
 
        /*
         *  For sanity, for when no connections are viable, and we can't make a new one
         */
        for (i = 0; i < (count + 1); i++) {
-               ROPTIONAL(RDEBUG2, DEBUG2, "Executing select query: %s", query);
+               MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Executing select query: %s", query);
 
                ret = (inst->module->sql_select_query)(*handle, inst->config, query);
                switch (ret) {
@@ -423,17 +454,18 @@ sql_rcode_t rlm_sql_select_query(rlm_sql_t *inst, REQUEST *request, rlm_sql_hand
                        /* Reconnection succeeded, try again with the new handle */
                        continue;
 
-               case RLM_SQL_QUERY_ERROR:
+               case RLM_SQL_QUERY_INVALID:
                case RLM_SQL_ERROR:
                default:
                        rlm_sql_print_error(inst, request, *handle, false);
+                       (inst->module->sql_finish_select_query)(*handle, inst->config);
                        break;
                }
 
                return ret;
        }
 
-       ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
+       MOD_ROPTIONAL(RERROR, ERROR, "Hit reconnection limit");
 
        return RLM_SQL_ERROR;
 }
@@ -458,10 +490,10 @@ int sql_getvpdata(TALLOC_CTX *ctx, rlm_sql_t *inst, REQUEST *request, rlm_sql_ha
        rcode = rlm_sql_select_query(inst, request, handle, query);
        if (rcode != RLM_SQL_OK) return -1; /* error handled by rlm_sql_select_query */
 
-       while (rlm_sql_fetch_row(inst, request, handle) == 0) {
+       while (rlm_sql_fetch_row(inst, request, handle) == RLM_SQL_OK) {
                row = (*handle)->row;
                if (!row) break;
-               if (sql_userparse(ctx, request, pair, row) != 0) {
+               if (sql_fr_pair_list_afrom_str(ctx, request, pair, row) != 0) {
                        REDEBUG("Error parsing user data from database result");
 
                        (inst->module->sql_finish_select_query)(*handle, inst->config);
@@ -487,13 +519,10 @@ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request,
        size_t len;
        bool failed = false;    /* Write the log message outside of the critical region */
 
-       if (section) {
-               filename = section->logfile;
-       } else {
-               filename = inst->config->logfile;
-       }
+       filename = inst->config->logfile;
+       if (section && section->logfile) filename = section->logfile;
 
-       if (!filename) {
+       if (!filename || !*filename) {
                return;
        }
 
@@ -503,7 +532,7 @@ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request,
 
        fd = exfile_open(inst->ef, filename, 0640, true);
        if (fd < 0) {
-               ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->config->xlat_name,
+               ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->name,
                      expanded, fr_syserror(errno));
 
                talloc_free(expanded);
@@ -516,7 +545,7 @@ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request,
        }
 
        if (failed) {
-               ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->config->xlat_name, expanded,
+               ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->name, expanded,
                      fr_syserror(errno));
        }