diff --git a/dnsmasq_interface.c b/dnsmasq_interface.c index 49b53d59b..fb58bff32 100644 --- a/dnsmasq_interface.c +++ b/dnsmasq_interface.c @@ -382,22 +382,23 @@ void FTL_dnsmasq_reload(void) // its own behalf (on initial reading, the config file is already opened) get_blocking_mode(NULL); - // Reread regex.list - // Free regex list and array of whitelisted domains + // Reread pihole-FTL.conf to see which debugging flags are set + read_debuging_settings(NULL); + + // Free regex list free_regex(); - free_whitelist_domains(); + + // (Re-)open gravity database connection + gravityDB_close(); + gravityDB_open(); // Start timer for regex compilation analysis timer_start(REGEX_TIMER); // Read and compile possible regex filters + // only after having called gravityDB_open() read_regex_from_database(); - // Read whitelisted domains from database - read_whitelist_from_database(); // Log result - log_regex_whitelist(timer_elapsed_msec(REGEX_TIMER)); - - // Reread pihole-FTL.conf to see which debugging flags are set - read_debuging_settings(NULL); + log_regex(timer_elapsed_msec(REGEX_TIMER)); // Print current set of capabilities if requested via debug flag if(config.debug & DEBUG_CAPS) diff --git a/gravity.c b/gravity.c index 76300329d..aa376cbf6 100644 --- a/gravity.c +++ b/gravity.c @@ -14,25 +14,28 @@ // Private variables static sqlite3 *gravitydb = NULL; static sqlite3_stmt* stmt = NULL; +static sqlite3_stmt* whitelist_stmt = NULL; +bool gravity_database_avail = false; // Prototypes from functions in dnsmasq's source void rehash(int size); // Open gravity database -static bool gravityDB_open(void) +bool gravityDB_open(void) { struct stat st; if(stat(FTLfiles.gravitydb, &st) != 0) { // File does not exist - logg("readGravity(): %s does not exist", FTLfiles.gravitydb); + logg("gravityDB_open(): %s does not exist", FTLfiles.gravitydb); return false; } int rc = sqlite3_open_v2(FTLfiles.gravitydb, &gravitydb, SQLITE_OPEN_READONLY, NULL); - if( rc ){ - logg("readGravity() - SQL error (%i): %s", rc, sqlite3_errmsg(gravitydb)); - sqlite3_close(gravitydb); + if( rc ) + { + logg("gravityDB_open() - SQL error (%i): %s", rc, sqlite3_errmsg(gravitydb)); + gravityDB_close(); return false; } @@ -40,26 +43,60 @@ static bool gravityDB_open(void) // temporary tables, indices, and views. char *zErrMsg = NULL; rc = sqlite3_exec(gravitydb, "PRAGMA temp_store = MEMORY", NULL, NULL, &zErrMsg); - if( rc != SQLITE_OK ){ - logg("readGravity(PRAGMA temp_store) - SQL error (%i): %s", rc, zErrMsg); + if( rc != SQLITE_OK ) + { + logg("gravityDB_open(PRAGMA temp_store) - SQL error (%i): %s", rc, zErrMsg); sqlite3_free(zErrMsg); - sqlite3_close(gravitydb); + gravityDB_close(); + return false; + } + + // Prepare whitelist statement + // We use SELECT EXISTS() as this is known to efficiently use the index + // We are only interested in whether the domain exists or not in the + // list but don't case about duplicates or similar. SELECT EXISTS(...) + // returns true as soon as it sees the first row from the query inside + // of EXISTS(). + rc = sqlite3_prepare_v2(gravitydb, "SELECT EXISTS(SELECT domain from vw_whitelist WHERE domain = ?);", -1, &whitelist_stmt, NULL); + if( rc ) + { + logg("gravityDB_open(\"SELECT EXISTS(...)\") - SQL error prepare (%i): %s", rc, sqlite3_errmsg(gravitydb)); + gravityDB_close(); return false; } + // Database connection is now open + gravity_database_avail = true; + if(config.debug & DEBUG_DATABASE) + logg("gravityDB_open(): Successfully opened gravity.db"); + return true; } +void gravityDB_close(void) +{ + // Return early if gravity database is not available + if(!gravity_database_avail) + return; + + // Finalize whitelist scanning statement + sqlite3_finalize(whitelist_stmt); + + // Close table + sqlite3_close(gravitydb); + gravity_database_avail = false; +} + // Prepare a SQLite3 statement which can be used by // gravityDB_getDomain() to get blocking domains from // a table which is specified when calling this function bool gravityDB_getTable(const unsigned char list) { - // Open gravity database - // Note: This might fail when the database has - // not yet been created by gravity - if(!gravityDB_open()) + if(!gravity_database_avail) + { + logg("gravityDB_getTable(%d): Gravity database not available", list); return false; + } // Select correct query string to be used depending on list to be read const char *querystr = NULL; @@ -71,9 +108,6 @@ bool gravityDB_getTable(const unsigned char list) case BLACK_LIST: querystr = "SELECT domain FROM vw_blacklist;"; break; - case WHITE_LIST: - querystr = "SELECT domain FROM vw_whitelist;"; - break; case REGEX_LIST: querystr = "SELECT domain FROM vw_regex;"; break; @@ -84,9 +118,10 @@ bool gravityDB_getTable(const unsigned char list) // Prepare SQLite3 statement int rc = sqlite3_prepare_v2(gravitydb, querystr, -1, &stmt, NULL); - if( rc ){ + if( rc ) + { logg("readGravity(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(gravitydb)); - sqlite3_close(gravitydb); + gravityDB_close(); return false; } @@ -128,14 +163,13 @@ inline const char* gravityDB_getDomain(void) } // Finalize statement of a gravity database transaction -// and close the database handle void gravityDB_finalizeTable(void) { + if(!gravity_database_avail) + return; + // Finalize statement sqlite3_finalize(stmt); - - // Close database handle - sqlite3_close(gravitydb); } // Get number of domains in a specified table of the gravity database @@ -143,6 +177,12 @@ void gravityDB_finalizeTable(void) // encounter any error int gravityDB_count(const unsigned char list) { + if(!gravity_database_avail) + { + logg("gravityDB_count(%d): Gravity database not available", list); + return DB_FAILED; + } + // Select correct query string to be used depending on list to be read const char* querystr = NULL; switch(list) @@ -164,16 +204,12 @@ int gravityDB_count(const unsigned char list) return DB_FAILED; } - // Open database handle - if(!gravityDB_open()) - return DB_FAILED; - // Prepare query int rc = sqlite3_prepare_v2(gravitydb, querystr, -1, &stmt, NULL); if( rc ){ logg("gravityDB_count(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(gravitydb)); sqlite3_finalize(stmt); - sqlite3_close(gravitydb); + gravityDB_close(); return DB_FAILED; } @@ -182,15 +218,74 @@ int gravityDB_count(const unsigned char list) if( rc != SQLITE_ROW ){ logg("gravityDB_count(%s) - SQL error step (%i): %s", querystr, rc, sqlite3_errmsg(gravitydb)); sqlite3_finalize(stmt); - sqlite3_close(gravitydb); + gravityDB_close(); return DB_FAILED; } // Get result when there was no error const int result = sqlite3_column_int(stmt, 0); - // Finalize statement and close database handle + // Finalize statement gravityDB_finalizeTable(); return result; } + +bool in_whitelist(const char *domain) +{ + int retval; + // Bind domain to prepared statement + // SQLITE_STATIC: Use the string without first duplicating it internally. + // We can do this as domain has dynamic scope that exceeds that of the binding. + if((retval = sqlite3_bind_text(whitelist_stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) + { + logg("in_whitelist(\"%s\"): Failed to bind domain (error %d) - %s", + domain, retval, sqlite3_errmsg(gravitydb)); + sqlite3_reset(whitelist_stmt); + return false; + } + + // Perform step + retval = sqlite3_step(whitelist_stmt); + if(retval == SQLITE_BUSY) + { + // Database is busy + logg("in_whitelist(\"%s\"): Database is busy, assuming domain is NOT whitelisted", + domain); + sqlite3_reset(whitelist_stmt); + sqlite3_clear_bindings(whitelist_stmt); + return false; + } + else if(retval != SQLITE_ROW) + { + // Any return code that is neither SQLITE_BUSY not SQLITE_ROW + // is a real error we should log + logg("in_whitelist(\"%s\"): Failed to perform step (error %d) - %s", + domain, retval, sqlite3_errmsg(gravitydb)); + sqlite3_reset(whitelist_stmt); + sqlite3_clear_bindings(whitelist_stmt); + return false; + } + + // Get result of query "SELECT EXISTS(...)" + const int result = sqlite3_column_int(whitelist_stmt, 0); + + if(config.debug & DEBUG_DATABASE) + logg("in_whitelist(\"%s\"): %d", domain, result); + + // The sqlite3_reset() function is called to reset a prepared + // statement object back to its initial state, ready to be + // re-executed. Note: Any SQL statement variables that had values + // bound to them using the sqlite3_bind_*() API retain their values. + sqlite3_reset(whitelist_stmt); + + // Contrary to the intuition of many, sqlite3_reset() does not reset + // the bindings on a prepared statement. Use this routine to reset + // all host parameters to NULL. + sqlite3_clear_bindings(whitelist_stmt); + + // Return result. + // SELECT EXISTS(...) either returns 0 (false) or 1 (true). + return result == 1; +} + diff --git a/main.c b/main.c index 862e3d203..e389a999e 100644 --- a/main.c +++ b/main.c @@ -90,11 +90,13 @@ int main (int argc, char* argv[]) // Free regex list and array of whitelisted domains free_regex(); - free_whitelist_domains(); // Remove shared memory objects destroy_shmem(); + // Close gravity database connection + gravityDB_close(); + //Remove PID file removepid(); logg("########## FTL terminated after %.1f ms! ##########", timer_elapsed_msec(EXIT_TIMER)); diff --git a/regex.c b/regex.c index 4ee9f5172..b3e71e20a 100644 --- a/regex.c +++ b/regex.c @@ -15,7 +15,6 @@ static int num_regex; static regex_t *regex = NULL; static bool *regexconfigured = NULL; static char **regexbuffer = NULL; -static whitelistStruct whitelist = { NULL, 0 }; static void log_regex_error(const char *where, const int errcode, const int index) { @@ -47,36 +46,6 @@ static bool init_regex(const char *regexin, const int index) return true; } -bool __attribute__((pure)) in_whitelist(const char *domain) -{ - bool found = false; - for(int i=0; i < whitelist.count; i++) - { - // strcasecmp() compares two strings ignoring case - if(strcasecmp(whitelist.domains[i], domain) == 0) - { - found = true; - break; - } - } - return found; -} - -void free_whitelist_domains(void) -{ - for(int i=0; i < whitelist.count; i++) - free(whitelist.domains[i]); - - whitelist.count = 0; - - // Free whitelist domains array only allocated - if(whitelist.domains != NULL) - { - free(whitelist.domains); - whitelist.domains = NULL; - } -} - bool match_regex(const char *input) { bool matched = false; @@ -160,51 +129,6 @@ void free_regex(void) num_regex = 0; } -void read_whitelist_from_database(void) -{ - // Get number of lines in the whitelist table - whitelist.count = gravityDB_count(WHITE_LIST); - - if(whitelist.count == 0) - { - logg("INFO: No whitelist entries found"); - return; - } - else if(whitelist.count == DB_FAILED) - { - logg("WARN: Database query failed, assuming there are no whitelist entries"); - whitelist.count = 0; - return; - } - - // Allocate memory for array of whitelisted domains - whitelist.domains = calloc(whitelist.count, sizeof(char*)); - - // Connect to whitelist table - if(!gravityDB_getTable(WHITE_LIST)) - { - logg("read_whitelist_from_database(): Error getting table from database"); - return; - } - - // Walk database table - const char *domain = NULL; - int i = 0; - while((domain = gravityDB_getDomain()) != NULL) - { - // Avoid buffer overflow if database table changed - // since we counted its entries - if(i >= whitelist.count) - break; - - // Copy this whitelisted domain into memory - whitelist.domains[i++] = strdup(domain); - } - - // Finalize statement and close gravity database handle - gravityDB_finalizeTable(); -} - void read_regex_from_database(void) { // Get number of lines in the regex table @@ -266,7 +190,7 @@ void read_regex_from_database(void) gravityDB_finalizeTable(); } -void log_regex_whitelist(const double time) +void log_regex(const double time) { - logg("Compiled %i Regex filters and %i whitelisted domains in %.1f msec", num_regex, whitelist.count, time); + logg("Compiled %i Regex filters in %.1f msec", num_regex, time); } diff --git a/request.c b/request.c index 30b1f6bf7..62907b00f 100644 --- a/request.c +++ b/request.c @@ -167,15 +167,12 @@ void process_request(const char *client_message, int *sock) // Reread regex.list // Free regex list and array of whitelisted domains free_regex(); - free_whitelist_domains(); // Start timer for regex compilation analysis timer_start(REGEX_TIMER); // Read and compile possible regex filters read_regex_from_database(); - // Read whitelisted domains from database - read_whitelist_from_database(); // Log result - log_regex_whitelist(timer_elapsed_msec(REGEX_TIMER)); + log_regex(timer_elapsed_msec(REGEX_TIMER)); unlock_shm(); } else if(command(client_message, ">update-mac-vendor")) diff --git a/routines.h b/routines.h index 44e73ae13..cecf5e03b 100644 --- a/routines.h +++ b/routines.h @@ -118,11 +118,8 @@ void resolveForwardDestinations(const bool onlynew); // regex.c bool match_regex(const char *input); void free_regex(void); -void free_whitelist_domains(void); void read_regex_from_database(void); -void read_whitelist_from_database(void); -bool in_whitelist(const char *domain) __attribute__((pure)); -void log_regex_whitelist(const double time); +void log_regex(const double time); // shmem.c bool init_shmem(void); @@ -164,8 +161,12 @@ void parse_arp_cache(void); void updateMACVendorRecords(void); // gravity.c +bool gravityDB_open(void); +void gravityDB_close(void); bool gravityDB_getTable(unsigned char list); const char* gravityDB_getDomain(void); void gravityDB_finalizeTable(void); int gravityDB_count(unsigned char list); +bool in_whitelist(const char *domain); + #endif // ROUTINES_H