/*
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 * Copyright (C) 2014 Vivocha S.p.A.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "dbd_sqlite.h"
#include "../mainrelay.h"

#if !defined(TURN_NO_SQLITE)

#include <sqlite3.h>

#if defined(__unix__) || defined(unix) || defined(__APPLE__)
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#include <pthread.h>

#include <stdbool.h>

//////////////////////////////////////////////////

static pthread_mutex_t rc_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t rc_cond = PTHREAD_COND_INITIALIZER;

static int read_threads = 0;
static int write_level = 0;
#if defined(WINDOWS)
static pthread_t write_thread = {0};
static pthread_t const null_thread = {0};
#else
static pthread_t write_thread = 0;
static pthread_t const null_thread = 0;
#endif

static void sqlite_lock(bool const write) {
  pthread_t const pths = pthread_self();

  bool can_move = false;
  while (!can_move) {
    pthread_mutex_lock(&rc_mutex);
    if (write) {
      if ((pthread_equal(write_thread, null_thread) && (read_threads < 1)) || pthread_equal(write_thread, pths)) {
        can_move = true;
        ++write_level;
        write_thread = pths;
      }
    } else if (pthread_equal(write_thread, null_thread) || pthread_equal(write_thread, pths)) {
      can_move = true;
      ++read_threads;
    }
    if (!can_move) {
      pthread_cond_wait(&rc_cond, &rc_mutex);
    }
    pthread_mutex_unlock(&rc_mutex);
  }
}

static void sqlite_unlock(bool const write) {
  pthread_mutex_lock(&rc_mutex);
  if (write) {
    if (--write_level == 0) {
      write_thread = null_thread;
      pthread_cond_broadcast(&rc_cond);
    }
  } else if (--read_threads == 0) {
    pthread_cond_broadcast(&rc_cond);
  }
  pthread_mutex_unlock(&rc_mutex);
}

//////////////////////////////////////////////////

static void sqlite_init_multithreaded(void) {
#if defined(SQLITE_CONFIG_MULTITHREAD)
  if (sqlite3_threadsafe() > 0) {
    int retCode = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
    if (retCode != SQLITE_OK) {
      retCode = sqlite3_config(SQLITE_CONFIG_SERIALIZED);
      if (retCode != SQLITE_OK) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "setting sqlite thread safe mode to serialized failed!!! return code: %d\n",
                      retCode);
        return;
      }
    }
    sqlite3_initialize();
  } else {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Your SQLite database is not compiled to be threadsafe.\n");
  }
#endif
}

static int donot_print_connection_success = 0;

static void fix_user_directory(char *dir0) {
  char *dir = dir0;
  while (*dir == ' ') {
    ++dir;
  }
#if defined(__unix__) || defined(unix) || defined(__APPLE__)
  if (*dir == '~') {
    char *home = getenv("HOME");
    if (!home) {
      struct passwd *pwd = getpwuid(getuid());
      if (!pwd) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot figure out the user's HOME directory (1)\n");
        return;
      } else {
        home = pwd->pw_dir;
        if (!home) {
          TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot figure out the user's HOME directory\n");
          return;
        }
      }
    }
    size_t szh = strlen(home);
    size_t sz = strlen(dir0) + 1 + szh;
    char *dir_fixed = (char *)malloc(sz);
    strncpy(dir_fixed, home, szh);
    strncpy(dir_fixed + szh, dir + 1, (sz - szh - 1));
    strncpy(dir0, dir_fixed, sz);
    free(dir_fixed);
  }
#endif
}

static void init_sqlite_database(sqlite3 *sqliteconnection) {

  const char *statements[] = {
      "CREATE TABLE turnusers_lt ( realm varchar(127) default '', name varchar(512), hmackey char(128), PRIMARY KEY "
      "(realm,name))",
      "CREATE TABLE turn_secret (realm varchar(127) default '', value varchar(127), primary key (realm,value))",
      "CREATE TABLE allowed_peer_ip (realm varchar(127) default '', ip_range varchar(256), primary key "
      "(realm,ip_range))",
      "CREATE TABLE denied_peer_ip (realm varchar(127) default '', ip_range varchar(256), primary key "
      "(realm,ip_range))",
      "CREATE TABLE turn_origin_to_realm (origin varchar(127),realm varchar(127),primary key (origin))",
      "CREATE TABLE turn_realm_option (realm varchar(127) default '',	opt varchar(32),	value "
      "varchar(128),	primary key (realm,opt))",
      "CREATE TABLE oauth_key (kid varchar(128),ikm_key varchar(256),timestamp bigint default 0,lifetime integer "
      "default 0,as_rs_alg varchar(64) default '',realm varchar(127) default '',primary key (kid))",
      "CREATE TABLE admin_user (name varchar(32), realm varchar(127), password varchar(127), primary key (name))",
      NULL};

  for (int i = 0; statements[i] != NULL; ++i) {
    sqlite3_stmt *statement = NULL;
    if (sqlite3_prepare(sqliteconnection, statements[i], -1, &statement, 0) == SQLITE_OK) {
      sqlite3_step(statement);
    }
    sqlite3_finalize(statement);
  }
}

static sqlite3 *get_sqlite_connection(void) {
  static pthread_once_t sqlite_init_once = PTHREAD_ONCE_INIT;

  persistent_users_db_t *pud = get_persistent_users_db();

  sqlite3 *sqliteconnection = (sqlite3 *)pthread_getspecific(connection_key);
  if (sqliteconnection != NULL) {
    return sqliteconnection;
  }

  fix_user_directory(pud->userdb);
  (void)pthread_once(&sqlite_init_once, sqlite_init_multithreaded);
  int const rc = sqlite3_open(pud->userdb, &sqliteconnection);
  if ((sqliteconnection == NULL) || (rc != SQLITE_OK)) {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(
        TURN_LOG_LEVEL_ERROR,
        "Cannot open SQLite DB connection: <%s>, runtime error:\n  %s\n  (If your intention is to use an SQLite "
        "database for the TURN server, then\n  check and fix, if necessary, the effective permissions of the TURN "
        "server\n  process and of the DB directory and then re-start the TURN server)\n",
        pud->userdb_sanitized, errmsg);
    if (sqliteconnection != NULL) {
      sqlite3_close(sqliteconnection);
      sqliteconnection = NULL;
    }
    turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_UNKNOWN;
  } else {
    init_sqlite_database(sqliteconnection);
    if (!donot_print_connection_success) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SQLite DB connection success: %s\n", pud->userdb_sanitized);
      donot_print_connection_success = 1;
    }
  }
  if (sqliteconnection) {
    (void)pthread_setspecific(connection_key, sqliteconnection);
  }

  return sqliteconnection;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////

static int sqlite_get_auth_secrets(secrets_list_t *sl, uint8_t *realm) {
  int ret = -1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement) - 1, "select value from turn_secret where realm='%s'", realm);

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    ret = 0;
    if (sqlite3_column_count(st) > 0) {

      for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

        if ((stepResult == SQLITE_ROW) && (sqlite3_column_type(st, 0) != SQLITE_NULL)) {
          add_to_secrets_list(sl, (const char *)sqlite3_column_text(st, 0));
        } else {
          const char *errmsg = sqlite3_errmsg(sqliteconnection);
          TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
          ret = -1;
          break;
        }
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }
  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_get_user_key(uint8_t *usname, uint8_t *realm, hmackey_t key) {
  int ret = -1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  /* direct user input eliminated - there is no SQL injection problem (since version 4.4.5.3) */
  snprintf(statement, sizeof(statement), "select hmackey from turnusers_lt where name='%s' and realm='%s'", usname,
           realm);

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Error if more than one result.
    if (sqlite3_step(st) == SQLITE_ROW) {
      char const *const kval = (const char *)sqlite3_column_text(st, 0);
      size_t const sz = get_hmackey_size(SHATYPE_DEFAULT);
      convert_string_key_to_binary(kval, key, sz);
      ret = 0;
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_get_oauth_key(const uint8_t *kid, oauth_key_data_raw *key) {
  int ret = -1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  sqlite3_stmt *st = NULL;

  /* direct user input eliminated - there is no SQL injection problem (since version 4.4.5.3) */
  snprintf(statement, sizeof(statement),
           "select ikm_key,timestamp,lifetime,as_rs_alg,realm from oauth_key where kid='%s'", (const char *)kid);

  sqlite_lock(0);

  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Error if more than one result.
    if (sqlite3_step(st) == SQLITE_ROW) {
      STRCPY(key->ikm_key, sqlite3_column_text(st, 0));
      key->timestamp = (uint64_t)strtoll((const char *)sqlite3_column_text(st, 1), NULL, 10);
      key->lifetime = (uint32_t)strtol((const char *)sqlite3_column_text(st, 2), NULL, 10);
      STRCPY(key->as_rs_alg, sqlite3_column_text(st, 3));
      STRCPY(key->realm, sqlite3_column_text(st, 4));
      STRCPY(key->kid, kid);
      ret = 0;
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_list_oauth_keys(secrets_list_t *kids, secrets_list_t *teas, secrets_list_t *tss, secrets_list_t *lts,
                                  secrets_list_t *realms) {
  int ret = -1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  oauth_key_data_raw key_;
  oauth_key_data_raw *key = &key_;

  donot_print_connection_success = 1;

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement),
           "select ikm_key,timestamp,lifetime,as_rs_alg,realm,kid from oauth_key order by kid");

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        STRCPY(key->ikm_key, sqlite3_column_text(st, 0));
        key->timestamp = (uint64_t)strtoll((const char *)sqlite3_column_text(st, 1), NULL, 10);
        key->lifetime = (uint32_t)strtol((const char *)sqlite3_column_text(st, 2), NULL, 10);
        STRCPY(key->as_rs_alg, sqlite3_column_text(st, 3));
        STRCPY(key->realm, sqlite3_column_text(st, 4));
        STRCPY(key->kid, sqlite3_column_text(st, 5));

        if (kids) {
          add_to_secrets_list(kids, key->kid);
          add_to_secrets_list(teas, key->as_rs_alg);
          add_to_secrets_list(realms, key->realm);
          {
            char ts[256];
            snprintf(ts, sizeof(ts) - 1, "%llu", (unsigned long long)key->timestamp);
            add_to_secrets_list(tss, ts);
          }
          {
            char lt[256];
            snprintf(lt, sizeof(lt) - 1, "%lu", (unsigned long)key->lifetime);
            add_to_secrets_list(lts, lt);
          }
        } else {
          printf("  kid=%s, ikm_key=%s, timestamp=%llu, lifetime=%lu, as_rs_alg=%s\n", key->kid, key->ikm_key,
                 (unsigned long long)key->timestamp, (unsigned long)key->lifetime, key->as_rs_alg);
        }

      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_set_user_key(uint8_t *usname, uint8_t *realm, const char *key) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement),
           "insert or replace into turnusers_lt (realm,name,hmackey) values('%s','%s','%s')", realm, usname, key);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check the result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_set_oauth_key(oauth_key_data_raw *key) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement),
           "insert or replace into oauth_key (kid,ikm_key,timestamp,lifetime,as_rs_alg,realm) "
           "values('%s','%s',%llu,%lu,'%s','%s')",
           key->kid, key->ikm_key, (unsigned long long)key->timestamp, (unsigned long)key->lifetime, key->as_rs_alg,
           key->realm);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check the result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error updating SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_del_user(uint8_t *usname, uint8_t *realm) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "delete from turnusers_lt where name='%s' and realm='%s'", usname, realm);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check the result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_del_oauth_key(const uint8_t *kid) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "delete from oauth_key where kid = '%s'", (const char *)kid);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check the result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_list_users(uint8_t *realm, secrets_list_t *users, secrets_list_t *realms) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  if ((realm != NULL) && (realm[0] != '\0')) {
    snprintf(statement, sizeof(statement), "select name,realm from turnusers_lt where realm='%s' order by name", realm);
  } else {
    snprintf(statement, sizeof(statement), "select name,realm from turnusers_lt order by realm,name");
  }

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Validate column count...
    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *kval = (const char *)sqlite3_column_text(st, 0);
        const char *rval = (const char *)sqlite3_column_text(st, 1);

        if (users) {
          add_to_secrets_list(users, kval);

          if (realms) {
            if (rval && *rval) {
              add_to_secrets_list(realms, rval);
            } else {
              add_to_secrets_list(realms, (char *)realm);
            }
          }

        } else {
          printf("%s[%s]\n", kval, rval);
        }

      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_list_secrets(uint8_t *realm, secrets_list_t *secrets, secrets_list_t *realms) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  if ((realm != NULL) && (realm[0] != '\0')) {
    snprintf(statement, sizeof(statement), "select value,realm from turn_secret where realm='%s' order by value",
             realm);
  } else {
    snprintf(statement, sizeof(statement), "select value,realm from turn_secret order by realm,value");
  }

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Validate column count...
    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *kval = (const char *)sqlite3_column_text(st, 0);
        const char *rval = (const char *)sqlite3_column_text(st, 1);

        if (secrets) {
          add_to_secrets_list(secrets, kval);

          if (realms) {
            if (rval && *rval) {
              add_to_secrets_list(realms, rval);
            } else {
              add_to_secrets_list(realms, (char *)realm);
            }
          }

        } else {
          printf("%s[%s]\n", kval, rval);
        }
      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_del_secret(uint8_t *secret, uint8_t *realm) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];

  if (!secret || (secret[0] == 0)) {
    snprintf(statement, sizeof(statement), "delete from turn_secret where realm='%s'", realm);
  } else {
    snprintf(statement, sizeof(statement), "delete from turn_secret where value='%s' and realm='%s'", secret, realm);
  }

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check results...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_set_secret(uint8_t *secret, uint8_t *realm) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "insert or replace into turn_secret (realm,value) values('%s','%s')", realm,
           secret);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check results...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_add_origin(uint8_t *origin, uint8_t *realm) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "insert or replace into turn_origin_to_realm (origin,realm) values('%s','%s')",
           origin, realm);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check results...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_del_origin(uint8_t *origin) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "delete from turn_origin_to_realm where origin='%s'", origin);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check results...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_list_origins(uint8_t *realm, secrets_list_t *origins, secrets_list_t *realms) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  if (realm && realm[0]) {
    snprintf(statement, sizeof(statement),
             "select origin,realm from turn_origin_to_realm where realm='%s' order by origin", realm);
  } else {
    snprintf(statement, sizeof(statement), "select origin,realm from turn_origin_to_realm order by realm,origin");
  }

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Validate column count...
    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *kval = (const char *)sqlite3_column_text(st, 0);
        const char *rval = (const char *)sqlite3_column_text(st, 1);

        if (origins) {
          add_to_secrets_list(origins, kval);

          if (realms) {
            if (rval && *rval) {
              add_to_secrets_list(realms, rval);
            } else {
              add_to_secrets_list(realms, (char *)realm);
            }
          }

        } else {
          printf("%s ==>> %s\n", kval, rval);
        }
      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_set_realm_option_one(uint8_t *realm, unsigned long value, const char *opt) {
  int ret = -1;

  if (value <= 0) {
    return ret;
  }

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement),
           "insert or replace into turn_realm_option (realm,opt,value) values('%s','%s','%lu')", realm, opt, value);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check results...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_list_realm_options(uint8_t *realm) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  if (realm && realm[0]) {
    snprintf(statement, sizeof(statement),
             "select realm,opt,value from turn_realm_option where realm='%s' order by realm,opt", realm);
  } else {
    snprintf(statement, sizeof(statement), "select realm,opt,value from turn_realm_option order by realm,opt");
  }

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Validate column count...
    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *rval = (const char *)sqlite3_column_text(st, 0);
        const char *oval = (const char *)sqlite3_column_text(st, 1);
        const char *vval = (const char *)sqlite3_column_text(st, 2);

        printf("%s[%s]=%s\n", oval, rval, vval);

      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static void sqlite_auth_ping(void *rch) { UNUSED_ARG(rch); }

