Merge branch 'development-v6' into new/validator

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2024-02-10 09:27:15 +01:00
commit 9b18127867
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
29 changed files with 942 additions and 548 deletions

View File

@ -119,7 +119,7 @@ jobs:
-
name: Store binary artifacts for later deployoment
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4.2.0
uses: actions/upload-artifact@v4.3.0
with:
name: ${{ matrix.bin_name }}-binary
path: '${{ matrix.bin_name }}*'
@ -131,7 +131,7 @@ jobs:
-
name: Upload documentation artifacts for deployoment
if: github.event_name != 'pull_request' && matrix.platform == 'linux/amd64'
uses: actions/upload-artifact@v4.2.0
uses: actions/upload-artifact@v4.3.0
with:
name: pihole-api-docs
path: 'api-docs.tar.gz'

View File

@ -294,7 +294,7 @@ find_library(LIBGMP NAMES libgmp${CMAKE_STATIC_LIBRARY_SUFFIX} gmp)
find_library(LIBNETTLE NAMES libnettle${CMAKE_STATIC_LIBRARY_SUFFIX} nettle HINTS /usr/local/lib64)
# for IDN2 we need the idn2 library which in turn depends on the unistring library
find_library(LIBIDN2 NAMES libidn2${CMAKE_STATIC_LIBRARY_SUFFIX} idn)
find_library(LIBIDN2 NAMES libidn2${CMAKE_STATIC_LIBRARY_SUFFIX} idn2)
find_library(LIBUNISTRING NAMES libunistring${CMAKE_STATIC_LIBRARY_SUFFIX} unistring)
target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN2} ${LIBUNISTRING})

View File

@ -76,7 +76,7 @@ components:
properties:
expires:
type: integer
description: Expiration time
description: Expiration time (0 = infinite lease, never expires)
example: 1675671991
name:
type: string
@ -112,4 +112,4 @@ components:
type: string
required: true
description: IP address of lease to be modified
example: 192.168.2.222
example: 192.168.2.222

View File

@ -216,9 +216,9 @@ components:
time:
type: number
description: Time until the response was received (ms, negative if N/A)
regex_id:
list_id:
type: integer
description: ID of regex (`NULL` if N/A)
description: ID of corresponding database table (adlist for anti-/gravity, else domainlist) (`NULL` if N/A)
nullable: true
upstream:
type: string
@ -237,7 +237,7 @@ components:
reply:
type: "IP"
time: 19
regex_id: NULL
list_id: NULL
upstream: "localhost#5353"
dbid: 112421354
- time: 1581907871.583821
@ -252,7 +252,7 @@ components:
reply:
type: "IP"
time: 12.3
regex_id: NULL
list_id: NULL
upstream: "localhost#5353"
dbid: 112421355
cursor:

View File

@ -173,7 +173,7 @@ int api_queries_suggestions(struct ftl_conn *api)
JSON_SEND_OBJECT(json);
}
#define QUERYSTR "SELECT q.id,timestamp,q.type,status,d.domain,f.forward,additional_info,reply_type,reply_time,dnssec,c.ip,c.name,a.content,regex_id"
#define QUERYSTR "SELECT q.id,timestamp,q.type,status,d.domain,f.forward,additional_info,reply_type,reply_time,dnssec,c.ip,c.name,a.content,list_id"
// JOIN: Only return rows where there is a match in BOTH tables
// LEFT JOIN: Return all rows from the left table, and the matched rows from the right table
#define JOINSTR "JOIN client_by_id c ON q.client = c.id JOIN domain_by_id d ON q.domain = d.id LEFT JOIN forward_by_id f ON q.forward = f.id LEFT JOIN addinfo_by_id a ON a.id = q.additional_info"
@ -214,8 +214,8 @@ static void querystr_finish(char *querystr, const char *sort_col, const char *so
sort_col_sql = "q.reply_time";
else if(strcasecmp(sort_col, "dnssec") == 0)
sort_col_sql = "q.dnssec";
else if(strcasecmp(sort_col, "regex_id") == 0)
sort_col_sql = "regex_id";
else if(strcasecmp(sort_col, "list_id") == 0)
sort_col_sql = "list_id";
// ... and the sort direction
if(strcasecmp(sort_dir, "asc") == 0 || strcasecmp(sort_dir, "ascending") == 0)
@ -887,11 +887,11 @@ int api_queries(struct ftl_conn *api)
JSON_ADD_NULL_TO_OBJECT(client, "name");
JSON_ADD_ITEM_TO_OBJECT(item, "client", client);
// Add regex_id if it exists
// Add list_id if it exists
if(sqlite3_column_type(read_stmt, 13) == SQLITE_INTEGER)
JSON_ADD_NUMBER_TO_OBJECT(item, "regex_id", sqlite3_column_int(read_stmt, 13)); // regex_id
JSON_ADD_NUMBER_TO_OBJECT(item, "list_id", sqlite3_column_int(read_stmt, 13)); // list_id
else
JSON_ADD_NULL_TO_OBJECT(item, "regex_id");
JSON_ADD_NULL_TO_OBJECT(item, "list_id");
const unsigned char *cname = NULL;
switch(query.status)

View File

@ -64,6 +64,8 @@
#include <idn2.h>
// sha256sum()
#include "files.h"
// resolveHostname()
#include "resolve.h"
// defined in dnsmasq.c
extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal);
@ -496,6 +498,25 @@ void parse_args(int argc, char* argv[])
exit(EXIT_SUCCESS);
}
// Local reverse name resolver
if(argc == 3 && strcasecmp(argv[1], "ptr") == 0)
{
// Enable stdout printing
cli_mode = true;
// Need to get dns.port and the resolver settings
readFTLconf(&config, false);
char *name = resolveHostname(argv[2], true);
if(name == NULL)
exit(EXIT_FAILURE);
// Print result
printf("%s\n", name);
free(name);
exit(EXIT_SUCCESS);
}
// start from 1, as argv[0] is the executable name
for(int i = 1; i < argc; i++)
{
@ -980,6 +1001,7 @@ void parse_args(int argc, char* argv[])
printf(" Decoding: %spihole-FTL idn2 -d %spunycode%s\n\n", green, cyan, normal);
printf("%sOther:%s\n", yellow, normal);
printf("\t%sptr %sIP%s Resolve IP address to hostname\n", green, cyan, normal);
printf("\t%ssha256sum %sfile%s Calculate SHA256 checksum of a file\n", green, cyan, normal);
printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal);
printf("\t network\n");

View File

@ -438,8 +438,8 @@ void initConfig(struct config *conf)
struct enum_options piholePTR[] =
{
{ get_ptr_type_str(PTR_NONE), "Pi-hole will not respond automatically on PTR requests to local interface addresses. Ensure pi.hole and/or hostname records exist elsewhere." },
{ get_ptr_type_str(PTR_HOSTNAME), "Pi-hole will not respond automatically on PTR requests to local interface addresses. Ensure pi.hole and/or hostname records exist elsewhere." },
{ get_ptr_type_str(PTR_HOSTNAMEFQDN), "Serve the machine's global hostname as fully qualified domain by adding the local suffix. If no local suffix has been defined, FTL appends the local domain .no_fqdn_available. In this case you should either add domain=whatever.com to a custom config file inside /etc/dnsmasq.d/ (to set whatever.com as local domain) or use domain=# which will try to derive the local domain from /etc/resolv.conf (or whatever is set with resolv-file, when multiple search directives exist, the first one is used)." },
{ get_ptr_type_str(PTR_HOSTNAME), "Serve the machine's hostname. The hostname is queried from the kernel through uname(2)->nodename. If the machine has multiple network interfaces, it can also have multiple nodenames. In this case, it is unspecified and up to the kernel which one will be returned. On Linux, the returned string is what has been set using sethostname(2) which is typically what has been set in /etc/hostname." },
{ get_ptr_type_str(PTR_HOSTNAMEFQDN), "Serve the machine's hostname (see limitations above) as fully qualified domain by adding the local domain. If no local domain has been defined (config option dns.domain), FTL tries to query the domain name from the kernel using getdomainname(2). If this fails, FTL appends \".no_fqdn_available\" to the hostname." },
{ get_ptr_type_str(PTR_PIHOLE), "Respond with \"pi.hole\"." }
};
CONFIG_ADD_ENUM_OPTIONS(conf->dns.piholePTR.a, piholePTR);
@ -514,8 +514,8 @@ void initConfig(struct config *conf)
conf->dns.dnssec.h = "Validate DNS replies using DNSSEC?";
conf->dns.dnssec.t = CONF_BOOL;
conf->dns.dnssec.f = FLAG_RESTART_FTL;
conf->dns.dnssec.d.b = true;
conf->dns.dnssec.c = validate_stub; // Only type-based checking
conf->dns.dnssec.d.b = false;
conf->dns.interface.k = "dns.interface";
conf->dns.interface.h = "Interface to use for DNS (see also dnsmasq.listening.mode) and DHCP (if enabled)";

View File

@ -178,20 +178,39 @@ char *getUserName(void)
// hyphen.
#define HOSTNAMESIZE 256
static char nodename[HOSTNAMESIZE] = { 0 };
static char dname[HOSTNAMESIZE] = { 0 };
// Returns the hostname of the system
const char *hostname(void)
{
// Ask kernel for node name if not known
// This is equivalent to "uname -n"
//
// According to man gethostname(2), this is exactly the same as calling
// getdomainname() just with one step less
if(nodename[0] == '\0')
{
struct utsname buf;
if(uname(&buf) == 0)
{
strncpy(nodename, buf.nodename, HOSTNAMESIZE);
nodename[HOSTNAMESIZE-1] = '\0';
strncpy(dname, buf.domainname, HOSTNAMESIZE);
}
nodename[HOSTNAMESIZE - 1] = '\0';
dname[HOSTNAMESIZE - 1] = '\0';
}
return nodename;
}
// Returns the domain name of the system
const char *domainname(void)
{
if(dname[0] == '\0')
hostname();
return dname;
}
void delay_startup(void)
{
// Exit early if not sleeping
@ -463,4 +482,4 @@ void init_locale(void)
// the dot as decimal separator (even if the system locale uses a
// comma, e.g., in German)
setlocale(LC_NUMERIC, "C");
}
}

View File

@ -17,6 +17,7 @@ void go_daemon(void);
void savepid(void);
char *getUserName(void);
const char *hostname(void);
const char *domainname(void);
void delay_startup(void);
bool is_fork(const pid_t mpid, const pid_t pid) __attribute__ ((const));
void cleanup(const int ret);

View File

@ -549,6 +549,26 @@ void db_init(void)
dbversion = db_get_int(db, DB_VERSION);
}
// Update to version 17 if lower
if(dbversion < 17)
{
// Update to version 17: Rename regex_id column to regex_id_old
log_info("Updating long-term database to version 17");
if(!rename_query_storage_column_regex_id(db))
{
log_info("regex_id cannot be renamed to list_id, database not available");
dbclose(&db);
return;
}
// Get updated version
dbversion = db_get_int(db, DB_VERSION);
}
// Last check after all migrations, if this happens, it will cause the
// CI to fail the tests
if(dbversion != MEMDB_VERSION)
log_err("Database version %i does not match MEMDB_VERSION %i", dbversion, MEMDB_VERSION);
lock_shm();
import_aliasclients(db);
unlock_shm();

View File

