FR-AD-003 - Pass correct statement length into sqlite3_prepare[_v2]
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_sqlite / rlm_sql_sqlite.c
index e06333b..9cf1aff 100644 (file)
@@ -1,7 +1,8 @@
 /*
  *   This program is is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License, version 2 if the
- *   License as published by the Free Software Foundation.
+ *   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
  * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
  * @copyright 2007 Apple Inc.
  */
-
-#include <freeradius-devel/ident.h>
 RCSID("$Id$")
 
 #include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
 
 #include <fcntl.h>
 #include <sys/stat.h>
@@ -44,6 +44,10 @@ RCSID("$Id$")
 #  define SQLITE_OPEN_NOMUTEX 0
 #endif
 
+#ifndef HAVE_SQLITE3_INT64
+typedef sqlite_int64 sqlite3_int64;
+#endif
+
 typedef struct rlm_sql_sqlite_conn {
        sqlite3 *db;
        sqlite3_stmt *statement;
@@ -51,138 +55,265 @@ typedef struct rlm_sql_sqlite_conn {
 } rlm_sql_sqlite_conn_t;
 
 typedef struct rlm_sql_sqlite_config {
-       const char *filename;
-       const char *bootstrap;
+       char const      *filename;
+       uint32_t        busy_timeout;
 } rlm_sql_sqlite_config_t;
 
 static const CONF_PARSER driver_config[] = {
-       {"filename", PW_TYPE_STRING_PTR,
-        offsetof(rlm_sql_sqlite_config_t, filename), NULL, NULL},
-       {"bootstrap", PW_TYPE_STRING_PTR,
-        offsetof(rlm_sql_sqlite_config_t, bootstrap), NULL, NULL},
-       
-       {NULL, -1, 0, NULL, NULL}
+       { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_OUTPUT | PW_TYPE_REQUIRED, rlm_sql_sqlite_config_t, filename), NULL },
+       { "busy_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_sqlite_config_t, busy_timeout), "200" },
+       CONF_PARSER_TERMINATOR
 };
 
-static int sql_check_error(sqlite3 *db)
+/** Convert an sqlite status code to an sql_rcode_t
+ *
+ * @param status to convert.
+ * @return
+ *     - RLM_SQL_OK - If no errors found.
+ *     - RLM_SQL_ERROR - If a known, non-fatal, error occurred.
+ *     - RLM_SQL_ALT_QUERY - If a constraints violation occurred.
+ *     - RLM_SQL_RECONNECT - Anything else, we assume the connection can no longer be used.
+ */
+static sql_rcode_t sql_error_to_rcode(int status)
 {
-       int error = sqlite3_errcode(db);
-       switch(error) {
+       /*
+        *      Lowest byte is error category, other byte may contain
+        *      the extended error, depending on version.
+        */
+       switch (status & 0xff) {
        /*
         *      Not errors
         */
        case SQLITE_OK:
        case SQLITE_DONE:
        case SQLITE_ROW:
-               return 0;
+               return RLM_SQL_OK;
        /*
         *      User/transient errors
         */
        case SQLITE_ERROR:      /* SQL error or missing database */
        case SQLITE_FULL:
-       case SQLITE_CONSTRAINT:
        case SQLITE_MISMATCH:
-               radlog(L_ERR, "rlm_sql_sqlite: Error (%d): %s", error, sqlite3_errmsg(db));
-               
-               return -1;
-               break;
-               
+               return RLM_SQL_ERROR;
+
+       /*
+        *      Constraints violations
+        */
+       case SQLITE_CONSTRAINT:
+               return RLM_SQL_ALT_QUERY;
+
        /*
         *      Errors with the handle, that probably require reinitialisation
         */
        default:
-               radlog(L_ERR, "rlm_sql_sqlite: Handle is unusable, error (%d): %s", error, sqlite3_errmsg(db));
-               return SQL_DOWN;
+               return RLM_SQL_RECONNECT;
+       }
+}
+
+/** Determine if an error occurred, and what type of error it was
+ *
+ * @param db handle to extract error from (may be NULL).
+ * @param status to check (if unused, set to SQLITE_OK).
+ * @return
+ *     - RLM_SQL_OK - If no errors found.
+ *     - RLM_SQL_ERROR - If a known, non-fatal, error occurred.
+ *     - RLM_SQL_ALT_QUERY - If a constraints violation occurred.
+ *     - RLM_SQL_RECONNECT - Anything else. We assume the connection can no longer be used.
+ */
+static sql_rcode_t sql_check_error(sqlite3 *db, int status)
+{
+       int hstatus = SQLITE_OK;
+
+       if (db) {
+               hstatus = sqlite3_errcode(db);
+               switch (hstatus & 0xff) {
+               case SQLITE_OK:
+               case SQLITE_DONE:
+               case SQLITE_ROW:
+                       hstatus = SQLITE_OK;
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       switch (status & 0xff) {
+       case SQLITE_OK:
+       case SQLITE_DONE:
+       case SQLITE_ROW:
+               status = SQLITE_OK;
+               break;
+
+       default:
                break;
        }
+
+       if (status != SQLITE_OK) return sql_error_to_rcode(status);
+       if (hstatus != SQLITE_OK) return sql_error_to_rcode(status);
+
+       return RLM_SQL_OK;
 }
 
-#ifdef HAVE_SQLITE_V2_API
-static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, const char *filename)
+/** Print an error to the global debug log
+ *
+ * If status does not indicate success, write an error to the global error log.
+ *
+ * @note The error code will be appended to the fmt string in the format ": code 0x<hex> (<int>)[: <string>]".
+ *
+ * @param db handle to extract error from (may be NULL).
+ * @param status to check (if unused, set to SQLITE_OK).
+ * @param fmt to preprend.
+ * @param ... arguments to fmt.
+ */
+static void sql_print_error(sqlite3 *db, int status, char const *fmt, ...)
+       CC_HINT(format (printf, 3, 4)) CC_HINT(nonnull (3));
+static void sql_print_error(sqlite3 *db, int status, char const *fmt, ...)
 {
-       ssize_t len;
-       char *buffer;
-       char *p, *q, *s;
-       int cl;
-       FILE *f;
-       struct stat finfo;
+       va_list ap;
+       char *p;
+       int hstatus = SQLITE_OK;
+
+       if (db) {
+               hstatus = sqlite3_errcode(db);
+               switch (hstatus & 0xff) {
+               case SQLITE_OK:
+               case SQLITE_DONE:
+               case SQLITE_ROW:
+                       hstatus = SQLITE_OK;
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       switch (status & 0xff) {
+       case SQLITE_OK:
+       case SQLITE_DONE:
+       case SQLITE_ROW:
+               status = SQLITE_OK;
+               break;
+
+       default:
+               break;
+       }
+
+       /*
+        *      No errors!
+        */
+       if ((hstatus == SQLITE_OK) && (status == SQLITE_OK)) return;
+
+       /*
+        *      At least one error...
+        */
+       va_start(ap, fmt);
+       MEM(p = talloc_vasprintf(NULL, fmt, ap));
+       va_end(ap);
+
+       /*
+        *      Disagreement between handle, and function return code,
+        *      print them both.
+        */
+       if ((status != SQLITE_OK) && (status != hstatus)) {
+#ifdef HAVE_SQLITE3_ERRSTR
+               ERROR("rlm_sql_sqlite: %s: Code 0x%04x (%i): %s", p, status, status, sqlite3_errstr(status));
+#else
+               ERROR("rlm_sql_sqlite: %s: Code 0x%04x (%i)", p, status, status);
+#endif
+       }
+
+       if (hstatus != SQLITE_OK) ERROR("rlm_sql_sqlite: %s: Code 0x%04x (%i): %s",
+                                       p, hstatus, hstatus, sqlite3_errmsg(db));
+}
+
+#ifdef HAVE_SQLITE3_OPEN_V2
+static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename)
+{
+       ssize_t         len;
+       int             statement_cnt = 0;
+       char            *buffer;
+       char            *p, *q;
+       int             cl;
+       FILE            *f;
+       struct stat     finfo;
 
        int status;
        sqlite3_stmt *statement;
-       const char *z_tail;
+       char const *z_tail;
 
-       radlog(L_INFO, "rlm_sql_sqlite: Executing SQL statements from file \"%s\"", filename);
+       INFO("rlm_sql_sqlite: Executing SQL statements from file \"%s\"", filename);
 
        f = fopen(filename, "r");
        if (!f) {
-               radlog(L_ERR, "rlm_sql_sqlite: Failed opening SQL file \"%s\": %s", filename,
-                      strerror(errno));
-       
+               ERROR("rlm_sql_sqlite: Failed opening SQL file \"%s\": %s", filename,
+                      fr_syserror(errno));
+
                return -1;
        }
-       
+
        if (fstat(fileno(f), &finfo) < 0) {
-               radlog(L_ERR, "rlm_sql_sqlite: Failed stating SQL file \"%s\": %s", filename,
-                      strerror(errno));
-               
+               ERROR("rlm_sql_sqlite: Failed stating SQL file \"%s\": %s", filename,
+                      fr_syserror(errno));
+
                fclose(f);
 
                return -1;
        }
-       
+
        if (finfo.st_size > BOOTSTRAP_MAX) {
                too_big:
-               radlog(L_ERR, "rlm_sql_sqlite: Size of SQL (%zu) file exceeds limit (%uk)",
+               ERROR("rlm_sql_sqlite: Size of SQL (%zu) file exceeds limit (%uk)",
                       (size_t) finfo.st_size / 1024, BOOTSTRAP_MAX / 1024);
-               
+
                fclose(f);
 
                return -1;
        }
-       
+
        MEM(buffer = talloc_array(ctx, char, finfo.st_size + 1));
        len = fread(buffer, sizeof(char), finfo.st_size + 1, f);
        if (len > finfo.st_size) {
                talloc_free(buffer);
                goto too_big;
        }
-       
+
        if (!len) {
                if (ferror(f)) {
-                       radlog(L_ERR, "rlm_sql_sqlite: Error reading SQL file: %s", strerror(errno));
-                       
+                       ERROR("rlm_sql_sqlite: Error reading SQL file: %s", fr_syserror(errno));
+
                        fclose(f);
                        talloc_free(buffer);
 
                        return -1;
                }
-               
-               radlog(L_DBG, "rlm_sql_sqlite: Ignoring empty SQL file");
-               
+
+               DEBUG("rlm_sql_sqlite: Ignoring empty SQL file");
+
                fclose(f);
                talloc_free(buffer);
-               
+
                return 0;
        }
-       
+
        buffer[len] = '\0';
        fclose(f);
 
        /*
-        *      Check if input data is UTF-8.  Allow CR/LF, too.
+        *      Check if input data is UTF-8.  Allow CR/LF \t, too.
         */
        for (p = buffer; p < (buffer + len); p += cl) {
                if (*p < ' ') {
-                       if ((*p != 0x0a) && (*p != 0x0d)) break;
+                       if ((*p != 0x0a) && (*p != 0x0d) && (*p != '\t')) break;
                        cl = 1;
                } else {
-                       cl = fr_utf8_char((uint8_t *) p);
+                       cl = fr_utf8_char((uint8_t *) p, -1);
                        if (!cl) break;
                }
        }
-       
+
        if ((p - buffer) != len) {
-               radlog(L_ERR, "rlm_sql_sqlite: Bootstrap file contains non-UTF8 char at offset %zu", p - buffer);
+               ERROR("rlm_sql_sqlite: Bootstrap file contains non-UTF8 char at offset %zu", p - buffer);
                talloc_free(buffer);
                return -1;
        }
@@ -190,33 +321,45 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, const char *filename)
        /*
         *      Statement delimiter is ;\n
         */
-       s = p = buffer;
+       p = buffer;
        while ((q = strchr(p, ';'))) {
-               if (q[1] != '\n') {
+               if ((q[1] != '\n') && (q[1] != '\0')) {
                        p = q + 1;
+                       statement_cnt++;
                        continue;
                }
-               
-               *q = '\0';
-               
-               (void) sqlite3_prepare_v2(db, s, len, &statement, &z_tail);
-               if (sql_check_error(db)) {
+
+#ifdef HAVE_SQLITE3_PREPARE_V2
+               status = sqlite3_prepare_v2(db, p, q - p, &statement, &z_tail);
+#else
+               status = sqlite3_prepare(db, p, q - p, &statement, &z_tail);
+#endif
+
+               if (sql_check_error(db, status) != RLM_SQL_OK) {
+                       sql_print_error(db, status, "Failed preparing statement %i", statement_cnt);
+                       talloc_free(buffer);
+                       return -1;
+               }
+
+               status = sqlite3_step(statement);
+               if (sql_check_error(db, status) != RLM_SQL_OK) {
+                       sql_print_error(db, status, "Failed executing statement %i", statement_cnt);
+                       sqlite3_finalize(statement);
                        talloc_free(buffer);
-                       return -1;      
+                       return -1;
                }
-       
-               (void) sqlite3_step(statement);
-               status = sql_check_error(db);
-       
-               (void) sqlite3_finalize(statement);
-               if (status || sql_check_error(db)) {
+
+               status = sqlite3_finalize(statement);
+               if (sql_check_error(db, status) != RLM_SQL_OK) {
+                       sql_print_error(db, status, "Failed finalizing statement %i", statement_cnt);
                        talloc_free(buffer);
-                       return -1;      
+                       return -1;
                }
-               
-               p = s = q + 1;
+
+               statement_cnt++;
+               p = q + 1;
        }
-       
+
        talloc_free(buffer);
        return 0;
 }
@@ -224,327 +367,382 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, const char *filename)
 
 static int mod_instantiate(CONF_SECTION *conf, rlm_sql_config_t *config)
 {
+       static bool version_done;
+
+       bool exists;
        rlm_sql_sqlite_config_t *driver;
-       int exists;
+       struct stat buf;
 
-       DEBUG("rlm_sql_sqlite: SQLite library version: %s", sqlite3_libversion());
+       if (!version_done) {
+               version_done = true;
 
-       if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
-               DEBUG2("rlm_sql_sqlite: SQLite library version (%s) is different from the version the server was "
-                      "originally built against (%s), this may cause issues",
-                      sqlite3_libversion(), SQLITE_VERSION);
+               if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
+                       WARN("rlm_sql_sqlite: libsqlite version changed since the server was built");
+                       WARN("rlm_sql_sqlite: linked: %s built: %s", sqlite3_libversion(), SQLITE_VERSION);
+               }
+               INFO("rlm_sql_sqlite: libsqlite version: %s", sqlite3_libversion());
        }
-       
+
        MEM(driver = config->driver = talloc_zero(config, rlm_sql_sqlite_config_t));
-       
        if (cf_section_parse(conf, driver, driver_config) < 0) {
                return -1;
        }
-       
        if (!driver->filename) {
-               MEM(driver->filename = talloc_asprintf(driver, "%s/%s", radius_dir, config->sql_db));
+               MEM(driver->filename = talloc_typed_asprintf(driver, "%s/%s", get_radius_dir(), config->sql_db));
        }
-       
-       exists = rad_file_exists(driver->filename);
-       if (exists < 0) {
-               radlog(L_ERR, "rlm_sql_sqlite: Database exists, but couldn't be opened: %s", strerror(errno));
-       
+
+       if (stat(driver->filename, &buf) == 0) {
+               exists = true;
+       } else if (errno == ENOENT) {
+               exists = false;
+       } else {
+               ERROR("rlm_sql_sqlite: Database exists, but couldn't be opened: %s", fr_syserror(errno));
                return -1;
        }
 
-       if (driver->bootstrap && !exists) {
-#ifdef HAVE_SQLITE_V2_API
-               int status;
-               int ret;
-               char *p;
-               char *buff;
-               sqlite3 *db = NULL;
-               
-               radlog(L_INFO, "rlm_sql_sqlite: Database doesn't exist, creating it and loading schema");
-               
+       if (cf_pair_find(conf, "bootstrap") && !exists) {
+#  ifdef HAVE_SQLITE3_OPEN_V2
+               int             status;
+               int             ret;
+               char const      *p;
+               char            *buff;
+               sqlite3         *db = NULL;
+               CONF_PAIR       *cp;
+
+               INFO("rlm_sql_sqlite: Database doesn't exist, creating it and loading schema");
+
                p = strrchr(driver->filename, '/');
                if (p) {
                        size_t len = (p - driver->filename) + 1;
-                       
+
                        buff = talloc_array(conf, char, len);
                        strlcpy(buff, driver->filename, len);
                } else {
-                       MEM(buff = talloc_strdup(conf, driver->filename));
-               }
-               
-               if (rad_mkdir(buff, 0700) < 0) {
-                       radlog(L_ERR, "rlm_sql_sqlite: Failed creating directory for SQLite database");
-                       
-                       talloc_free(buff);
-                       
-                       return -1;
+                       MEM(buff = talloc_typed_strdup(conf, driver->filename));
                }
 
+               ret = rad_mkdir(buff, 0700, -1, -1);
                talloc_free(buff);
+               if (ret < 0) {
+                       ERROR("rlm_sql_sqlite: Failed creating directory for SQLite database: %s", fr_syserror(errno));
+
+                       return -1;
+               };
 
                status = sqlite3_open_v2(driver->filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
                if (!db) {
-                       radlog(L_ERR, "rlm_sql_sqlite: Failed creating opening/creating SQLite database, error "
-                              "code (%u)", status);
-                       
+#    ifdef HAVE_SQLITE3_ERRSTR
+                       ERROR("rlm_sql_sqlite: Failed creating opening/creating SQLite database: %s",
+                             sqlite3_errstr(status));
+#    else
+                       ERROR("rlm_sql_sqlite: Failed creating opening/creating SQLite database, got code (%i)",
+                             status);
+#    endif
+
                        goto unlink;
                }
-               
-               if (sql_check_error(db)) {
+
+               if (sql_check_error(db, status) != RLM_SQL_OK) {
                        (void) sqlite3_close(db);
-                       
+
                        goto unlink;
                }
-               
-               ret = sql_loadfile(conf, db, driver->bootstrap);
-               
+
+               /*
+                *      Execute multiple bootstrap SQL files in order
+                */
+               for (cp = cf_pair_find(conf, "bootstrap");
+                    cp;
+                    cp = cf_pair_find_next(conf, cp, "bootstrap")) {
+                       p = cf_pair_value(cp);
+                       if (!p) continue;
+
+                       ret = sql_loadfile(conf, db, p);
+                       if (ret < 0) goto unlink;
+               }
+
                status = sqlite3_close(db);
                if (status != SQLITE_OK) {
-                       radlog(L_ERR, "rlm_sql_sqlite: Error closing SQLite handle, error code (%u)", status);
+               /*
+                *      Safer to use sqlite3_errstr here, just in case the handle is in a weird state
+                */
+#  ifdef HAVE_SQLITE3_ERRSTR
+                       ERROR("rlm_sql_sqlite: Error closing SQLite handle: %s", sqlite3_errstr(status));
+#  else
+                       ERROR("rlm_sql_sqlite: Error closing SQLite handle, got code (%i)", status);
+#  endif
+
                        goto unlink;
                }
-               
+
                if (ret < 0) {
-                       unlink:
-                       if (unlink(driver->filename) < 0) {
-                               radlog(L_ERR, "rlm_sql_sqlite: Error removing partially initialised database: %s",
-                                      strerror(errno));
+               unlink:
+                       if ((unlink(driver->filename) < 0) && (errno != ENOENT)) {
+                               ERROR("rlm_sql_sqlite: Error removing partially initialised database: %s",
+                                     fr_syserror(errno));
                        }
                        return -1;
                }
 #else
-               DEBUGW("rlm_sql_sqlite: sqlite3_open_v2() not available, cannot bootstrap database. "
+               WARN("rlm_sql_sqlite: sqlite3_open_v2() not available, cannot bootstrap database. "
                       "Upgrade to SQLite >= 3.5.1 if you need this functionality");
 #endif
        }
-       
+
        return 0;
 }
 
-static int sql_socket_destructor(void *c)
+static int _sql_socket_destructor(rlm_sql_sqlite_conn_t *conn)
 {
        int status = 0;
-       rlm_sql_sqlite_conn_t * conn = c;
-       
+
        DEBUG2("rlm_sql_sqlite: Socket destructor called, closing socket");
-       
+
        if (conn->db) {
                status = sqlite3_close(conn->db);
-               if (status != SQLITE_OK) {
-                       DEBUGW("rlm_sql_sqlite: Got SQLite error code (%u) when closing socket", status);
-               }
+               if (status != SQLITE_OK) WARN("rlm_sql_sqlite: Got SQLite error code (%u) when closing socket", status);
        }
-       
+
        return 0;
 }
 
-static int sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
+static void _sql_greatest(sqlite3_context *ctx, int num_values, sqlite3_value **values)
+{
+       int i;
+       sqlite3_int64 value, max = 0;
+
+       for (i = 0; i < num_values; i++) {
+               value = sqlite3_value_int64(values[i]);
+               if (value > max) {
+                       max = value;
+               }
+       }
+
+       sqlite3_result_int64(ctx, max);
+}
+
+static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
        rlm_sql_sqlite_conn_t *conn;
        rlm_sql_sqlite_config_t *driver = config->driver;
-       
+
        int status;
 
        MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_sqlite_conn_t));
-       talloc_set_destructor((void *) conn, sql_socket_destructor);
-
-       radlog(L_INFO, "rlm_sql_sqlite: Opening SQLite database \"%s\"", driver->filename);
+       talloc_set_destructor(conn, _sql_socket_destructor);
 
-#ifdef HAVE_SQLITE_V2_API      
+       INFO("rlm_sql_sqlite: Opening SQLite database \"%s\"", driver->filename);
+#ifdef HAVE_SQLITE3_OPEN_V2
        status = sqlite3_open_v2(driver->filename, &(conn->db), SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX, NULL);
 #else
        status = sqlite3_open(driver->filename, &(conn->db));
 #endif
-       if (!conn->db) {
-               radlog(L_ERR, "rlm_sql_sqlite: Failed creating opening/creating SQLite database error code (%u)",
-                      status);
-               
-               return -1;
+
+       if (!conn->db || (sql_check_error(conn->db, status) != RLM_SQL_OK)) {
+               sql_print_error(conn->db, status, "Error opening SQLite database \"%s\"", driver->filename);
+               return RLM_SQL_ERROR;
        }
-       
-       if (sql_check_error(conn->db)) {
-               return -1;
+       status = sqlite3_busy_timeout(conn->db, driver->busy_timeout);
+       if (sql_check_error(conn->db, status) != RLM_SQL_OK) {
+               sql_print_error(conn->db, status, "Error setting busy timeout");
+               return RLM_SQL_ERROR;
        }
-       
+
        /*
         *      Enable extended return codes for extra debugging info.
         */
+#ifdef HAVE_SQLITE3_EXTENDED_RESULT_CODES
        status = sqlite3_extended_result_codes(conn->db, 1);
-       
-       if (sql_check_error(conn->db)) {
-               return -1;
+       if (sql_check_error(conn->db, status) != RLM_SQL_OK) {
+               sql_print_error(conn->db, status, "Error enabling extended result codes");
+               return RLM_SQL_ERROR;
        }
-       
-       return 0;
+#endif
+
+#ifdef HAVE_SQLITE3_CREATE_FUNCTION_V2
+       status = sqlite3_create_function_v2(conn->db, "GREATEST", -1, SQLITE_ANY, NULL,
+                                           _sql_greatest, NULL, NULL, NULL);
+#else
+       status = sqlite3_create_function(conn->db, "GREATEST", -1, SQLITE_ANY, NULL,
+                                        _sql_greatest, NULL, NULL);
+#endif
+       if (sql_check_error(conn->db, status) != RLM_SQL_OK) {
+               sql_print_error(conn->db, status, "Failed registering 'GREATEST' sql function");
+               return RLM_SQL_ERROR;
+       }
+
+       return RLM_SQL_OK;
 }
 
-static int sql_select_query(rlm_sql_handle_t *handle,
-                          UNUSED rlm_sql_config_t *config, char *querystr)
+static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
 {
-       int status;
-       rlm_sql_sqlite_conn_t *conn = handle->conn;
-       const char *z_tail;
-       
-#ifdef HAVE_SQLITE_V2_API
-       status = sqlite3_prepare_v2(conn->db, querystr, strlen(querystr), &conn->statement, &z_tail);
+       rlm_sql_sqlite_conn_t   *conn = handle->conn;
+       char const              *z_tail;
+       int                     status;
+
+#ifdef HAVE_SQLITE3_PREPARE_V2
+       status = sqlite3_prepare_v2(conn->db, query, strlen(query), &conn->statement, &z_tail);
 #else
-       status = sqlite3_prepare(conn->db, querystr, strlen(querystr), &conn->statement, &z_tail);
+       status = sqlite3_prepare(conn->db, query, strlen(query), &conn->statement, &z_tail);
 #endif
-                               
+
        conn->col_count = 0;
-               
-       return sql_check_error(conn->db);
+
+       return sql_check_error(conn->db, status);
 }
 
 
-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)
 {
-       int status;
-       rlm_sql_sqlite_conn_t *conn = handle->conn;
-       const char *z_tail;
 
-#ifdef HAVE_SQLITE_V2_API
-       status = sqlite3_prepare_v2(conn->db, querystr, strlen(querystr), &conn->statement, &z_tail);
+       sql_rcode_t             rcode;
+       rlm_sql_sqlite_conn_t   *conn = handle->conn;
+       char const              *z_tail;
+       int                     status;
+
+#ifdef HAVE_SQLITE3_PREPARE_V2
+       status = sqlite3_prepare_v2(conn->db, query, strlen(query), &conn->statement, &z_tail);
 #else
-       status = sqlite3_prepare(conn->db, querystr, strlen(querystr), &conn->statement, &z_tail);
-#endif                         
-       status = sqlite3_step(conn->statement);
-               
-       return sql_check_error(conn->db);
-}
+       status = sqlite3_prepare(conn->db, query, strlen(query), &conn->statement, &z_tail);
+#endif
+       rcode = sql_check_error(conn->db, status);
+       if (rcode != RLM_SQL_OK) return rcode;
 
-static int sql_store_result(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
-{
-       return 0;
+       status = sqlite3_step(conn->statement);
+       return sql_check_error(conn->db, status);
 }
 
-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)
 {
        rlm_sql_sqlite_conn_t *conn = handle->conn;
-       
+
        if (conn->statement) {
                return sqlite3_column_count(conn->statement);
        }
-       
+
        return 0;
 }
 
 static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
        rlm_sql_sqlite_conn_t *conn = handle->conn;
-       
+
        if (conn->statement) {
                return sqlite3_data_count(conn->statement);
        }
-       
+
        return 0;
 }
 
-static int sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
+static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
+{
+       rlm_sql_sqlite_conn_t *conn = handle->conn;
+
+       int             fields, i;
+       char const      **names;
+
+       fields = sqlite3_column_count(conn->statement);
+       if (fields <= 0) return RLM_SQL_ERROR;
+
+       MEM(names = talloc_zero_array(handle, char const *, fields + 1));
+
+       for (i = 0; i < fields; i++) names[i] = sqlite3_column_name(conn->statement, i);
+       *out = names;
+
+       return RLM_SQL_OK;
+}
+
+static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
        int status;
        rlm_sql_sqlite_conn_t *conn = handle->conn;
-       
+
        int i = 0;
-       
+
        char **row;
 
+       TALLOC_FREE(handle->row);
+
        /*
         *      Executes the SQLite query and interates over the results
         */
        status = sqlite3_step(conn->statement);
-       
+
        /*
         *      Error getting next row
         */
-       if (sql_check_error(conn->db)) {
-               return -1;
-       }
+       if (sql_check_error(conn->db, status) != RLM_SQL_OK) return RLM_SQL_ERROR;
 
        /*
         *      No more rows to process (were done)
         */
-       if (status == SQLITE_DONE) {
-               return 1;
-       }
-       
+       if (status == SQLITE_DONE) return RLM_SQL_NO_MORE_ROWS;
+
        /*
         *      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 -1;
-               }
+               if (conn->col_count == 0) return RLM_SQL_ERROR;
        }
 
-       /*
-        *      Free the previous result (also gets called on finish_query)
-        */
-       talloc_free(handle->row);
-       
        MEM(row = handle->row = talloc_zero_array(handle->conn, char *, conn->col_count + 1));
-       
-       for (i = 0; i < conn->col_count; i++)
-       {
-               switch (sqlite3_column_type(conn->statement, i))
-               {
-               case SQLITE_INTEGER:    
-                       MEM(row[i] = talloc_asprintf(row, "%d", sqlite3_column_int(conn->statement, i)));
+
+       for (i = 0; i < conn->col_count; i++) {
+               switch (sqlite3_column_type(conn->statement, i)) {
+               case SQLITE_INTEGER:
+                       MEM(row[i] = talloc_typed_asprintf(row, "%d", sqlite3_column_int(conn->statement, i)));
                        break;
-                       
+
                case SQLITE_FLOAT:
-                       MEM(row[i] = talloc_asprintf(row, "%f", sqlite3_column_double(conn->statement, i)));
+                       MEM(row[i] = talloc_typed_asprintf(row, "%f", sqlite3_column_double(conn->statement, i)));
                        break;
-                       
+
                case SQLITE_TEXT:
-                       {
-                               const char *p;
-                               p = (const char *) sqlite3_column_text(conn->statement, i);
-                               
-                               if (p) {
-                                       MEM(row[i] = talloc_strdup(row, p));
-                               }
-                       }
+               {
+                       char const *p;
+                       p = (char const *) sqlite3_column_text(conn->statement, i);
+
+                       if (p) MEM(row[i] = talloc_typed_strdup(row, p));
+               }
                        break;
-                       
+
                case SQLITE_BLOB:
-                       {
-                               const uint8_t *p;
-                               size_t len;
-
-                               p = sqlite3_column_blob(conn->statement, i);
-                               if (p) {
-                                       len = sqlite3_column_bytes(conn->statement, i);
-                                       
-                                       MEM(row[i] = talloc_zero_array(row, char, len + 1));
-                                       memcpy(row[i], p, len);         
-                               }
+               {
+                       uint8_t const *p;
+                       size_t len;
+
+                       p = sqlite3_column_blob(conn->statement, i);
+                       if (p) {
+                               len = sqlite3_column_bytes(conn->statement, i);
+
+                               MEM(row[i] = talloc_zero_array(row, char, len + 1));
+                               memcpy(row[i], p, len);
                        }
+               }
                        break;
-                       
+
                default:
                        break;
                }
        }
-       
-       return 0;
+
+       return RLM_SQL_OK;
 }
 
-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_sqlite_conn_t *conn = handle->conn;
-       
+
        if (conn->statement) {
                TALLOC_FREE(handle->row);
-               
+
                (void) sqlite3_finalize(conn->statement);
                conn->statement = NULL;
                conn->col_count = 0;
        }
-       
+
        /*
         *      There's no point in checking the code returned by finalize
         *      as it'll have already been encountered elsewhere in the code.
@@ -552,23 +750,38 @@ static int sql_free_result(rlm_sql_handle_t *handle,
         *      It's just the last error that occurred processing the
         *      statement.
         */
-       return 0;
+       return RLM_SQL_OK;
 }
 
-static const char *sql_error(rlm_sql_handle_t *handle,
-                            UNUSED rlm_sql_config_t *config)
+/** 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(UNUSED TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
+                       rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
 {
        rlm_sql_sqlite_conn_t *conn = handle->conn;
+       char const *error;
 
-       if (conn->db) {
-               return sqlite3_errmsg(conn->db);
-       }
+       rad_assert(outlen > 0);
+
+       error = sqlite3_errmsg(conn->db);
+       if (!error) return 0;
 
-       return "Invalid handle";
+       out[0].type = L_ERR;
+       out[0].msg = error;
+
+       return 1;
 }
 
-static int sql_finish_query(rlm_sql_handle_t *handle,
-                           UNUSED rlm_sql_config_t *config)
+static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
 {
        return sql_free_result(handle, config);
 }
@@ -578,28 +791,28 @@ static int sql_affected_rows(rlm_sql_handle_t *handle,
 {
        rlm_sql_sqlite_conn_t *conn = handle->conn;
 
-       if (conn->db) {
-               return sqlite3_changes(conn->db);       
-       }
+       if (conn->db) return sqlite3_changes(conn->db);
 
        return -1;
 }
 
 
 /* Exported to rlm_sql */
+extern rlm_sql_module_t rlm_sql_sqlite;
 rlm_sql_module_t rlm_sql_sqlite = {
-       "rlm_sql_sqlite",
-       mod_instantiate,
-       sql_socket_init,
-       sql_query,
-       sql_select_query,
-       sql_store_result,
-       sql_num_fields,
-       sql_num_rows,
-       sql_fetch_row,
-       sql_free_result,
-       sql_error,
-       sql_finish_query,
-       sql_finish_query,
-       sql_affected_rows
+       .name                           = "rlm_sql_sqlite",
+       .flags                          = RLM_SQL_RCODE_FLAGS_ALT_QUERY,
+       .mod_instantiate                = mod_instantiate,
+       .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_fields                     = sql_fields,
+       .sql_free_result                = sql_free_result,
+       .sql_error                      = sql_error,
+       .sql_finish_query               = sql_finish_query,
+       .sql_finish_select_query        = sql_finish_query
 };