static int sqlite_get_ip_list(const char *kind, ip_range_list_t *list) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "select ip_range,realm from %s_peer_ip", kind);

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    // TODO: Validate column count...
    ret = 0;
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *kval = (const char *)sqlite3_column_text(st, 0);
        const char *rval = (const char *)sqlite3_column_text(st, 1);

        add_ip_list_range(kval, rval, list);

      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_set_permission_ip(const char *kind, uint8_t *realm, const char *ip, int del) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  uint8_t realm0[STUN_MAX_REALM_SIZE + 1] = "\0";
  if (!realm) {
    realm = realm0;
  }

  sqlite_lock(1);

  char statement[TURN_LONG_STRING_SIZE];
  if (del) {
    snprintf(statement, sizeof(statement), "delete from %s_peer_ip where realm = '%s'  and ip_range = '%s'", kind,
             (char *)realm, ip);
  } else {
    snprintf(statement, sizeof(statement), "insert or replace into %s_peer_ip (realm,ip_range) values('%s','%s')", kind,
             (char *)realm, ip);
  }

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error updating SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static void sqlite_reread_realms(secrets_list_t *realms_list) {
  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return;
  }

  {
    char statement[TURN_LONG_STRING_SIZE];
    snprintf(statement, sizeof(statement), "select origin,realm from turn_origin_to_realm");

    sqlite_lock(0);

    sqlite3_stmt *st = NULL;
    if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

      ur_string_map *o_to_realm_new = ur_string_map_create(free);

      // TODO: Validate column count...
      for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

        if (stepResult == SQLITE_ROW) {

          // TODO: Why does oval need to be strdup? It seems used read-only, non-owning?
          char *oval = strdup((const char *)sqlite3_column_text(st, 0));
          char *rval = strdup((const char *)sqlite3_column_text(st, 1));

          get_realm(rval);
          ur_string_map_value_type value = rval;
          ur_string_map_put(o_to_realm_new, (ur_string_map_key_type)oval, value);

          free(oval);

        } else {
          const char *errmsg = sqlite3_errmsg(sqliteconnection);
          TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
          break;
        }
      }

      update_o_to_realm(o_to_realm_new);

    } else {
      const char *errmsg = sqlite3_errmsg(sqliteconnection);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
    }

    sqlite3_finalize(st);

    sqlite_unlock(0);
  }

  {
    lock_realms();
    size_t const rlsz = realms_list->sz;
    unlock_realms();

    for (size_t i = 0; i < rlsz; ++i) {
      char *realm = realms_list->secrets[i];

      realm_params_t *rp = get_realm(realm);

      lock_realms();
      rp->options.perf_options.max_bps = turn_params.max_bps;
      rp->options.perf_options.total_quota = turn_params.total_quota;
      rp->options.perf_options.user_quota = turn_params.user_quota;
      unlock_realms();
    }
  }

  {
    sqlite_lock(0);

    char statement[TURN_LONG_STRING_SIZE];
    snprintf(statement, sizeof(statement), "select realm,opt,value from turn_realm_option");

    sqlite3_stmt *st = NULL;
    if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

      // TODO: Validate column count...
      for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

        if (stepResult == SQLITE_ROW) {

          // TODO: Why does rval need strdup? seems read-only non-owning.
          char *rval = strdup((const char *)sqlite3_column_text(st, 0));
          const char *oval = (const char *)sqlite3_column_text(st, 1);
          const char *vval = (const char *)sqlite3_column_text(st, 2);

          realm_params_t *rp = get_realm(rval);
          if (strcmp(oval, "max-bps") == 0) {
            rp->options.perf_options.max_bps = (band_limit_t)strtoul(vval, NULL, 10);
          } else if (strcmp(oval, "total-quota") == 0) {
            rp->options.perf_options.total_quota = (vint)atoi(vval);
          } else if (strcmp(oval, "user-quota") == 0) {
            rp->options.perf_options.user_quota = (vint)atoi(vval);
          } else {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown realm option: %s\n", oval);
          }

          free(rval);

        } else {
          const char *errmsg = sqlite3_errmsg(sqliteconnection);
          TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
          break;
        }
      }
    } else {
      const char *errmsg = sqlite3_errmsg(sqliteconnection);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
    }

    sqlite3_finalize(st);

    sqlite_unlock(0);
  }
}