@ -857,7 +857,7 @@ bool gravityDB_prepare_client_statements(clientsData *client)
// Prepare gravity statement
log_debug(DEBUG_DATABASE, "gravityDB_open(): Preparing vw_gravity statement for client %s", clientip);
querystr = get_client_querystr("vw_gravity", "domain", getstr(client->groupspos));
querystr = get_client_querystr("vw_gravity", "adlist_id", getstr(client->groupspos));
rc = sqlite3_prepare_v3(gravity_db, querystr, -1, SQLITE_PREPARE_PERSISTENT, &stmt, NULL);
if( rc != SQLITE_OK )
{
@ -870,7 +870,7 @@ bool gravityDB_prepare_client_statements(clientsData *client)
// Prepare antigravity statement
log_debug(DEBUG_DATABASE, "gravityDB_open(): Preparing vw_antigravity statement for client %s", clientip);
querystr = get_client_querystr("vw_antigravity", "domain", getstr(client->groupspos));
querystr = get_client_querystr("vw_antigravity", "adlist_id", getstr(client->groupspos));
rc = sqlite3_prepare_v3(gravity_db, querystr, -1, SQLITE_PREPARE_PERSISTENT, &stmt, NULL);
if( rc != SQLITE_OK )
{
@ -1244,6 +1244,11 @@ enum db_result in_allowlist(const char *domain, DNSCacheData *dns_cache, clients
// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(client);
// Check again as the client may have been reloaded if this is a TCP
// worker
if(whitelist_stmt == NULL)
return LIST_NOT_AVAILABLE;
// Get whitelist statement from vector of prepared statements if available
sqlite3_stmt *stmt = whitelist_stmt->get(whitelist_stmt, client->id);
@ -1262,7 +1267,7 @@ enum db_result in_allowlist(const char *domain, DNSCacheData *dns_cache, clients
// We have to check both the exact whitelist (using a prepared database statement)
// as well the compiled regex whitelist filters to check if the current domain is
// whitelisted.
return domain_in_list(domain, stmt, "whitelist", &dns_cache->domainlist_id);
return domain_in_list(domain, stmt, "whitelist", &dns_cache->list_id);
}
cJSON *gen_abp_patterns(const char *domain, const bool antigravity)
@ -1377,6 +1382,11 @@ enum db_result in_gravity(const char *domain, clientsData *client, const bool an
// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(client);
// Check again as the client may have been reloaded if this is a TCP
// worker
if(gravity_stmt == NULL || antigravity_stmt == NULL)
return LIST_NOT_AVAILABLE;
// Get whitelist statement from vector of prepared statements
sqlite3_stmt *stmt = antigravity ?
antigravity_stmt->get(antigravity_stmt, client->id) :
@ -1451,6 +1461,11 @@ enum db_result in_denylist(const char *domain, DNSCacheData *dns_cache, clientsD
// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(client);
// Check again as the client may have been reloaded if this is a TCP
// worker
if(blacklist_stmt == NULL)
return LIST_NOT_AVAILABLE;
// Get whitelist statement from vector of prepared statements
sqlite3_stmt *stmt = blacklist_stmt->get(blacklist_stmt, client->id);
@ -1466,7 +1481,7 @@ enum db_result in_denylist(const char *domain, DNSCacheData *dns_cache, clientsD
if(stmt == NULL)
stmt = blacklist_stmt->get(blacklist_stmt, client->id);
return domain_in_list(domain, stmt, "blacklist", &dns_cache->domainlist_id);
return domain_in_list(domain, stmt, "blacklist", &dns_cache->list_id);
}
bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int numregex, const regexData *regex,
@ -1969,8 +1984,8 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
else if(listtype == GRAVITY_ADLISTS)
{
// This is actually a three-step deletion to satisfy foreign-key constraints
querystrs[0] = "DELETE FROM gravity WHERE adlist_id = (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[1] = "DELETE FROM antigravity WHERE adlist_id = (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[0] = "DELETE FROM gravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[1] = "DELETE FROM antigravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[2] = "DELETE FROM adlist WHERE address IN (SELECT item FROM deltable);";
}
else if(listtype == GRAVITY_CLIENTS)
@ -1983,7 +1998,6 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
querystrs[3] = "DELETE FROM domainlist WHERE domain IN (SELECT item FROM deltable WHERE type = 3) AND type = 3;";
}
bool okay = true;
for(unsigned int i = 0; i < ArraySize(querystrs); i++)
{
// Finish if no more queries
@ -1997,13 +2011,12 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
*message = sqlite3_errmsg(gravity_db);
log_err("gravityDB_delFromTable(%d): SQL error exec(\"%s\"): %s",
listtype, querystrs[i], *message);
okay = false;
// Rollback transaction
querystr = "ROLLBACK TRANSACTION;";
sqlite3_exec(gravity_db, querystr, NULL, NULL, NULL);
break;
return false;
}
// Add number of deleted rows
@ -2018,7 +2031,6 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
*message = sqlite3_errmsg(gravity_db);
log_err("gravityDB_delFromTable(%d): SQL error exec(\"%s\"): %s",
listtype, querystr, *message);
okay = false;
// Rollback transaction
querystr = "ROLLBACK TRANSACTION;";
@ -2033,14 +2045,13 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
*message = sqlite3_errmsg(gravity_db);
log_err("gravityDB_delFromTable(%d): SQL error exec(\"%s\"): %s",
listtype, querystr, *message);
okay = false;
// Rollback transaction
querystr = "ROLLBACK TRANSACTION;";
sqlite3_exec(gravity_db, querystr, NULL, NULL, NULL);
}
return okay;
return true;
}
static sqlite3_stmt* read_stmt = NULL;

View File

@ -19,7 +19,7 @@
#include "../datastructure.h"
// struct config
#include "../config/config.h"
// resolveHostname()
// resolve_this_name()
#include "../resolve.h"
// killed
#include "../signals.h"

View File

@ -770,6 +770,29 @@ bool add_ftl_table_description(sqlite3 *db)
return true;
}
bool rename_query_storage_column_regex_id(sqlite3 *db)
{
// Start transaction of database update
SQL_bool(db, "BEGIN TRANSACTION");
// Rename column regex_id to list_id
SQL_bool(db, "ALTER TABLE query_storage RENAME COLUMN regex_id TO list_id;");
// The VIEW queries is automatically updated by SQLite3
// Update database version to 17
if(!db_set_FTL_property(db, DB_VERSION, 17))
{
log_err("rename_query_storage_column_regex_id(): Failed to update database version!");
return false;
}
// Finish transaction
SQL_bool(db, "COMMIT");
return true;
}
bool optimize_queries_table(sqlite3 *db)
{
// Start transaction of database update
@ -1118,7 +1141,7 @@ void DB_read_queries(void)
// a) we have a cache entry
// b) the value of additional_info is not NULL (0 bytes storage size)
if(cache != NULL && sqlite3_column_bytes(stmt, 7) != 0)
cache->domainlist_id = sqlite3_column_int(stmt, 7);
cache->list_id = sqlite3_column_int(stmt, 7);
}
// Increment status counters
@ -1467,15 +1490,15 @@ bool queries_to_database(void)
break;
}
}
else if(cache != NULL && query->status == QUERY_REGEX)
else if(cache != NULL && cache->list_id != -1)
{
// Restore regex ID if applicable
sqlite3_bind_int(query_stmt, 9, ADDINFO_REGEX_ID);
sqlite3_bind_int(query_stmt, 10, cache->domainlist_id);
sqlite3_bind_int(query_stmt, 9, ADDINFO_LIST_ID);
sqlite3_bind_int(query_stmt, 10, cache->list_id);
// Execute prepared addinfo statement and check if successful
sqlite3_bind_int(addinfo_stmt, 1, ADDINFO_REGEX_ID);
sqlite3_bind_int(addinfo_stmt, 2, cache->domainlist_id);
sqlite3_bind_int(addinfo_stmt, 1, ADDINFO_LIST_ID);
sqlite3_bind_int(addinfo_stmt, 2, cache->list_id);
rc = sqlite3_step(addinfo_stmt);
sqlite3_clear_bindings(addinfo_stmt);
sqlite3_reset(addinfo_stmt);
@ -1506,9 +1529,9 @@ bool queries_to_database(void)
// DNSSEC
sqlite3_bind_int(query_stmt, 13, query->dnssec);
// REGEX_ID
if(cache != NULL && cache->domainlist_id > -1)
sqlite3_bind_int(query_stmt, 14, cache->domainlist_id);
// LIST_ID
if(cache != NULL && cache->list_id != -1)
sqlite3_bind_int(query_stmt, 14, cache->list_id);
else
// Not applicable, setting NULL
sqlite3_bind_null(query_stmt, 14);

View File

@ -23,20 +23,21 @@
"client TEXT NOT NULL, " \
"forward TEXT );"
#define CREATE_QUERY_STORAGE_TABLE_V13 "CREATE TABLE query_storage ( id INTEGER PRIMARY KEY AUTOINCREMENT, " \
"timestamp INTEGER NOT NULL, " \
"type INTEGER NOT NULL, " \
"status INTEGER NOT NULL, " \
"domain INTEGER NOT NULL, " \
"client INTEGER NOT NULL, " \
"forward INTEGER, " \
"additional_info INTEGER, " \
"reply_type INTEGER, " \
"reply_time REAL, " \
"dnssec INTEGER, " \
"regex_id INTEGER );"
#define MEMDB_VERSION 17
#define CREATE_QUERY_STORAGE_TABLE "CREATE TABLE query_storage ( id INTEGER PRIMARY KEY AUTOINCREMENT, " \
"timestamp INTEGER NOT NULL, " \
"type INTEGER NOT NULL, " \
"status INTEGER NOT NULL, " \
"domain INTEGER NOT NULL, " \
"client INTEGER NOT NULL, " \
"forward INTEGER, " \
"additional_info INTEGER, " \
"reply_type INTEGER, " \
"reply_time REAL, " \
"dnssec INTEGER, " \
"list_id INTEGER );"
#define CREATE_QUERIES_VIEW_V13 "CREATE VIEW queries AS " \
#define CREATE_QUERIES_VIEW "CREATE VIEW queries AS " \
"SELECT id, timestamp, type, status, " \
"CASE typeof(domain) " \
"WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain," \
@ -46,7 +47,7 @@
"WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward," \
"CASE typeof(additional_info) "\
"WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, " \
"reply_type, reply_time, dnssec, regex_id FROM query_storage q"
"reply_type, reply_time, dnssec, list_id FROM query_storage q"
// Version 1
#define CREATE_QUERIES_TIMESTAMP_INDEX "CREATE INDEX idx_queries_timestamp ON queries (timestamp);"
@ -62,8 +63,7 @@
#define CREATE_QUERY_STORAGE_REPLY_TYPE_INDEX "CREATE INDEX idx_query_storage_reply_type ON query_storage (reply_type);"
#define CREATE_QUERY_STORAGE_REPLY_TIME_INDEX "CREATE INDEX idx_query_storage_reply_time ON query_storage (reply_time);"
#define CREATE_QUERY_STORAGE_DNSSEC_INDEX "CREATE INDEX idx_query_storage_dnssec ON query_storage (dnssec);"
//#define CREATE_QUERY_STORAGE_TTL_INDEX "CREATE INDEX idx_query_storage_ttl ON query_storage (ttl);"
//#define CREATE_QUERY_STORAGE_REGEX_ID_INDEX "CREATE INDEX idx_query_storage_regex_id ON query_storage (regex_id);"
#define CREATE_QUERY_STORAGE_LIST_ID_INDEX "CREATE INDEX idx_query_storage_list_id ON query_storage (list_id);"
#define CREATE_DOMAINS_BY_ID "CREATE TABLE domain_by_id (id INTEGER PRIMARY KEY, domain TEXT NOT NULL);"
#define CREATE_CLIENTS_BY_ID "CREATE TABLE client_by_id (id INTEGER PRIMARY KEY, ip TEXT NOT NULL, name TEXT);"
@ -77,12 +77,12 @@
#ifdef QUERY_TABLE_PRIVATE
const char *table_creation[] = {
CREATE_QUERY_STORAGE_TABLE_V13,
CREATE_QUERY_STORAGE_TABLE,
CREATE_DOMAINS_BY_ID,
CREATE_CLIENTS_BY_ID,
CREATE_FORWARD_BY_ID,
CREATE_ADDINFO_BY_ID,
CREATE_QUERIES_VIEW_V13,
CREATE_QUERIES_VIEW,
};
const char *index_creation[] = {
CREATE_QUERY_STORAGE_ID_INDEX,
@ -96,8 +96,7 @@ const char *index_creation[] = {
CREATE_QUERY_STORAGE_REPLY_TYPE_INDEX,
CREATE_QUERY_STORAGE_REPLY_TIME_INDEX,
CREATE_QUERY_STORAGE_DNSSEC_INDEX,
// CREATE_QUERY_STORAGE_TTL_INDEX,
// CREATE_QUERY_STORAGE_REGEX_ID_INDEX
CREATE_QUERY_STORAGE_LIST_ID_INDEX
CREATE_DOMAIN_BY_ID_DOMAIN_INDEX,
CREATE_CLIENTS_BY_ID_IPNAME_INDEX,
CREATE_FORWARD_BY_ID_FORWARD_INDEX,
@ -126,5 +125,6 @@ bool create_addinfo_table(sqlite3 *db);
bool add_query_storage_columns(sqlite3 *db);
bool add_query_storage_column_regex_id(sqlite3 *db);
bool add_ftl_table_description(sqlite3 *db);
bool rename_query_storage_column_regex_id(sqlite3 *db);
#endif //QUERY_TABLE_PRIVATE_H

View File

@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.45.0. By combining all the individual C code files into this
** version 3.45.1. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
** 1066602b2b1976fe58b5150777cced894af1.
** e876e51a0ed5c5b3126f52e532044363a014.
*/
#define SQLITE_CORE 1
#define SQLITE_AMALGAMATION 1
@ -459,9 +459,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.0"
#define SQLITE_VERSION_NUMBER 3045000
#define SQLITE_SOURCE_ID "2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d"
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -43408,11 +43408,16 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
#if SQLITE_MAX_MMAP_SIZE>0
if( pFd->mmapSizeMax>0 ){
/* Ensure that there is always at least a 256 byte buffer of addressable
** memory following the returned page. If the database is corrupt,
** SQLite may overread the page slightly (in practice only a few bytes,
** but 256 is safe, round, number). */
const int nEofBuffer = 256;
if( pFd->pMapRegion==0 ){
int rc = unixMapfile(pFd, -1);
if( rc!=SQLITE_OK ) return rc;
}
if( pFd->mmapSize >= iOff+nAmt ){
if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){
*pp = &((u8 *)pFd->pMapRegion)[iOff];
pFd->nFetchOut++;
}
@ -50765,6 +50770,11 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
#if SQLITE_MAX_MMAP_SIZE>0
if( pFd->mmapSizeMax>0 ){
/* Ensure that there is always at least a 256 byte buffer of addressable
** memory following the returned page. If the database is corrupt,
** SQLite may overread the page slightly (in practice only a few bytes,
** but 256 is safe, round, number). */
const int nEofBuffer = 256;
if( pFd->pMapRegion==0 ){
int rc = winMapfile(pFd, -1);
if( rc!=SQLITE_OK ){
@ -50773,7 +50783,7 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
return rc;
}
}
if( pFd->mmapSize >= iOff+nAmt ){
if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){
assert( pFd->pMapRegion!=0 );
*pp = &((u8 *)pFd->pMapRegion)[iOff];
pFd->nFetchOut++;
@ -76402,7 +76412,10 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){
}
pPage = pCur->pPage;
assert( pPage->isInit );
if( sqlite3FaultSim(412) ) pPage->isInit = 0;
if( !pPage->isInit ){
return SQLITE_CORRUPT_BKPT;
}
if( !pPage->leaf ){
int idx = pCur->ix;
rc = moveToChild(pCur, get4byte(findCell(pPage, idx)));
@ -166812,7 +166825,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
/* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */
testcase( pOrderBy && pOrderBy->nExpr==BMS-1 );
if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0;
if( pOrderBy && pOrderBy->nExpr>=BMS ){
pOrderBy = 0;
wctrlFlags &= ~WHERE_WANT_DISTINCT;
}
/* The number of tables in the FROM clause is limited by the number of
** bits in a Bitmask
@ -184747,6 +184763,8 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int);
SQLITE_PRIVATE int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*);
SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk);
#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */
#endif /* _FTSINT_H */
@ -188469,7 +188487,7 @@ static int fts3ShadowName(const char *zName){
** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
** table.
*/
static int fts3Integrity(
static int fts3IntegrityMethod(
sqlite3_vtab *pVtab, /* The virtual table to be checked */
const char *zSchema, /* Name of schema in which pVtab lives */
const char *zTabname, /* Name of the pVTab table */
@ -188477,30 +188495,21 @@ static int fts3Integrity(
char **pzErr /* Write error message here */
){
Fts3Table *p = (Fts3Table*)pVtab;
char *zSql;
int rc;
char *zErr = 0;
int bOk = 0;
assert( pzErr!=0 );
assert( *pzErr==0 );
UNUSED_PARAMETER(isQuick);
zSql = sqlite3_mprintf(
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
zSchema, zTabname, zTabname);
if( zSql==0 ){
return SQLITE_NOMEM;
}
rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr);
sqlite3_free(zSql);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
p->bFts4 ? 4 : 3, zSchema, zTabname);
}else if( rc!=SQLITE_OK ){
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
assert( rc!=SQLITE_CORRUPT_VTAB || bOk==0 );
if( rc!=SQLITE_OK && rc!=SQLITE_CORRUPT_VTAB ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS%d table %s.%s: %s",
p->bFts4 ? 4 : 3, zSchema, zTabname, zErr);
p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc));
}else if( bOk==0 ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
p->bFts4 ? 4 : 3, zSchema, zTabname);
}
sqlite3_free(zErr);
sqlite3Fts3SegmentsClose(p);
return SQLITE_OK;
}
@ -188531,7 +188540,7 @@ static const sqlite3_module fts3Module = {
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
/* xShadowName */ fts3ShadowName,
/* xIntegrity */ fts3Integrity,
/* xIntegrity */ fts3IntegrityMethod,
};
/*
@ -200085,7 +200094,7 @@ static u64 fts3ChecksumIndex(
** If an error occurs (e.g. an OOM or IO error), return an SQLite error
** code. The final value of *pbOk is undefined in this case.
*/
static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
int rc = SQLITE_OK; /* Return code */
u64 cksum1 = 0; /* Checksum based on FTS index contents */
u64 cksum2 = 0; /* Checksum based on %_content contents */
@ -200163,7 +200172,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
sqlite3_finalize(pStmt);
}
*pbOk = (cksum1==cksum2);
*pbOk = (rc==SQLITE_OK && cksum1==cksum2);
return rc;
}
@ -200203,7 +200212,7 @@ static int fts3DoIntegrityCheck(
){
int rc;
int bOk = 0;
rc = fts3IntegrityCheck(p, &bOk);
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB;
return rc;
}
@ -203756,6 +203765,16 @@ static void jsonAppendChar(JsonString *p, char c){
}
}
/* Remove a single character from the end of the string
*/
static void jsonStringTrimOneChar(JsonString *p){
if( p->eErr==0 ){
assert( p->nUsed>0 );
p->nUsed--;
}
}
/* Make sure there is a zero terminator on p->zBuf[]
**
** Return true on success. Return false if an OOM prevents this
@ -203763,7 +203782,7 @@ static void jsonAppendChar(JsonString *p, char c){
*/
static int jsonStringTerminate(JsonString *p){
jsonAppendChar(p, 0);
p->nUsed--;
jsonStringTrimOneChar(p);
return p->eErr==0;
}
@ -205229,8 +205248,8 @@ static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){
(pParse->aBlob[i+7]<<8) + pParse->aBlob[i+8];
n = 9;
}
if( i+sz+n > pParse->nBlob
&& i+sz+n > pParse->nBlob-pParse->delta
if( (i64)i+sz+n > pParse->nBlob
&& (i64)i+sz+n > pParse->nBlob-pParse->delta
){
sz = 0;
n = 0;
@ -205280,6 +205299,7 @@ static u32 jsonTranslateBlobToText(
}
case JSONB_INT:
case JSONB_FLOAT: {
if( sz==0 ) goto malformed_jsonb;
jsonAppendRaw(pOut, (const char*)&pParse->aBlob[i+n], sz);
break;
}
@ -205288,6 +205308,7 @@ static u32 jsonTranslateBlobToText(
sqlite3_uint64 u = 0;
const char *zIn = (const char*)&pParse->aBlob[i+n];
int bOverflow = 0;
if( sz==0 ) goto malformed_jsonb;
if( zIn[0]=='-' ){
jsonAppendChar(pOut, '-');
k++;
@ -205310,6 +205331,7 @@ static u32 jsonTranslateBlobToText(
case JSONB_FLOAT5: { /* Float literal missing digits beside "." */
u32 k = 0;
const char *zIn = (const char*)&pParse->aBlob[i+n];
if( sz==0 ) goto malformed_jsonb;
if( zIn[0]=='-' ){
jsonAppendChar(pOut, '-');
k++;
@ -205423,11 +205445,12 @@ static u32 jsonTranslateBlobToText(
jsonAppendChar(pOut, '[');
j = i+n;
iEnd = j+sz;
while( j<iEnd ){
while( j<iEnd && pOut->eErr==0 ){
j = jsonTranslateBlobToText(pParse, j, pOut);
jsonAppendChar(pOut, ',');
}
if( sz>0 ) pOut->nUsed--;
if( j>iEnd ) pOut->eErr |= JSTRING_MALFORMED;
if( sz>0 ) jsonStringTrimOneChar(pOut);
jsonAppendChar(pOut, ']');
break;
}
@ -205436,17 +205459,18 @@ static u32 jsonTranslateBlobToText(
jsonAppendChar(pOut, '{');
j = i+n;
iEnd = j+sz;
while( j<iEnd ){
while( j<iEnd && pOut->eErr==0 ){
j = jsonTranslateBlobToText(pParse, j, pOut);
jsonAppendChar(pOut, (x++ & 1) ? ',' : ':');
}
if( x & 1 ) pOut->eErr |= JSTRING_MALFORMED;
if( sz>0 ) pOut->nUsed--;
if( (x & 1)!=0 || j>iEnd ) pOut->eErr |= JSTRING_MALFORMED;
if( sz>0 ) jsonStringTrimOneChar(pOut);
jsonAppendChar(pOut, '}');
break;
}
default: {
malformed_jsonb:
pOut->eErr |= JSTRING_MALFORMED;
break;
}
@ -206373,6 +206397,38 @@ jsonInsertIntoBlob_patherror:
return;
}
/*
** If pArg is a blob that seems like a JSONB blob, then initialize
** p to point to that JSONB and return TRUE. If pArg does not seem like
** a JSONB blob, then return FALSE;
**
** This routine is only called if it is already known that pArg is a
** blob. The only open question is whether or not the blob appears
** to be a JSONB blob.
*/
static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){
u32 n, sz = 0;
p->aBlob = (u8*)sqlite3_value_blob(pArg);
p->nBlob = (u32)sqlite3_value_bytes(pArg);
if( p->nBlob==0 ){
p->aBlob = 0;
return 0;
}
if( NEVER(p->aBlob==0) ){
return 0;
}
if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT
&& (n = jsonbPayloadSize(p, 0, &sz))>0
&& sz+n==p->nBlob
&& ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0)
){
return 1;
}
p->aBlob = 0;
p->nBlob = 0;
return 0;
}
/*
** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob,
** from the SQL function argument pArg. Return a pointer to the new
@ -206429,29 +206485,24 @@ rebuild_from_cache:
return p;
}
if( eType==SQLITE_BLOB ){
u32 n, sz = 0;
p->aBlob = (u8*)sqlite3_value_blob(pArg);
p->nBlob = (u32)sqlite3_value_bytes(pArg);
if( p->nBlob==0 ){
goto json_pfa_malformed;
if( jsonArgIsJsonb(pArg,p) ){
if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){
goto json_pfa_oom;
}
return p;
}
if( NEVER(p->aBlob==0) ){
goto json_pfa_oom;
}
if( (p->aBlob[0] & 0x0f)>JSONB_OBJECT ){
goto json_pfa_malformed;
}
n = jsonbPayloadSize(p, 0, &sz);
if( n==0
|| sz+n!=p->nBlob
|| ((p->aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0)
){
goto json_pfa_malformed;
}
if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){
goto json_pfa_oom;
}
return p;
/* If the blob is not valid JSONB, fall through into trying to cast
** the blob into text which is then interpreted as JSON. (tag-20240123-a)
**
** This goes against all historical documentation about how the SQLite
** JSON functions were suppose to work. From the beginning, blob was
** reserved for expansion and a blob value should have raised an error.
** But it did not, due to a bug. And many applications came to depend
** upon this buggy behavior, espeically when using the CLI and reading
** JSON text using readfile(), which returns a blob. For this reason
** we will continue to support the bug moving forward.
** See for example https://sqlite.org/forum/forumpost/012136abd5292b8d
*/
}
p->zJson = (char*)sqlite3_value_text(pArg);
p->nJson = sqlite3_value_bytes(pArg);
@ -207427,12 +207478,12 @@ static void jsonValidFunc(
return;
}
case SQLITE_BLOB: {
if( (flags & 0x0c)!=0 && jsonFuncArgMightBeBinary(argv[0]) ){
if( jsonFuncArgMightBeBinary(argv[0]) ){
if( flags & 0x04 ){
/* Superficial checking only - accomplished by the
** jsonFuncArgMightBeBinary() call above. */
res = 1;
}else{
}else if( flags & 0x08 ){
/* Strict checking. Check by translating BLOB->TEXT->BLOB. If
** no errors occur, call that a "strict check". */
JsonParse px;
@ -207443,8 +207494,11 @@ static void jsonValidFunc(
iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1);
res = iErr==0;
}
break;
}
break;
/* Fall through into interpreting the input as text. See note
** above at tag-20240123-a. */
/* no break */ deliberate_fall_through
}
default: {
JsonParse px;
@ -207569,7 +207623,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){
if( isFinal ){
if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf);
}else{
pStr->nUsed--;
jsonStringTrimOneChar(pStr);
}
return;
}else if( isFinal ){
@ -207579,7 +207633,7 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){
pStr->bStatic = 1;
}else{
sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
pStr->nUsed--;
jsonStringTrimOneChar(pStr);
}
}else{
sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC);
@ -207689,7 +207743,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
if( isFinal ){
if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf);
}else{
pStr->nUsed--;
jsonStringTrimOneChar(pStr);
}
return;
}else if( isFinal ){
@ -207699,7 +207753,7 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
pStr->bStatic = 1;
}else{
sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
pStr->nUsed--;
jsonStringTrimOneChar(pStr);
}
}else{
sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC);
@ -208180,13 +208234,9 @@ static int jsonEachFilter(
memset(&p->sParse, 0, sizeof(p->sParse));
p->sParse.nJPRef = 1;
p->sParse.db = p->db;
if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
if( jsonFuncArgMightBeBinary(argv[0]) ){
p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
}else{
goto json_each_malformed_input;
}
if( jsonFuncArgMightBeBinary(argv[0]) ){
p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
}else{
p->sParse.zJson = (char*)sqlite3_value_text(argv[0]);
p->sParse.nJson = sqlite3_value_bytes(argv[0]);
@ -250495,7 +250545,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a", -1, SQLITE_TRANSIENT);
}
/*
@ -250526,27 +250576,21 @@ static int fts5IntegrityMethod(
char **pzErr /* Write error message here */
){
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
Fts5Config *pConfig = pTab->p.pConfig;
char *zSql;
char *zErr = 0;
int rc;
assert( pzErr!=0 && *pzErr==0 );
UNUSED_PARAM(isQuick);
zSql = sqlite3_mprintf(
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
zSchema, zTabname, pConfig->zName);
if( zSql==0 ) return SQLITE_NOMEM;
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr);
sqlite3_free(zSql);
rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
zSchema, zTabname);
}else if( rc!=SQLITE_OK ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS5 table %s.%s: %s",
zSchema, zTabname, zErr);
zSchema, zTabname, sqlite3_errstr(rc));
}
sqlite3_free(zErr);
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
return SQLITE_OK;
}