////////////////////////////////////////////////////

static int sqlite_get_admin_user(const uint8_t *usname, uint8_t *realm, password_t pwd) {
  int ret = -1;

  if ((realm == NULL) || (usname == NULL)) {
    return ret;
  }

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  realm[0] = 0;
  pwd[0] = 0;

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "select realm,password from admin_user where name='%s'", usname);

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Validate column count...
    // TODO: Error if more than one result.
    if (sqlite3_step(st) == SQLITE_ROW) {

      const char *kval = (const char *)sqlite3_column_text(st, 0);
      const char *rval = (const char *)sqlite3_column_text(st, 1);

      if (kval) {
        strncpy((char *)realm, kval, STUN_MAX_REALM_SIZE);
      }
      if (rval) {
        strncpy((char *)pwd, rval, STUN_MAX_PWD_SIZE);
      }

      ret = 0;
    } else {
      const char *errmsg = sqlite3_errmsg(sqliteconnection);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static int sqlite_set_admin_user(const uint8_t *usname, const uint8_t *realm, const password_t pwd) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement),
           "insert or replace into admin_user (realm,name,password) values('%s','%s','%s')", realm, usname, pwd);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_del_admin_user(const uint8_t *usname) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "delete from admin_user where name='%s'", usname);

  sqlite_lock(1);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {
    // TODO: Check result...
    sqlite3_step(st);
    ret = 0;
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(1);

  return ret;
}

static int sqlite_list_admin_users(int no_print) {
  int ret = -1;

  donot_print_connection_success = 1;

  sqlite3 *const sqliteconnection = get_sqlite_connection();
  if (sqliteconnection == NULL) {
    return ret;
  }

  char statement[TURN_LONG_STRING_SIZE];
  snprintf(statement, sizeof(statement), "select name,realm from admin_user order by realm,name");

  sqlite_lock(0);

  sqlite3_stmt *st = NULL;
  if (sqlite3_prepare(sqliteconnection, statement, -1, &st, 0) == SQLITE_OK) {

    ret = 0;
    // TODO: Validate column count...
    for (int stepResult = sqlite3_step(st); stepResult != SQLITE_DONE; stepResult = sqlite3_step(st)) {

      if (stepResult == SQLITE_ROW) {

        const char *kval = (const char *)sqlite3_column_text(st, 0);
        const char *rval = (const char *)sqlite3_column_text(st, 1);

        if (!no_print) {
          if (rval && *rval) {
            printf("%s[%s]\n", kval, rval);
          } else {
            printf("%s\n", kval);
          }
        }

        ++ret; // TODO: Why increment this? No other function does.

      } else {
        const char *errmsg = sqlite3_errmsg(sqliteconnection);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
        ret = -1;
        break;
      }
    }
  } else {
    const char *errmsg = sqlite3_errmsg(sqliteconnection);
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving SQLite DB information: %s\n", errmsg);
  }

  sqlite3_finalize(st);

  sqlite_unlock(0);

  return ret;
}