View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.0"
#define SQLITE_VERSION_NUMBER 3045000
#define SQLITE_SOURCE_ID "2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d"
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
/*
** CAPI3REF: Run-Time Library Version Numbers

View File

@ -436,7 +436,7 @@ int _findCacheID(const int domainID, const int clientID, const enum query_type q
dns_cache->clientID = clientID;
dns_cache->query_type = query_type;
dns_cache->force_reply = 0u;
dns_cache->domainlist_id = -1; // -1 = not set
dns_cache->list_id = -1; // -1 = not set
// Increase counter by one
counters->dns_cache_size++;
@ -568,7 +568,7 @@ void FTL_reset_per_client_domain_data(void)
// Reset blocking status
dns_cache->blocking_status = UNKNOWN_BLOCKED;
// Reset domainlist ID
dns_cache->domainlist_id = -1;
dns_cache->list_id = -1;
}
}

View File

@ -113,7 +113,7 @@ typedef struct {
enum query_type query_type;
int domainID;
int clientID;
int domainlist_id;
int list_id;
char *cname_target;
} DNSCacheData;

View File

@ -1143,16 +1143,25 @@ static bool check_domain_blocked(const char *domain, const int clientID,
}
// Check domain against antigravity
int domain_id = -1;
const enum db_result antigravity = in_gravity(domain, client, true, &domain_id);
int list_id = -1;
const enum db_result antigravity = in_gravity(domain, client, true, &list_id);
if(antigravity == FOUND)
{
log_debug(DEBUG_QUERIES, "Allowing query due to antigravity match (ID %i)", domain_id);
log_debug(DEBUG_QUERIES, "Allowing query due to antigravity match (list ID %i)", list_id);
// Store ID of the matching antigravity list
// positive values (incl. 0) are used for domainlists
// -1 means "not set"
// -2 is gravity list 0
// -3 is gravity list 1
// ...
dns_cache->list_id = -1 * (list_id + 2);
return false;
}
// Check domains against gravity domains
const enum db_result gravity = in_gravity(domain, client, false, &domain_id);
const enum db_result gravity = in_gravity(domain, client, false, &list_id);
if(gravity == FOUND)
{
// Set new status
@ -1162,6 +1171,12 @@ static bool check_domain_blocked(const char *domain, const int clientID,
// Mark domain as gravity blocked for this client
set_dnscache_blockingstatus(dns_cache, client, GRAVITY_BLOCKED, domain);
log_debug(DEBUG_QUERIES, "Blocking query due to gravity match (list ID %i)", list_id);
// Store ID of the matching gravity list
// see remarks above for the list_id values
dns_cache->list_id = -1 * (list_id + 2);
// We block this domain
return true;
}
@ -1222,7 +1237,7 @@ static bool check_domain_blocked(const char *domain, const int clientID,
cname_target = dns_cache->cname_target;
// Store ID of this regex (fork-private)
last_regex_idx = dns_cache->domainlist_id;
last_regex_idx = dns_cache->list_id;
// We block this domain
return true;
@ -1362,14 +1377,14 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c
// the lengthy tests below
blockingreason = "regex denied";
log_debug(DEBUG_QUERIES, "%s is known as %s (cache regex ID: %i)",
domainstr, blockingreason, dns_cache->domainlist_id);
domainstr, blockingreason, dns_cache->list_id);
// Do not block if the entire query is to be permitted as something
// along the CNAME path hit the whitelist
if(!query->flags.allowed)
{
force_next_DNS_reply = dns_cache->force_reply;
last_regex_idx = dns_cache->domainlist_id;
last_regex_idx = dns_cache->list_id;
query_blocked(query, domain, client, QUERY_REGEX);
return true;
}
@ -1481,7 +1496,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c
if(config.debug.queries.v.b)
{
log_debug(DEBUG_QUERIES, "Blocking %s as %s is %s (domainlist ID: %i)",
domainstr, blockedDomain, blockingreason, dns_cache->domainlist_id);
domainstr, blockedDomain, blockingreason, dns_cache->list_id);
if(force_next_DNS_reply != 0)
log_debug(DEBUG_QUERIES, "Forcing next reply to %s", get_query_reply_str(force_next_DNS_reply));
}
@ -1496,7 +1511,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c
// Debug output
// client is guaranteed to be non-NULL above
log_debug(DEBUG_QUERIES, "DNS cache: %s/%s is %s (domainlist ID: %i)", getstr(client->ippos),
domainstr, query->flags.allowed ? "whitelisted" : "not blocked", dns_cache->domainlist_id);
domainstr, query->flags.allowed ? "whitelisted" : "not blocked", dns_cache->list_id);
}
free(domainstr);
@ -1606,8 +1621,8 @@ bool _FTL_CNAME(const char *dst, const char *src, const int id, const char* file
// Propagate ID of responsible regex up from the child to the parent
// domain (but only if set)
if(parent_cache != NULL && child_cache != NULL && child_cache->domainlist_id != -1)
parent_cache->domainlist_id = child_cache->domainlist_id;
if(parent_cache != NULL && child_cache != NULL && child_cache->list_id != -1)
parent_cache->list_id = child_cache->list_id;
// Set status
query_set_status(query, QUERY_REGEX_CNAME);
@ -2967,10 +2982,16 @@ static char *get_ptrname(struct in_addr *addr)
suffix = get_domain(*addr);
else
suffix = daemon->domain_suffix;
// If local suffix is not available, we try to obtain the domain from
// the kernel similar to how we do it for the hostname
if(!suffix)
suffix = (char*)domainname();
// If local suffix is not available, we substitute "no_fqdn_available"
// see the comment about PIHOLE_PTR=HOSTNAMEFQDN in the Pi-hole docs
// for further details on why this was chosen
if(!suffix)
if(!suffix || suffix[0] == '\0')
suffix = (char*)"no_fqdn_available";
// Get enough space for domain building

View File

@ -283,7 +283,7 @@ enum ptr_type {
enum addinfo_type {
ADDINFO_CNAME_DOMAIN = 1,
ADDINFO_REGEX_ID
ADDINFO_LIST_ID
} __attribute__ ((packed));
enum listening_mode {

View File

@ -548,7 +548,7 @@ bool in_regex(const char *domain, DNSCacheData *dns_cache, const int clientID, c
if(regex_id != -1)
{
// We found a match
dns_cache->domainlist_id = regex_id;
dns_cache->list_id = regex_id;
return true;
}

View File

@ -31,8 +31,69 @@
#include "events.h"
// resolve_regex_cnames()
#include "regex_r.h"
// statis_assert()
#include <assert.h>
static bool res_initialized = false;
// Function Prototypes
static void name_toDNS(unsigned char *dns, const size_t dnslen, const char *host, const size_t hostlen) __attribute__((nonnull(1,3)));
static unsigned char *name_fromDNS(unsigned char *reader, unsigned char *buffer, uint16_t *count) __attribute__((malloc)) __attribute__((nonnull(1,2,3)));
// Avoid "error: packed attribute causes inefficient alignment for ..." on ARM32
// builds due to the use of __attribute__((packed)) in the following structs
// Their correct size is ensured for each by static_assert() below
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wattributes\"")
// DNS header structure
struct DNS_HEADER
{
uint16_t id; // identification number
bool rd :1; // recursion desired
bool tc :1; // truncated message
bool aa :1; // authoritative answer
uint8_t opcode :4; // purpose of message
bool qr :1; // query/response flag
uint8_t rcode :4; // response code
bool cd :1; // checking disabled
bool ad :1; // authenticated data
bool z :1; // its z! reserved
bool ra :1; // recursion available
uint16_t q_count; // number of question entries
uint16_t ans_count; // number of answer entries
uint16_t auth_count; // number of authority entries
uint16_t add_count; // number of resource entries
} __attribute__((packed));
static_assert(sizeof(struct DNS_HEADER) == 12);
// Constant sized fields of query structure
struct QUESTION
{
uint16_t qtype;
uint16_t qclass;
};
static_assert(sizeof(struct QUESTION) == 4);
// Constant sized fields of the resource record structure
struct R_DATA
{
uint16_t type;
uint16_t class;
uint32_t ttl; // RFC 1035 defines the TTL field as "positive values of a signed 32bit number"
uint16_t data_len;
} __attribute__((packed));
static_assert(sizeof(struct R_DATA) == 10);
_Pragma("GCC diagnostic pop")
// Pointers to resource record contents
struct RES_RECORD
{
unsigned char *name;
struct R_DATA *resource;
uint8_t *rdata;
};
// Validate given hostname
static bool valid_hostname(char* name, const char* clientip)
@ -75,56 +136,6 @@ static bool valid_hostname(char* name, const char* clientip)
return true;
}
#define NUM_NS4 (sizeof(_res.nsaddr_list) / sizeof(_res.nsaddr_list[0]))
#define NUM_NS6 (sizeof(_res._u._ext.nsaddrs) / sizeof(_res._u._ext.nsaddrs[0]))
static void print_used_resolvers(const char *message)
{
// Print details only when debugging
if(!(config.debug.resolver.v.b))
return;
log_debug(DEBUG_RESOLVER, "%s", message);
for(unsigned int i = 0u; i < NUM_NS4 + NUM_NS6; i++)
{
int family;
in_port_t port;
void *addr = NULL;
if(i < NUM_NS4)
{
// Regular name servers (IPv4)
const unsigned int j = i;
// Some of the entries may not be configured
if(_res.nsaddr_list[j].sin_family != AF_INET)
continue;
// IPv4 name servers
addr = &_res.nsaddr_list[j].sin_addr;
port = ntohs(_res.nsaddr_list[j].sin_port);
family = _res.nsaddr_list[j].sin_family;
}
else
{
// Extension name servers (IPv6)
const unsigned int j = i - NUM_NS4;
// Some of the entries may not be configured
if(_res._u._ext.nsaddrs[j] == NULL ||
_res._u._ext.nsaddrs[j]->sin6_family != AF_INET6)
continue;
addr = &_res._u._ext.nsaddrs[j]->sin6_addr;
port = ntohs(_res._u._ext.nsaddrs[j]->sin6_port);
family = _res._u._ext.nsaddrs[j]->sin6_family;
}
// Convert nameserver information to human-readable form
char nsname[INET6_ADDRSTRLEN];
inet_ntop(family, addr, nsname, INET6_ADDRSTRLEN);
log_debug(DEBUG_RESOLVER, " %s %u: %s:%hu (IPv%u)", i < MAXNS ? " " : "EXT",
i, nsname, port, family == AF_INET ? 4u : 6u);
}
}
// Return if we want to resolve address to names at all
// (may be disabled due to config settings)
bool __attribute__((pure)) resolve_names(void)
@ -143,13 +154,257 @@ bool __attribute__((pure)) resolve_this_name(const char *ipaddr)
return true;
}
char *resolveHostname(const char *addr)
// Perform a name lookup by sending a packet to ourselves
static char *__attribute__((malloc)) ngethostbyname(const char *host, const char *ipaddr)
{
uint8_t buf[1024] = { 0 };
uint8_t *qname = NULL, *reader = NULL;
struct RES_RECORD answers[20] = { 0 }; // buffer for DNS replies
struct DNS_HEADER *dns = NULL;
struct QUESTION *qinfo = NULL;
// Set the DNS structure to standard queries
dns = (struct DNS_HEADER *)&buf;
dns->id = (unsigned short) htons(random()); // random query ID
dns->qr = 0; // This is a query
dns->opcode = 0; // This is a standard query
dns->aa = 0; // Not Authoritative
dns->tc = 0; // This message is not truncated
dns->rd = 1; // Recursion Desired
dns->ra = 0; // Recursion not available!
dns->z = 0; // Reserved
dns->ad = 0; // This is not an authenticated answer
dns->cd = 0; // Checking Disabled
dns->rcode = 0; // Response code
dns->q_count = htons(1); // 1 question
dns->ans_count = 0; // No answers
dns->auth_count = 0; // No authority
dns->add_count = 0; // No additional
// Point to the query portion
qname = &buf[sizeof(struct DNS_HEADER)];
// Make a copy of the hostname with two extra bytes for the length and
// the final dot, copy the hostname into it and convert to convert to
// DNS format
const size_t hnamelen = strlen(host) + 2;
char *hname = calloc(hnamelen, sizeof(char));
if(hname == NULL)
{
log_err("Unable to allocate memory for hname");
return strdup("");
}
strncpy(hname, host, hnamelen);
strncat(hname, ".", hnamelen - strlen(hname));
hname[hnamelen - 1] = '\0';
name_toDNS(qname, sizeof(buf) - sizeof(struct DNS_HEADER), hname, hnamelen);
free(hname);
qinfo = (void*)&buf[sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1)];
qinfo->qtype = htons(T_PTR); // Type of the query, A, MX, CNAME, NS etc
qinfo->qclass = htons(1); // IN
// UDP packet for DNS queries
const int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in dest = { 0 };
dest.sin_family = AF_INET; // IPv4
dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
dest.sin_port = htons(config.dns.port.v.u16); // Configured DNS port
const size_t questionlen = sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1) + sizeof(struct QUESTION);
if(sendto(s, buf, questionlen, 0, (struct sockaddr*)&dest, sizeof(dest)) < 0)
{
perror("sendto failed");
close(s);
return strdup("");
}
// Receive the answer
socklen_t addrlen = sizeof(dest);
if(recvfrom (s, buf, sizeof(buf), 0, (struct sockaddr*)&dest, &addrlen) < 0)
{
perror("recvfrom failed");
close(s);
return strdup("");
}
// Close socket
close(s);
// Parse the reply
dns = (struct DNS_HEADER*) buf;
// Move ahead of the dns header and the query field
reader = &buf[questionlen];
// Start reading answers
uint16_t stop = 0;
char *name = NULL;
for(uint16_t i = 0; i < ntohs(dns->ans_count); i++)
{
answers[i].name = name_fromDNS(reader, buf, &stop);
reader = reader + stop;
answers[i].resource = (struct R_DATA*)(reader);
reader = reader + sizeof(struct R_DATA);
// We only care about PTR answers and ignore all others
if(ntohs(answers[i].resource->type) != T_PTR)
continue;
// Read the answer and convert from network to host representation
answers[i].rdata = name_fromDNS(reader, buf, &stop);
reader = reader + stop;
name = (char *)answers[i].rdata;
log_debug(DEBUG_RESOLVER, "Resolving %s (PTR \"%s\"): %u = \"%s\"",
ipaddr, answers[i].name, i, answers[i].rdata);
// We break out of the loop if this is a valid hostname
if(valid_hostname(name, ipaddr))
{
break;
}
else
{
// Discard this answer: free memory and set name to NULL
free(name);
name = NULL;
}
}
if(name != NULL)
{
// We have a valid hostname, return it
// This is allocated memory
return name;
}
else
{
// No valid hostname found, return empty string
return strdup("");
}
}
// Convert hostname from network to host representation
// This routine supports DNS compression pointers
// 3www6google3com -> www.google.com
static u_char * __attribute__((malloc)) __attribute__((nonnull(1,2,3))) name_fromDNS(unsigned char *reader, unsigned char *buffer, uint16_t *count)
{
unsigned char *name = calloc(MAXHOSTNAMELEN, sizeof(char));
unsigned int p = 0, jumped = 0;
// Initialize count
*count = 1;
// Parse DNS label string encoding (e.g, 3www6google3com)
//
// In its text format, a domain name is a sequence of dot-separated
// "labels". The dot separators are not used in binary DNS messages.
// Instead, each label is preceded by a byte containing its length, and
// the name is terminated by a zero-length label representing the root
// zone.
while(*reader != 0)
{
if(*reader >= 0xC0)
{
// RFC 1035, Section 4.1.4: Message compression
//
// A label can be up to 63 bytes long; if the length
// byte is 64 (0x40) or greater, it has a special
// meaning. Values between 0x40 and 0xBF have no purpose
// except to cause painful memories for those involved
// in DNS extensions in the late 1990s.
//
// However, if the length byte is 0xC0 or greater, the
// length byte and the next byte form a "compression
// pointer". A DNS name compression pointer allows DNS
// messages to reuse parent domains. The lower 14 bits
// are an offset into the DNS message where the
// remaining suffix of the name previously occurred.
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | 1 1| OFFSET |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// The first two bits are ones. This allows a pointer
// to be distinguished from a label, since the label
// must begin with two zero bits because labels are
// restricted to 63 octets or less. See the referenced
// RFC for more details.
const unsigned int offset = ((*reader - 0xC0) << 8) + *(reader + 1);
reader = buffer + offset - 1;
jumped = 1; // We have jumped to another location so counting won't go up
}
else
// Copy character to name
name[p++] = *reader;
// Increment read pointer
reader = reader + 1;
if(jumped == 0)
*count = *count + 1; // If we haven't jumped to another location then we can count up
}
// Terminate string
name[p] = '\0';
// Number of steps we actually moved forward in the packet
if(jumped == 1)
*count += 1;
// Now convert 3www6google3com0 to www.google.com
unsigned int i = 0;
for(; i < strlen((const char*)name); i++)
{
p = name[i];
for(unsigned j = 0; j < p; j++)
{
name[i] = name[i + 1];
i = i + 1;
}
name[i] = '.';
}
// Strip off the trailing dot
name[i-1] = '\0';
return name;
}
// Convert hostname from host to network representation
// www.google.com -> 3www6google3com
// We do not use DNS compression pointers here as we do not know if the DNS
// server we are talking to supports them
static void __attribute__((nonnull(1,3))) name_toDNS(unsigned char *dns, const size_t dnslen, const char *host, const size_t hostlen)
{
unsigned int lock = 0;
// Iterate over hostname characters
for(unsigned int i = 0 ; i < strlen((char*)host); i++)
{
// If we encounter a dot, write the number of characters since the last dot
// and then write the characters themselves
if(host[i] == '.')
{
*dns++ = i - lock;
for(;lock < i; lock++)
*dns++ = host[lock];
lock++;
}
}
// Terminate the string at the end
*dns++='\0';
}
char *__attribute__((malloc)) resolveHostname(const char *addr, const bool force)
{
// Get host name
char *hostn = NULL;
// Check if we want to resolve host names
if(!resolve_this_name(addr))
if(!force && !resolve_this_name(addr))
{
log_debug(DEBUG_RESOLVER, "Configured to not resolve host name for %s", addr);
@ -177,15 +432,6 @@ char *resolveHostname(const char *addr)
return hostn;
}
// Check if we want to resolve host names
if(!resolve_this_name(addr))
{
log_debug(DEBUG_RESOLVER, "Configured to not resolve host name for %s", addr);
// Return an empty host name
return strdup("");
}
// Test if we want to resolve an IPv6 address
bool IPv6 = false;
if(strstr(addr,":") != NULL)
@ -193,6 +439,7 @@ char *resolveHostname(const char *addr)
// Convert address into binary form
struct sockaddr_storage ss = { 0 };
char *inaddr = NULL;
if(IPv6)
{
// Get binary form of IPv6 address
@ -202,6 +449,47 @@ char *resolveHostname(const char *addr)
log_warn("Invalid IPv6 address when trying to resolve hostname: %s", addr);
return strdup("");
}
// Need extra space for ".ip6.arpa" suffix
// The 1.2.3.4... string is 63 + terminating \0 = 64 bytes long
inaddr = calloc(64 + 10, sizeof(char));
if(inaddr == NULL)
{
log_err("Unable to allocate memory for reverse lookup");
return strdup("");
}
// Convert IPv6 address to reverse lookup format
// b a 9 8 7 6 5 4 |<-- :: -->| 0 1 2 3 4
// 4321:0::4:567:89ab -> b.a.9.8.7.6.5.0.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.2.3.4
for(int i = 0; i < 32; i++)
{
// Get current nibble
uint8_t nibble = ((uint8_t *)&(((struct sockaddr_in6 *)&ss)->sin6_addr))[i/2];
// Get lower nibble for even i, upper nibble for odd i
if(i % 2 == 0)
nibble = nibble >> 4;
// Mask out upper nibble
nibble = nibble & 0x0F;
// Convert to ASCII
char c = '0' + nibble;
if(c > '9')
c = 'a' + nibble - 10;
// Prepend to string
inaddr[62-2*i] = c;
// Add dot after (actually: before) every nibble except
// the last one
if(i != 31)
inaddr[62-2*i-1] = '.';
}
// Add suffix
strcat(inaddr, ".ip6.arpa");
}
else
{
@ -212,148 +500,28 @@ char *resolveHostname(const char *addr)
log_warn("Invalid IPv4 address when trying to resolve hostname: %s", addr);
return strdup("");
}
}
// Initialize resolver subroutines if trying to resolve for the first time
// res_init() reads resolv.conf to get the default domain name and name server
// address(es). If no server is given, the local host is tried. If no domain
// is given, that associated with the local host is used.
if(!res_initialized)
{
res_init();
res_initialized = true;
}
// INADDR_LOOPBACK is in host byte order, however, in_addr has to be in
// network byte order, convert it here if necessary
struct in_addr FTLaddr = { htonl(INADDR_LOOPBACK) };
in_port_t FTLport = htons(config.dns.port.v.u16);
// Temporarily set FTL as system resolver
// Backup configured name servers and invalidate them (IPv4)
struct in_addr ns_addr_bck[MAXNS];
in_port_t ns_port_bck[MAXNS];
int bck_nscount = _res.nscount;
for(unsigned int i = 0u; i < NUM_NS4; i++)
{
ns_addr_bck[i] = _res.nsaddr_list[i].sin_addr;
ns_port_bck[i] = _res.nsaddr_list[i].sin_port;
_res.nsaddr_list[i].sin_addr.s_addr = 0; // 0.0.0.0
}
// Set FTL at 127.0.0.1 as the only resolver
_res.nsaddr_list[0].sin_addr.s_addr = FTLaddr.s_addr;
// Set resolver port
_res.nsaddr_list[0].sin_port = FTLport;
// Configure resolver to use only one resolver
_res.nscount = 1;
// Backup configured name server and invalidate them (IPv6)
struct in6_addr ns6_addr_bck[MAXNS];
in_port_t ns6_port_bck[MAXNS];
int bck_nscount6 = _res._u._ext.nscount6;
for(unsigned int i = 0u; i < NUM_NS6; i++)
{
if(_res._u._ext.nsaddrs[i] == NULL)
continue;
memcpy(&ns6_addr_bck[i], &_res._u._ext.nsaddrs[i]->sin6_addr, sizeof(struct in6_addr));
ns6_port_bck[i] = _res._u._ext.nsaddrs[i]->sin6_port;
memcpy(&_res._u._ext.nsaddrs[i]->sin6_addr, &in6addr_any, sizeof(struct in6_addr));
}
// Set FTL as the only resolver only when IPv6 is enabled
if(bck_nscount6 > 0)
{
// Set FTL at ::1 as the only resolver
memcpy(&_res._u._ext.nsaddrs[0]->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr));
// Set resolver port
_res._u._ext.nsaddrs[0]->sin6_port = FTLport;
// Configure resolver to use only one resolver
_res._u._ext.nscount6 = 1;
}
if(config.debug.resolver.v.b)
print_used_resolvers("Set nameservers to:");
// Try to resolve address
char host[NI_MAXHOST] = { 0 };
int ret = getnameinfo((struct sockaddr*)&ss, sizeof(ss), host, sizeof(host), NULL, 0, NI_NAMEREQD);
// Check if getnameinfo() returned a host name
if(ret == 0)
{
if(valid_hostname(host, addr))
// Need extra space for ".in-addr.arpa" suffix
inaddr = calloc(INET_ADDRSTRLEN + 14, sizeof(char));
if(inaddr == NULL)
{
// Return hostname copied to new memory location
hostn = strdup(host);
}
else
{
hostn = strdup("[invalid host name]");
log_err("Unable to allocate memory for reverse lookup");
return strdup("");
}
log_debug(DEBUG_RESOLVER, " ---> \"%s\" (found internally)", hostn);
}
else
log_debug(DEBUG_RESOLVER, " ---> \"\" (not found internally: %s", gai_strerror(ret));
// Restore IPv4 resolvers
for(unsigned int i = 0u; i < NUM_NS4; i++)
{
_res.nsaddr_list[i].sin_addr = ns_addr_bck[i];
_res.nsaddr_list[i].sin_port = ns_port_bck[i];
}
_res.nscount = bck_nscount;
// Restore IPv6 resolvers
for(unsigned int i = 0u; i < NUM_NS6; i++)
{
if(_res._u._ext.nsaddrs[i] == NULL)
continue;
memcpy(&_res._u._ext.nsaddrs[i]->sin6_addr, &ns6_addr_bck[i], sizeof(struct in6_addr));
_res._u._ext.nsaddrs[i]->sin6_port = ns6_port_bck[i];
}
_res._u._ext.nscount6 = bck_nscount6;
if(config.debug.resolver.v.b)
print_used_resolvers("Restored nameservers to:");
// If no host name was found before, try again with system-configured
// resolvers (necessary for docker and friends)
if(hostn == NULL)
{
// Try to resolve address
ret = getnameinfo((struct sockaddr*)&ss, sizeof(ss), host, sizeof(host), NULL, 0, NI_NAMEREQD);
// Check if getnameinfo() returned a host name this time
// First check for he not being NULL before trying to dereference it
if(ret == 0)
{
if(valid_hostname(host, addr))
{
// Return hostname copied to new memory location
hostn = strdup(host);
}
else
{
hostn = strdup("[invalid host name]");
}
log_debug(DEBUG_RESOLVER, " ---> \"%s\" (found externally)", hostn);
}
else
{
// No hostname found (empty PTR)
hostn = strdup("");
if(config.debug.resolver.v.b)
log_debug(DEBUG_RESOLVER, " ---> \"\" (not found externally: %s)", gai_strerror(ret));
}
// Convert IPv4 address to reverse lookup format
// 12.34.56.78 -> 78.56.34.12.in-addr.arpa
snprintf(inaddr, INET_ADDRSTRLEN + 14, "%d.%d.%d.%d.in-addr.arpa",
(int)((uint8_t *)&(((struct sockaddr_in *)&ss)->sin_addr))[3],
(int)((uint8_t *)&(((struct sockaddr_in *)&ss)->sin_addr))[2],
(int)((uint8_t *)&(((struct sockaddr_in *)&ss)->sin_addr))[1],
(int)((uint8_t *)&(((struct sockaddr_in *)&ss)->sin_addr))[0]);
}
// Return result
return hostn;
// Get host name by making a reverse lookup to ourselves (server at 127.0.0.1 with port 53)
// We implement a minimalistic resolver here as we cannot rely on the system resolver using whatever
// nameserver we configured in /etc/resolv.conf
return ngethostbyname(inaddr, addr);
}
// Resolve upstream destination host names
@ -382,7 +550,7 @@ static size_t resolveAndAddHostname(size_t ippos, size_t oldnamepos)
// Important: Don't hold a lock while resolving as the main thread
// (dnsmasq) needs to be operable during the call to resolveHostname()
char *newname = resolveHostname(ipaddr);
char *newname = resolveHostname(ipaddr, false);
// If no hostname was found, try to obtain hostname from the network table
// This may be disabled due to a user setting

View File

@ -11,7 +11,7 @@
#define RESOLVE_H
void *DNSclient_thread(void *val);
char *resolveHostname(const char *addr);
char *resolveHostname(const char *addr, const bool force) __attribute__((malloc));
bool resolve_names(void) __attribute__((pure));
bool resolve_this_name(const char *ipaddr) __attribute__((pure));

View File

@ -78,21 +78,81 @@ static int generate_private_key_ec(mbedtls_pk_context *key,
return 0;
}
// Write a key and/or certificate to a file
static bool write_to_file(const char *filename, const char *type, const char *suffix, const char *cert, const char *key)
{
// Create file with CA certificate only
char *targetname = calloc(strlen(filename) + (suffix != NULL ? strlen(suffix) : 0) + 1, sizeof(char));
strcpy(targetname, filename);
if(suffix != NULL)
{
// If the certificate file name ends with ".pem", replace it
// with the specified suffix. Otherwise, append the specified
// suffix to the certificate file name
if (strlen(targetname) > 4 && strcmp(targetname + strlen(targetname) - 4, ".pem") == 0)
targetname[strlen(filename) - 4] = '\0';
strcat(targetname, suffix);
}
printf("Storing %s in %s ...\n", type, targetname);
FILE *f = NULL;
if ((f = fopen(targetname, "wb")) == NULL)
{
printf("ERROR: Could not open %s for writing\n", targetname);
return false;
}
// Write key (if provided)
if(key != NULL)
{
const size_t olen = strlen((char *) key);
if (fwrite(key, 1, olen, f) != olen)
{
printf("ERROR: Could not write key to %s\n", targetname);
fclose(f);
return false;
}
}
// Write certificate (if provided)
if(cert != NULL)
{
const size_t olen = strlen((char *) cert);
if (fwrite(cert, 1, olen, f) != olen)
{
printf("ERROR: Could not write certificate to %s\n", targetname);
fclose(f);
return false;
}
}
// Close cert file
fclose(f);
free(targetname);
return true;
}
bool generate_certificate(const char* certfile, bool rsa, const char *domain)
{
int ret;
mbedtls_x509write_cert crt;
mbedtls_pk_context key;
mbedtls_x509write_cert ca_cert, server_cert;
mbedtls_pk_context ca_key, server_key;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
const char *pers = "pihole-FTL";
unsigned char ca_buffer[BUFFER_SIZE];
unsigned char cert_buffer[BUFFER_SIZE];
unsigned char key_buffer[BUFFER_SIZE];
size_t olen = 0;
unsigned char ca_key_buffer[BUFFER_SIZE];
// Initialize structures
mbedtls_x509write_crt_init(&crt);
mbedtls_pk_init(&key);
mbedtls_x509write_crt_init(&ca_cert);
mbedtls_x509write_crt_init(&server_cert);
mbedtls_pk_init(&ca_key);
mbedtls_pk_init(&server_key);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
@ -110,7 +170,12 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
{
// Generate RSA key
printf("Generating RSA key...\n");
if((ret = generate_private_key_rsa(&key, &ctr_drbg, key_buffer)) != 0)
if((ret = generate_private_key_rsa(&ca_key, &ctr_drbg, ca_key_buffer)) != 0)
{
printf("ERROR: generate_private_key returned %d\n", ret);
return false;
}
if((ret = generate_private_key_rsa(&server_key, &ctr_drbg, key_buffer)) != 0)
{
printf("ERROR: generate_private_key returned %d\n", ret);
return false;
@ -120,7 +185,12 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
{
// Generate EC key
printf("Generating EC key...\n");
if((ret = generate_private_key_ec(&key, &ctr_drbg, key_buffer)) != 0)
if((ret = generate_private_key_ec(&ca_key, &ctr_drbg, ca_key_buffer)) != 0)
{
printf("ERROR: generate_private_key_ec returned %d\n", ret);
return false;
}
if((ret = generate_private_key_ec(&server_key, &ctr_drbg, key_buffer)) != 0)
{
printf("ERROR: generate_private_key_ec returned %d\n", ret);
return false;
@ -139,48 +209,73 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
// only one certificate being issued with a given browser. Any new generated
// certificate would be rejected by the browser as it would have the same
// serial number as the previous one and uniques is violated.
unsigned char serial[16] = { 0 };
mbedtls_ctr_drbg_random(&ctr_drbg, serial, sizeof(serial));
for(unsigned int i = 0; i < sizeof(serial) - 1; i++)
serial[i] = '0' + (serial[i] % 10);
serial[sizeof(serial) - 1] = '\0';
unsigned char serial1[16] = { 0 }, serial2[16] = { 0 };
mbedtls_ctr_drbg_random(&ctr_drbg, serial1, sizeof(serial1));
for(unsigned int i = 0; i < sizeof(serial1) - 1; i++)
serial1[i] = '0' + (serial1[i] % 10);
serial1[sizeof(serial1) - 1] = '\0';
mbedtls_ctr_drbg_random(&ctr_drbg, serial2, sizeof(serial2));
for(unsigned int i = 0; i < sizeof(serial2) - 1; i++)
serial2[i] = '0' + (serial2[i] % 10);
serial2[sizeof(serial2) - 1] = '\0';
// Create validity period
// Use YYYYMMDDHHMMSS as required by RFC 5280
// Use YYYYMMDDHHMMSS as required by RFC 5280 (UTCTime)
const time_t now = time(NULL);
struct tm tms = { 0 };
struct tm *tm = localtime_r(&now, &tms);
struct tm *tm = gmtime_r(&now, &tms);
char not_before[16] = { 0 };
char not_after[16] = { 0 };
strftime(not_before, sizeof(not_before), "%Y%m%d%H%M%S", tm);
tm->tm_year += 30; // 30 years from now
strftime(not_after, sizeof(not_after), "%Y%m%d%H%M%S", tm);
// Generate certificate
printf("Generating new certificate with serial number %s...\n", serial);
mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3);
// 1. Create CA certificate
printf("Generating new CA with serial number %s...\n", serial1);
mbedtls_x509write_crt_set_version(&ca_cert, MBEDTLS_X509_CRT_VERSION_3);
mbedtls_x509write_crt_set_serial_raw(&crt, serial, sizeof(serial)-1);
mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256);
mbedtls_x509write_crt_set_subject_key(&crt, &key);
mbedtls_x509write_crt_set_issuer_key(&crt, &key);
mbedtls_x509write_crt_set_issuer_name(&crt, "CN=pi.hole");
mbedtls_x509write_crt_set_validity(&crt, not_before, not_after);
mbedtls_x509write_crt_set_basic_constraints(&crt, 0, -1);
mbedtls_x509write_crt_set_subject_key_identifier(&crt);
mbedtls_x509write_crt_set_authority_key_identifier(&crt);
mbedtls_x509write_crt_set_serial_raw(&ca_cert, serial1, sizeof(serial1)-1);
mbedtls_x509write_crt_set_md_alg(&ca_cert, MBEDTLS_MD_SHA256);
mbedtls_x509write_crt_set_subject_key(&ca_cert, &ca_key);
mbedtls_x509write_crt_set_subject_key_identifier(&ca_cert);
mbedtls_x509write_crt_set_issuer_key(&ca_cert, &ca_key);
mbedtls_x509write_crt_set_authority_key_identifier(&ca_cert);
mbedtls_x509write_crt_set_issuer_name(&ca_cert, "CN=pi.hole,O=Pi-hole,C=DE");
mbedtls_x509write_crt_set_subject_name(&ca_cert, "CN=pi.hole,O=Pi-hole,C=DE");
mbedtls_x509write_crt_set_validity(&ca_cert, not_before, not_after);
mbedtls_x509write_crt_set_basic_constraints(&ca_cert, 1, -1);
// Export CA in PEM format
if((ret = mbedtls_x509write_crt_pem(&ca_cert, ca_buffer, sizeof(ca_buffer),
mbedtls_ctr_drbg_random, &ctr_drbg)) != 0)
{
printf("ERROR: mbedtls_x509write_crt_pem (CA) returned %d\n", ret);
return false;
}
printf("Generating new server certificate with serial number %s...\n", serial2);
mbedtls_x509write_crt_set_version(&server_cert, MBEDTLS_X509_CRT_VERSION_3);
mbedtls_x509write_crt_set_serial_raw(&server_cert, serial2, sizeof(serial2)-1);
mbedtls_x509write_crt_set_md_alg(&server_cert, MBEDTLS_MD_SHA256);
mbedtls_x509write_crt_set_subject_key(&server_cert, &server_key);
mbedtls_x509write_crt_set_subject_key_identifier(&server_cert);
mbedtls_x509write_crt_set_issuer_key(&server_cert, &ca_key);
mbedtls_x509write_crt_set_authority_key_identifier(&server_cert);
// subject name set below
mbedtls_x509write_crt_set_issuer_name(&server_cert, "CN=pi.hole,O=Pi-hole,C=DE");
mbedtls_x509write_crt_set_validity(&server_cert, not_before, not_after);
mbedtls_x509write_crt_set_basic_constraints(&server_cert, 0, -1);
// Set subject name depending on the (optionally) specified domain
{
char *subject_name = calloc(strlen(domain) + 4, sizeof(char));
strcpy(subject_name, "CN=");
strcat(subject_name, domain);
mbedtls_x509write_crt_set_subject_name(&crt, subject_name);
mbedtls_x509write_crt_set_subject_name(&server_cert, subject_name);
free(subject_name);
}
// Add "DNS:pi.hole" as subject alternative name (SAN)
//
// Since RFC 2818 (May 2000), the Common Name (CN) field is ignored
@ -209,85 +304,32 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
san_dns_pihole.next = &san_dns_domain; // Link this domain
}
ret = mbedtls_x509write_crt_set_subject_alternative_name(&crt, &san_dns_pihole);
ret = mbedtls_x509write_crt_set_subject_alternative_name(&server_cert, &san_dns_pihole);
if (ret != 0)
printf("mbedtls_x509write_crt_set_subject_alternative_name returned %d\n", ret);
// Export certificate in PEM format
if((ret = mbedtls_x509write_crt_pem(&crt, cert_buffer, sizeof(cert_buffer),
if((ret = mbedtls_x509write_crt_pem(&server_cert, cert_buffer, sizeof(cert_buffer),
mbedtls_ctr_drbg_random, &ctr_drbg)) != 0)
{
printf("ERROR: mbedtls_x509write_crt_pem returned %d\n", ret);
return false;
}
// Write private key and certificate to file
FILE *f = NULL;
printf("Storing key + certificate in %s ...\n", certfile);
if ((f = fopen(certfile, "wb")) == NULL)
{
printf("ERROR: Could not open %s for writing\n", certfile);
return false;
}
// Create file with CA certificate only
write_to_file(certfile, "CA certificate", "_ca.crt", (char*)ca_buffer, NULL);
// Write private key
olen = strlen((char *) key_buffer);
if (fwrite(key_buffer, 1, olen, f) != olen)
{
printf("ERROR: Could not write key to %s\n", certfile);
fclose(f);
return false;
}
// Create file with server certificate only
write_to_file(certfile, "server certificate", ".crt", (char*)cert_buffer, NULL);
// Write certificate
olen = strlen((char *) cert_buffer);
if (fwrite(cert_buffer, 1, olen, f) != olen)
{
printf("ERROR: Could not write certificate to %s\n", certfile);
fclose(f);
return false;
}
// Close key+cert file
fclose(f);
// Create second file with certificate only
char *certfile2 = calloc(strlen(certfile) + 5, sizeof(char));
strcpy(certfile2, certfile);
// If the certificate file name ends with ".pem" or ".key", replace it with ".crt"
// Otherwise, append ".crt" to the certificate file name
if (strlen(certfile2) > 4 &&
(strcmp(certfile2 + strlen(certfile2) - 4, ".pem") == 0 ||
strcmp(certfile2 + strlen(certfile2) - 4, ".key") == 0))
certfile2[strlen(certfile) - 4] = '\0';
strcat(certfile2, ".crt");
printf("Storing certificate in %s ...\n", certfile2);
if ((f = fopen(certfile2, "wb")) == NULL)
{
printf("ERROR: Could not open %s for writing\n", certfile2);
return false;
}
// Write certificate
olen = strlen((char *) cert_buffer);
if (fwrite(cert_buffer, 1, olen, f) != olen)
{
printf("ERROR: Could not write certificate to %s\n", certfile2);
fclose(f);
return false;
}
// Close cert file
fclose(f);
free(certfile2);
// Write server's private key and certificate to file
write_to_file(certfile, "server key + certificate", NULL, (char*)cert_buffer, (char*)key_buffer);
// Free resources
mbedtls_x509write_crt_free(&crt);
mbedtls_pk_free(&key);
mbedtls_x509write_crt_free(&ca_cert);
mbedtls_x509write_crt_free(&server_cert);
mbedtls_pk_free(&ca_key);
mbedtls_pk_free(&server_key);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
@ -345,11 +387,12 @@ enum cert_check read_certificate(const char* certfile, const char *domain, const
return CERT_FILE_NOT_FOUND;
}
bool has_key = true;
int rc = mbedtls_pk_parse_keyfile(&key, certfile, NULL, mbedtls_ctr_drbg_random, &ctr_drbg);
if (rc != 0)
{
log_err("Cannot parse key: Error code %d", rc);
return CERT_CANNOT_PARSE_KEY;
log_info("No key found");
has_key = false;
}
rc = mbedtls_x509_crt_parse_file(&crt, certfile);
@ -444,7 +487,7 @@ next_san:
puts("Certificate (X.509):\n");
puts(certinfo);
if(!private_key)
if(!private_key || !has_key)
goto end;
puts("Private key:");

View File

@ -64,7 +64,7 @@ CREATE TABLE info
value TEXT NOT NULL
);
INSERT INTO "info" VALUES('version','12');
INSERT INTO "info" VALUES('version','19');
CREATE TABLE domainlist_by_group
(
@ -136,14 +136,14 @@ CREATE VIEW vw_regex_blacklist AS SELECT domain, domainlist.id AS id, domainlist
AND domainlist.type = 3
ORDER BY domainlist.id;
CREATE VIEW vw_gravity AS SELECT domain, adlist_by_group.group_id AS group_id
CREATE VIEW vw_gravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM gravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id
LEFT JOIN adlist ON adlist.id = gravity.adlist_id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_id
WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1) AND adlist.type = 0;
CREATE VIEW vw_antigravity AS SELECT domain, adlist_by_group.group_id AS group_id
CREATE VIEW vw_antigravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM antigravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = antigravity.adlist_id
LEFT JOIN adlist ON adlist.id = antigravity.adlist_id
@ -180,17 +180,17 @@ CREATE TRIGGER tr_group_zero AFTER DELETE ON "group"
INSERT OR IGNORE INTO "group" (id,enabled,name) VALUES (0,1,'Default');
END;
CREATE TRIGGER tr_domainlist_delete AFTER DELETE ON domainlist
CREATE TRIGGER tr_domainlist_delete BEFORE DELETE ON domainlist
BEGIN
DELETE FROM domainlist_by_group WHERE domainlist_id = OLD.id;
END;
CREATE TRIGGER tr_adlist_delete AFTER DELETE ON adlist
CREATE TRIGGER tr_adlist_delete BEFORE DELETE ON adlist
BEGIN
DELETE FROM adlist_by_group WHERE adlist_id = OLD.id;
END;
CREATE TRIGGER tr_client_delete AFTER DELETE ON client
CREATE TRIGGER tr_client_delete BEFORE DELETE ON client
BEGIN
DELETE FROM client_by_group WHERE client_id = OLD.id;
END;

View File

@ -1,13 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB+jCCAVmgAwIBAgIPMDY1NTgwNDA4Mjk5OTE2MAwGCCqGSM49BAMCBQAwEjEQ
MA4GA1UEAwwHcGkuaG9sZTAeFw0wMTAxMDEwMDAwMDBaFw0zMDEyMzEyMzU5NTla
MBIxEDAOBgNVBAMMB3BpLmhvbGUwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAFD
nUd44uNJqnVev6mcVAmq8Flz3ZNfIvgrAhl2mweX3k9zRdyfxfPKjHRxaEzLJxCB
wZsjDee558Jlpd9zcK5TfQE8N1v3WH6sOFXj5WQSQA08FuApDqQKIc20wB26DJp4
fHMWmp6AYa+BDaXhWn3yXgzvMEIbor9FtkOUO81QKDASUaNNMEswCQYDVR0TBAIw
ADAdBgNVHQ4EFgQUwoMB6ZJW7JkdwmCy2Hp6sP0dkEAwHwYDVR0jBBgwFoAUwoMB
6ZJW7JkdwmCy2Hp6sP0dkEAwDAYIKoZIzj0EAwIFAAOBjAAwgYgCQgEj2ykySK/P
gbyT+J+vXVMBWbdHudfkncM7ItPhMN1PyM1J0Tp5emXm6ZLtlZpNGgqXxH1U94UO
5AFs5PeJuLI43AJCAZJAEiEHqEycXxCm3Ip+64a7lb5H6Y3gpbUKjHwsZW4svTdk
vn5eqsRcmuhW7t0pYJcpGGE52tV+Ayo8BQOLHJzd
MIIB4TCCAWagAwIBAgIPNjYyMjUxNzYwMDkxMDA3MAoGCCqGSM49BAMCMDExEDAO
BgNVBAMMB3BpLmhvbGUxEDAOBgNVBAoMB1BpLWhvbGUxCzAJBgNVBAYTAkRFMCAX
DTIzMDExNjIxMTUxMloYDzIwNTMwMTE2MjExNTEyWjASMRAwDgYDVQQDDAdwaS5o
b2xlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuH7sWfGRkvm5s5LVYTwbM6PjZmuK
4KPhA5qaWfVqJw4jeEMkvyT4CKtiruLEBcqzimkBhP6dlMOUM/K0caRC5Jm46fMC
9bV374ibYXxiX4bkiu8m/GDjM5RgiS1D1x+Uo2EwXzAdBgNVHQ4EFgQUh4lGwfX0
GfdLVzkkHCuoxDkdiYkwHwYDVR0jBBgwFoAUUeMrZ6L+iRSicUSYbomqc/gPaikw
CQYDVR0TBAIwADASBgNVHREECzAJggdwaS5ob2xlMAoGCCqGSM49BAMCA2kAMGYC
MQDalH2DB1QTs5T3Vr4Ok+k+9E2xE2eHowMow5jHhYqmxW0jUqeNO1GWVyKQydmH
sbQCMQDlcnMv4G3at02j/E7RBU67mRYL+DE5k0ygX1ANFYMZKrTM0960uoo8/DUl
3cdeFrg=
-----END CERTIFICATE-----

View File

@ -1,20 +1,19 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIALL5s+KkTtEXyERZbBHO3A3tbBhh8hoWu9KWDVMcGHDiBc+CwA3Sl
XOrHu1iGFZydVLPAIFZDVaD6caVVWTBBVtigBwYFK4EEACOhgYkDgYYABAFDnUd4
4uNJqnVev6mcVAmq8Flz3ZNfIvgrAhl2mweX3k9zRdyfxfPKjHRxaEzLJxCBwZsj
Dee558Jlpd9zcK5TfQE8N1v3WH6sOFXj5WQSQA08FuApDqQKIc20wB26DJp4fHMW
mp6AYa+BDaXhWn3yXgzvMEIbor9FtkOUO81QKDASUQ==
MIGkAgEBBDBGWIbQ11v8sQjrlj+KUS7OJoR0M9xyZyMLhkejtXlHGNXn2lK8ZzPW
UUA6+ZqgdA+gBwYFK4EEACKhZANiAAS4fuxZ8ZGS+bmzktVhPBszo+Nma4rgo+ED
mppZ9WonDiN4QyS/JPgIq2Ku4sQFyrOKaQGE/p2Uw5Qz8rRxpELkmbjp8wL1tXfv
iJthfGJfhuSK7yb8YOMzlGCJLUPXH5Q=
-----END EC PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIB+jCCAVmgAwIBAgIPMDY1NTgwNDA4Mjk5OTE2MAwGCCqGSM49BAMCBQAwEjEQ
MA4GA1UEAwwHcGkuaG9sZTAeFw0wMTAxMDEwMDAwMDBaFw0zMDEyMzEyMzU5NTla
MBIxEDAOBgNVBAMMB3BpLmhvbGUwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAFD
nUd44uNJqnVev6mcVAmq8Flz3ZNfIvgrAhl2mweX3k9zRdyfxfPKjHRxaEzLJxCB
wZsjDee558Jlpd9zcK5TfQE8N1v3WH6sOFXj5WQSQA08FuApDqQKIc20wB26DJp4
fHMWmp6AYa+BDaXhWn3yXgzvMEIbor9FtkOUO81QKDASUaNNMEswCQYDVR0TBAIw
ADAdBgNVHQ4EFgQUwoMB6ZJW7JkdwmCy2Hp6sP0dkEAwHwYDVR0jBBgwFoAUwoMB
6ZJW7JkdwmCy2Hp6sP0dkEAwDAYIKoZIzj0EAwIFAAOBjAAwgYgCQgEj2ykySK/P
gbyT+J+vXVMBWbdHudfkncM7ItPhMN1PyM1J0Tp5emXm6ZLtlZpNGgqXxH1U94UO
5AFs5PeJuLI43AJCAZJAEiEHqEycXxCm3Ip+64a7lb5H6Y3gpbUKjHwsZW4svTdk
vn5eqsRcmuhW7t0pYJcpGGE52tV+Ayo8BQOLHJzd
MIIB4TCCAWagAwIBAgIPNjYyMjUxNzYwMDkxMDA3MAoGCCqGSM49BAMCMDExEDAO
BgNVBAMMB3BpLmhvbGUxEDAOBgNVBAoMB1BpLWhvbGUxCzAJBgNVBAYTAkRFMCAX
DTIzMDExNjIxMTUxMloYDzIwNTMwMTE2MjExNTEyWjASMRAwDgYDVQQDDAdwaS5o
b2xlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuH7sWfGRkvm5s5LVYTwbM6PjZmuK
4KPhA5qaWfVqJw4jeEMkvyT4CKtiruLEBcqzimkBhP6dlMOUM/K0caRC5Jm46fMC
9bV374ibYXxiX4bkiu8m/GDjM5RgiS1D1x+Uo2EwXzAdBgNVHQ4EFgQUh4lGwfX0
GfdLVzkkHCuoxDkdiYkwHwYDVR0jBBgwFoAUUeMrZ6L+iRSicUSYbomqc/gPaikw
CQYDVR0TBAIwADASBgNVHREECzAJggdwaS5ob2xlMAoGCCqGSM49BAMCA2kAMGYC
MQDalH2DB1QTs5T3Vr4Ok+k+9E2xE2eHowMow5jHhYqmxW0jUqeNO1GWVyKQydmH
sbQCMQDlcnMv4G3at02j/E7RBU67mRYL+DE5k0ygX1ANFYMZKrTM0960uoo8/DUl
3cdeFrg=
-----END CERTIFICATE-----

13
test/test_ca.crt Normal file
View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB8TCCAXegAwIBAgIPMDQ4MzEwMzI5NDMzNTEyMAoGCCqGSM49BAMCMDExEDAO
BgNVBAMMB3BpLmhvbGUxEDAOBgNVBAoMB1BpLWhvbGUxCzAJBgNVBAYTAkRFMCAX
DTIzMDExNjIxMTUxMloYDzIwNTMwMTE2MjExNTEyWjAxMRAwDgYDVQQDDAdwaS5o
b2xlMRAwDgYDVQQKDAdQaS1ob2xlMQswCQYDVQQGEwJERTB2MBAGByqGSM49AgEG
BSuBBAAiA2IABJLGB8r6Fs40ARHd8OtOIfAjAyW07QMO7LRlS/M2lJhXWUoPMn3W
KsGhfOPB24aJZ/aFjZRSBnlgiBTten/SJIQ8ooSVLHLZXKHMDwAqjLA7woMb2kOC
QiqHRnKdY3xH2KNTMFEwHQYDVR0OBBYEFFHjK2ei/okUonFEmG6JqnP4D2opMB8G
A1UdIwQYMBaAFFHjK2ei/okUonFEmG6JqnP4D2opMA8GA1UdEwEB/wQFMAMBAf8w
CgYIKoZIzj0EAwIDaAAwZQIxAJAP3Hx3d6WWys1JiV68CeGL8+hngTmEkzcPivyC
YqkJR8Ni7TBvhtYViyccQ6g3LAIwIsbtRFStxJYuRDi9tTwHuOKZby/oYUwRXMff
7XmiEM85caDyY4rG6oN910Cm+v8c
-----END CERTIFICATE-----

View File

@ -190,7 +190,7 @@
run bash -c "grep -c 'get_client_querystr: SELECT id from vw_blacklist WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} != "0" ]]
run bash -c "grep -c 'get_client_querystr: SELECT domain from vw_gravity WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
run bash -c "grep -c 'get_client_querystr: SELECT adlist_id from vw_gravity WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} != "0" ]]
run bash -c "grep -c 'Regex allow ([[:digit:]]*, DB ID [[:digit:]]*) .* NOT ENABLED for client 127.0.0.4' /var/log/pihole/FTL.log"
@ -222,7 +222,7 @@
run bash -c "grep -c 'get_client_querystr: SELECT id from vw_blacklist WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} != "0" ]]
run bash -c "grep -c 'get_client_querystr: SELECT domain from vw_gravity WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
run bash -c "grep -c 'get_client_querystr: SELECT adlist_id from vw_gravity WHERE domain = ? AND group_id IN (4);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} != "0" ]]
run bash -c "grep -c 'Regex allow ([[:digit:]]*, DB ID [[:digit:]]*) .* NOT ENABLED for client 127.0.0.5' /var/log/pihole/FTL.log"
@ -260,7 +260,7 @@
run bash -c "grep -c 'get_client_querystr: SELECT id from vw_blacklist WHERE domain = ? AND group_id IN (5);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "1" ]]
run bash -c "grep -c 'get_client_querystr: SELECT domain from vw_gravity WHERE domain = ? AND group_id IN (5);' /var/log/pihole/FTL.log"
run bash -c "grep -c 'get_client_querystr: SELECT adlist_id from vw_gravity WHERE domain = ? AND group_id IN (5);' /var/log/pihole/FTL.log"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "1" ]]
run bash -c "grep -c 'Regex allow ([[:digit:]]*, DB ID [[:digit:]]*) .* NOT ENABLED for client 127.0.0.6' /var/log/pihole/FTL.log"
@ -454,16 +454,16 @@
@test "pihole-FTL.db schema is as expected" {
run bash -c './pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db .dump'
printf "%s\n" "${lines[@]}"
[[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"query_storage\" (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type INTEGER NOT NULL, status INTEGER NOT NULL, domain INTEGER NOT NULL, client INTEGER NOT NULL, forward INTEGER, additional_info INTEGER, reply_type INTEGER, reply_time REAL, dnssec INTEGER, regex_id INTEGER);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"query_storage\" (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type INTEGER NOT NULL, status INTEGER NOT NULL, domain INTEGER NOT NULL, client INTEGER NOT NULL, forward INTEGER, additional_info INTEGER, reply_type INTEGER, reply_time REAL, dnssec INTEGER, list_id INTEGER);"* ]]
[[ "${lines[@]}" == *"CREATE INDEX idx_queries_timestamps ON \"query_storage\" (timestamp);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE ftl (id INTEGER PRIMARY KEY NOT NULL, value BLOB NOT NULL, description TEXT);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE counters (id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"network\" (id INTEGER PRIMARY KEY NOT NULL, hwaddr TEXT UNIQUE NOT NULL, interface TEXT NOT NULL, firstSeen INTEGER NOT NULL, lastQuery INTEGER NOT NULL, numQueries INTEGER NOT NULL, macVendor TEXT, aliasclient_id INTEGER);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"network_addresses\" (network_id INTEGER NOT NULL, ip TEXT UNIQUE NOT NULL, lastSeen INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), name TEXT, nameUpdated INTEGER, FOREIGN KEY(network_id) REFERENCES network(id));"* ]]
[[ "${lines[@]}" == *"CREATE TABLE aliasclient (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, comment TEXT);"* ]]
[[ "${lines[@]}" == *"INSERT INTO ftl VALUES(0,16,'Database version');"* ]]
[[ "${lines[@]}" == *"INSERT INTO ftl VALUES(0,17,'Database version');"* ]]
# vvv This has been added in version 10 vvv
[[ "${lines[@]}" == *"CREATE VIEW queries AS SELECT id, timestamp, type, status, CASE typeof(domain) WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain,CASE typeof(client) WHEN 'integer' THEN (SELECT ip FROM client_by_id c WHERE c.id = q.client) ELSE client END client,CASE typeof(forward) WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward,CASE typeof(additional_info) WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, reply_type, reply_time, dnssec, regex_id FROM query_storage q;"* ]]
[[ "${lines[@]}" == *"CREATE VIEW queries AS SELECT id, timestamp, type, status, CASE typeof(domain) WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain,CASE typeof(client) WHEN 'integer' THEN (SELECT ip FROM client_by_id c WHERE c.id = q.client) ELSE client END client,CASE typeof(forward) WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward,CASE typeof(additional_info) WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, reply_type, reply_time, dnssec, list_id FROM query_storage q;"* ]]
[[ "${lines[@]}" == *"CREATE TABLE domain_by_id (id INTEGER PRIMARY KEY, domain TEXT NOT NULL);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE client_by_id (id INTEGER PRIMARY KEY, ip TEXT NOT NULL, name TEXT);"* ]]
[[ "${lines[@]}" == *"CREATE TABLE forward_by_id (id INTEGER PRIMARY KEY, forward TEXT NOT NULL);"* ]]
@ -1455,22 +1455,23 @@
[[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]]
[[ "${lines[1]}" == "Certificate (X.509):" ]]
[[ "${lines[2]}" == " cert. version : 3" ]]
[[ "${lines[3]}" == " serial number : 30:36:35:35:38:30:34:30:38:32:39:39:39:31:36" ]]
[[ "${lines[4]}" == " issuer name : CN=pi.hole" ]]
[[ "${lines[3]}" == " serial number : 36:36:32:32:35:31:37:36:30:30:39:31:30:30:37" ]]
[[ "${lines[4]}" == " issuer name : CN=pi.hole, O=Pi-hole, C=DE" ]]
[[ "${lines[5]}" == " subject name : CN=pi.hole" ]]
[[ "${lines[6]}" == " issued on : 2001-01-01 00:00:00" ]]
[[ "${lines[7]}" == " expires on : 2030-12-31 23:59:59" ]]
[[ "${lines[6]}" == " issued on : 2023-01-16 21:15:12" ]]
[[ "${lines[7]}" == " expires on : 2053-01-16 21:15:12" ]]
[[ "${lines[8]}" == " signed using : ECDSA with SHA256" ]]
[[ "${lines[9]}" == " EC key size : 521 bits" ]]
[[ "${lines[9]}" == " EC key size : 384 bits" ]]
[[ "${lines[10]}" == " basic constraints : CA=false" ]]
[[ "${lines[11]}" == "Public key (PEM):" ]]
[[ "${lines[12]}" == "-----BEGIN PUBLIC KEY-----" ]]
[[ "${lines[13]}" == "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBQ51HeOLjSap1Xr+pnFQJqvBZc92T" ]]
[[ "${lines[14]}" == "XyL4KwIZdpsHl95Pc0Xcn8Xzyox0cWhMyycQgcGbIw3nuefCZaXfc3CuU30BPDdb" ]]
[[ "${lines[15]}" == "91h+rDhV4+VkEkANPBbgKQ6kCiHNtMAdugyaeHxzFpqegGGvgQ2l4Vp98l4M7zBC" ]]
[[ "${lines[16]}" == "G6K/RbZDlDvNUCgwElE=" ]]
[[ "${lines[17]}" == "-----END PUBLIC KEY-----" ]]
[[ "${lines[18]}" == "" ]]
[[ "${lines[11]}" == " subject alt name :" ]]
[[ "${lines[12]}" == " dNSName : pi.hole" ]]
[[ "${lines[13]}" == "Public key (PEM):" ]]
[[ "${lines[14]}" == "-----BEGIN PUBLIC KEY-----" ]]
[[ "${lines[15]}" == "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuH7sWfGRkvm5s5LVYTwbM6PjZmuK4KPh" ]]
[[ "${lines[16]}" == "A5qaWfVqJw4jeEMkvyT4CKtiruLEBcqzimkBhP6dlMOUM/K0caRC5Jm46fMC9bV3" ]]
[[ "${lines[17]}" == "74ibYXxiX4bkiu8m/GDjM5RgiS1D1x+U" ]]
[[ "${lines[18]}" == "-----END PUBLIC KEY-----" ]]
[[ "${lines[19]}" == "" ]]
}
@test "X.509 certificate parser returns expected result (with private key)" {
@ -1480,38 +1481,38 @@
[[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]]
[[ "${lines[1]}" == "Certificate (X.509):" ]]
[[ "${lines[2]}" == " cert. version : 3" ]]
[[ "${lines[3]}" == " serial number : 30:36:35:35:38:30:34:30:38:32:39:39:39:31:36" ]]
[[ "${lines[4]}" == " issuer name : CN=pi.hole" ]]
[[ "${lines[3]}" == " serial number : 36:36:32:32:35:31:37:36:30:30:39:31:30:30:37" ]]
[[ "${lines[4]}" == " issuer name : CN=pi.hole, O=Pi-hole, C=DE" ]]
[[ "${lines[5]}" == " subject name : CN=pi.hole" ]]
[[ "${lines[6]}" == " issued on : 2001-01-01 00:00:00" ]]
[[ "${lines[7]}" == " expires on : 2030-12-31 23:59:59" ]]
[[ "${lines[6]}" == " issued on : 2023-01-16 21:15:12" ]]
[[ "${lines[7]}" == " expires on : 2053-01-16 21:15:12" ]]
[[ "${lines[8]}" == " signed using : ECDSA with SHA256" ]]
[[ "${lines[9]}" == " EC key size : 521 bits" ]]
[[ "${lines[9]}" == " EC key size : 384 bits" ]]
[[ "${lines[10]}" == " basic constraints : CA=false" ]]
[[ "${lines[11]}" == "Private key:" ]]
[[ "${lines[12]}" == " Type: EC" ]]
[[ "${lines[13]}" == " Curve type: Short Weierstrass (y^2 = x^3 + a x + b)" ]]
[[ "${lines[14]}" == " Bitlen: 518 bit" ]]
[[ "${lines[15]}" == " Private key:" ]]
[[ "${lines[16]}" == " D = 0x2CBE6CF8A913B445F211165B0473B7037B5B06187C8685AEF4A58354C7061C388173E0B00374A55CEAC7BB5886159C9D54B3C020564355A0FA71A55559304156D8"* ]]
[[ "${lines[17]}" == " Public key:" ]]
[[ "${lines[18]}" == " X = 0x01439D4778E2E349AA755EBFA99C5409AAF05973DD935F22F82B0219769B0797DE4F7345DC9FC5F3CA8C7471684CCB271081C19B230DE7B9E7C265A5DF7370AE537D"* ]]
[[ "${lines[19]}" == " Y = 0x013C375BF7587EAC3855E3E56412400D3C16E0290EA40A21CDB4C01DBA0C9A787C73169A9E8061AF810DA5E15A7DF25E0CEF30421BA2BF45B643943BCD5028301251"* ]]
[[ "${lines[20]}" == " Z = 0x01"* ]]
[[ "${lines[21]}" == "Private key (PEM):" ]]
[[ "${lines[22]}" == "-----BEGIN EC PRIVATE KEY-----" ]]
[[ "${lines[23]}" == "MIHcAgEBBEIALL5s+KkTtEXyERZbBHO3A3tbBhh8hoWu9KWDVMcGHDiBc+CwA3Sl" ]]
[[ "${lines[24]}" == "XOrHu1iGFZydVLPAIFZDVaD6caVVWTBBVtigBwYFK4EEACOhgYkDgYYABAFDnUd4" ]]
[[ "${lines[25]}" == "4uNJqnVev6mcVAmq8Flz3ZNfIvgrAhl2mweX3k9zRdyfxfPKjHRxaEzLJxCBwZsj" ]]
[[ "${lines[26]}" == "Dee558Jlpd9zcK5TfQE8N1v3WH6sOFXj5WQSQA08FuApDqQKIc20wB26DJp4fHMW" ]]
[[ "${lines[27]}" == "mp6AYa+BDaXhWn3yXgzvMEIbor9FtkOUO81QKDASUQ==" ]]
[[ "${lines[28]}" == "-----END EC PRIVATE KEY-----" ]]
[[ "${lines[29]}" == "Public key (PEM):" ]]
[[ "${lines[30]}" == "-----BEGIN PUBLIC KEY-----" ]]
[[ "${lines[31]}" == "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBQ51HeOLjSap1Xr+pnFQJqvBZc92T" ]]
[[ "${lines[32]}" == "XyL4KwIZdpsHl95Pc0Xcn8Xzyox0cWhMyycQgcGbIw3nuefCZaXfc3CuU30BPDdb" ]]
[[ "${lines[33]}" == "91h+rDhV4+VkEkANPBbgKQ6kCiHNtMAdugyaeHxzFpqegGGvgQ2l4Vp98l4M7zBC" ]]
[[ "${lines[34]}" == "G6K/RbZDlDvNUCgwElE=" ]]
[[ "${lines[11]}" == " subject alt name :" ]]
[[ "${lines[12]}" == " dNSName : pi.hole" ]]
[[ "${lines[13]}" == "Private key:" ]]
[[ "${lines[14]}" == " Type: EC" ]]
[[ "${lines[15]}" == " Curve type: Short Weierstrass (y^2 = x^3 + a x + b)" ]]
[[ "${lines[16]}" == " Bitlen: 383 bit" ]]
[[ "${lines[17]}" == " Private key:" ]]
[[ "${lines[18]}" == " D = 0x465886D0D75BFCB108EB963F8A512ECE26847433DC7267230B8647A3B5794718D5E7DA52BC6733D651403AF99AA0740F"* ]]
[[ "${lines[19]}" == " Public key:" ]]
[[ "${lines[20]}" == " X = 0xB87EEC59F19192F9B9B392D5613C1B33A3E3666B8AE0A3E1039A9A59F56A270E23784324BF24F808AB62AEE2C405CAB3"* ]]
[[ "${lines[21]}" == " Y = 0x8A690184FE9D94C39433F2B471A442E499B8E9F302F5B577EF889B617C625F86E48AEF26FC60E3339460892D43D71F94"* ]]
[[ "${lines[22]}" == " Z = 0x01"* ]]
[[ "${lines[23]}" == "Private key (PEM):" ]]
[[ "${lines[24]}" == "-----BEGIN EC PRIVATE KEY-----" ]]
[[ "${lines[25]}" == "MIGkAgEBBDBGWIbQ11v8sQjrlj+KUS7OJoR0M9xyZyMLhkejtXlHGNXn2lK8ZzPW" ]]
[[ "${lines[26]}" == "UUA6+ZqgdA+gBwYFK4EEACKhZANiAAS4fuxZ8ZGS+bmzktVhPBszo+Nma4rgo+ED" ]]
[[ "${lines[27]}" == "mppZ9WonDiN4QyS/JPgIq2Ku4sQFyrOKaQGE/p2Uw5Qz8rRxpELkmbjp8wL1tXfv" ]]
[[ "${lines[28]}" == "iJthfGJfhuSK7yb8YOMzlGCJLUPXH5Q=" ]]
[[ "${lines[29]}" == "-----END EC PRIVATE KEY-----" ]]
[[ "${lines[30]}" == "Public key (PEM):" ]]
[[ "${lines[31]}" == "-----BEGIN PUBLIC KEY-----" ]]
[[ "${lines[32]}" == "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuH7sWfGRkvm5s5LVYTwbM6PjZmuK4KPh" ]]
[[ "${lines[33]}" == "A5qaWfVqJw4jeEMkvyT4CKtiruLEBcqzimkBhP6dlMOUM/K0caRC5Jm46fMC9bV3" ]]
[[ "${lines[34]}" == "74ibYXxiX4bkiu8m/GDjM5RgiS1D1x+U" ]]
[[ "${lines[35]}" == "-----END PUBLIC KEY-----" ]]
[[ "${lines[36]}" == "" ]]
}
@ -1567,7 +1568,16 @@
@test "SHA256 checksum working" {
run bash -c './pihole-FTL sha256sum test/test.pem'
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "eae293f0c30369935a7457a789658bedebf92d544e7526bc43aa07883a597fa9 test/test.pem" ]]
[[ ${lines[0]} == "ce4c01340ef46bf3bc26831f7c53763d57c863528826aa795f1da5e16d6e7b2d test/test.pem" ]]
}
@test "Internal IP -> name resolution works" {
run bash -c "./pihole-FTL ptr 127.0.0.1 | tail -n1"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "localhost" ]]
run bash -c "./pihole-FTL ptr ::1 | tail -n1"
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "localhost" ]]
}
@test "API validation" {