static void sqlite_disconnect(void) {
  sqlite3 *const sqliteconnection = (sqlite3 *)pthread_getspecific(connection_key);
  if (sqliteconnection) {
    sqlite3_close(sqliteconnection);
  }
  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SQLite connection was closed.\n");
}

///////////////////////////////////////////////////////

static const turn_dbdriver_t driver = {
    &sqlite_get_auth_secrets,   &sqlite_get_user_key,   &sqlite_set_user_key,   &sqlite_del_user,
    &sqlite_list_users,         &sqlite_list_secrets,   &sqlite_del_secret,     &sqlite_set_secret,
    &sqlite_add_origin,         &sqlite_del_origin,     &sqlite_list_origins,   &sqlite_set_realm_option_one,
    &sqlite_list_realm_options, &sqlite_auth_ping,      &sqlite_get_ip_list,    &sqlite_set_permission_ip,
    &sqlite_reread_realms,      &sqlite_set_oauth_key,  &sqlite_get_oauth_key,  &sqlite_del_oauth_key,
    &sqlite_list_oauth_keys,    &sqlite_get_admin_user, &sqlite_set_admin_user, &sqlite_del_admin_user,
    &sqlite_list_admin_users,   &sqlite_disconnect};

//////////////////////////////////////////////////

const turn_dbdriver_t *get_sqlite_dbdriver(void) { return &driver; }

//////////////////////////////////////////////////

#else // defined(TURN_NO_SQLITE)

const turn_dbdriver_t *get_sqlite_dbdriver(void) { return NULL; }

#endif // !defined(TURN_NO_SQLITE)
