Merge remote-tracking branch 'origin/development' into new/http

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2023-05-23 20:08:39 +02:00
27 changed files with 7081 additions and 2854 deletions

View File

@@ -24,7 +24,7 @@ jobs:
steps:
-
name: Checkout code
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
-
name: "Calculate required variables"
id: variables
@@ -90,7 +90,7 @@ jobs:
steps:
-
name: Checkout code
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
-
name: "Fix ownership of repository"
run: chown -R root .
@@ -144,7 +144,7 @@ jobs:
steps:
-
name: Checkout code
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
-
name: Get Binaries and documentation built in previous jobs
uses: actions/download-artifact@v3.0.2

View File

@@ -10,7 +10,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
-
name: Spell-Checking
uses: codespell-project/actions-codespell@master

View File

@@ -2,12 +2,16 @@ name: Mark stale issues
on:
schedule:
- cron: '0 8 * * *'
- cron: '0 8 * * *'
workflow_dispatch:
issue_comment:
env:
stale_label: stale
jobs:
stale:
stale_action:
if: github.event_name != 'issue_comment'
runs-on: ubuntu-latest
permissions:
issues: write
@@ -19,8 +23,23 @@ jobs:
days-before-stale: 30
days-before-close: 5
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'
stale-issue-label: 'stale'
stale-issue-label: $stale_label
exempt-issue-labels: 'Fixed in next release, Bug, Bug:Confirmed, Bugfix in progress, documentation needed, internal'
exempt-all-issue-assignees: true
operations-per-run: 300
close-issue-reason: 'not_planned'
remove_stale: # trigger "stale" removal immediately when stale issues are commented on
if: github.event_name == 'issue_comment'
permissions:
contents: read # for actions/checkout
issues: write # to edit issues label
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.4.0
- name: Remove 'stale' label
run: gh issue edit ${{ github.event.issue.number }} --remove-label $stale_label
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,7 +11,7 @@ jobs:
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v3.5.0
uses: actions/checkout@v3.5.2
- name: Opening pull request
run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'
env:

View File

@@ -42,11 +42,18 @@ for dir in "${path[@]}"; do
ls -1"
)"
# Only try to create the subdir if does not already exist
if [[ "${dir_content[*]}" =~ "${dir}" ]]; then
echo "Dir: ${old_path}/${dir} already exists"
else
echo "Creating dir: ${old_path}/${dir}"
# Loop over the dir content and check if this exact dir already exists
path_exists=0
for content in "${dir_content[@]}"; do
if [[ "${content}" == "${dir}" ]]; then
echo "Dir: ${old_path}/${dir} already exists"
path_exists=1
fi
done
# If the dir does not exist, create it
if [[ "${path_exists}" -eq 0 ]]; then
echo "Dir: ${old_path}/${dir} does not exist. Creating it."
sftp -b - "${USER}"@"${HOST}" <<< "cd ${old_path}
-mkdir ${dir}"
fi

View File

@@ -194,8 +194,6 @@ set(sources
daemon.h
datastructure.c
datastructure.h
dhcp-discover.c
dhcp-discover.h
dnsmasq_interface.c
dnsmasq_interface.h
edns0.c
@@ -208,6 +206,8 @@ set(sources
FTL.h
gc.c
gc.h
gravity-tools.c
gravity-tools.h
log.c
log.h
main.c
@@ -267,6 +267,7 @@ add_executable(pihole-FTL
$<TARGET_OBJECTS:syscalls>
$<TARGET_OBJECTS:tomlc99>
$<TARGET_OBJECTS:config>
$<TARGET_OBJECTS:tools>
)
if(STATIC)
set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON)
@@ -352,3 +353,4 @@ add_subdirectory(lua/scripts)
add_subdirectory(tre-regex)
add_subdirectory(syscalls)
add_subdirectory(config)
add_subdirectory(tools)

View File

@@ -36,8 +36,10 @@
#include "shmem.h"
// LUA dependencies
#include "lua/ftl_lua.h"
// gravity_parseList()
#include "gravity-tools.h"
// run_dhcp_discover()
#include "dhcp-discover.h"
#include "tools/dhcp-discover.h"
// mg_version()
#include "webserver/civetweb/civetweb.h"
// cJSON_Version()
@@ -52,6 +54,10 @@
#include "api/api.h"
// generate_certificate()
#include "webserver/x509.h"
// run_dhcp_discover()
#include "tools/dhcp-discover.h"
// run_arp_scan()
#include "tools/arp-scan.h"
// defined in dnsmasq.c
extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal);
@@ -143,7 +149,15 @@ static const char __attribute__ ((pure)) *cli_color(const char *color)
return is_term() ? color : "";
}
static inline bool strEndsWith(const char *input, const char *end){
// Go back to beginning of line and erase to end of line if STDOUT is a terminal
const char __attribute__ ((pure)) *cli_over(void)
{
// \x1b[K is the ANSI escape sequence for "erase to end of line"
return is_term() ? "\r\x1b[K" : "\r";
}
static inline bool strEndsWith(const char *input, const char *end)
{
return strcmp(input + strlen(input) - strlen(end), end) == 0;
}
@@ -307,6 +321,39 @@ void parse_args(int argc, char* argv[])
exit(generate_certificate(argv[2], rsa) ? EXIT_SUCCESS : EXIT_FAILURE);
}
// If the first argument is "gravity" (e.g., /usr/bin/pihole-FTL gravity),
// we offer some specialized gravity tools
if(argc > 1 && strcmp(argv[1], "gravity") == 0)
{
// pihole-FTL gravity parseList <infile> <outfile> <adlistID>
if(argc == 6 && strcmp(argv[2], "parseList") == 0)
{
// Parse the given list and write the result to the given file
exit(gravity_parseList(argv[3], argv[4], argv[5]));
}
printf("Incorrect usage of pihole-FTL gravity subcommand\n");
exit(EXIT_FAILURE);
}
// DHCP discovery mode
if(argc > 1 && strcmp(argv[1], "dhcp-discover") == 0)
{
// Enable stdout printing
cli_mode = true;
exit(run_dhcp_discover());
}
// ARP scanning mode
if(argc > 1 && strcmp(argv[1], "arp-scan") == 0)
{
// Enable stdout printing
cli_mode = true;
const bool scan_all = argc > 2 && strcmp(argv[2], "-a") == 0;
const bool extreme_mode = argc > 2 && strcmp(argv[2], "-x") == 0;
exit(run_arp_scan(scan_all, extreme_mode));
}
// start from 1, as argv[0] is the executable name
for(int i = 1; i < argc; i++)
{
@@ -639,14 +686,6 @@ void parse_args(int argc, char* argv[])
}
}
// Regex test mode
if(strcmp(argv[i], "dhcp-discover") == 0)
{
// Enable stdout printing
cli_mode = true;
exit(run_dhcp_discover());
}
// List of implemented arguments
if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0)
{
@@ -719,8 +758,8 @@ void parse_args(int argc, char* argv[])
printf("%sDebugging and special use:%s\n", yellow, normal);
printf("\t%sd%s, %sdebug%s Enter debugging mode\n", green, normal, green, normal);
printf("\t%stest%s Don't start pihole-FTL but\n", green, normal);
printf("\t instead quit immediately\n");
printf("\t%stest%s Don't start pihole-FTL but instead\n", green, normal);
printf("\t quit immediately\n");
printf("\t%s-f%s, %sno-daemon%s Don't go into daemon mode\n\n", green, normal, green, normal);
printf("%sConfig options:%s\n", yellow, normal);
@@ -751,6 +790,12 @@ void parse_args(int argc, char* argv[])
printf("%sOther:%s\n", yellow, normal);
printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal);
printf("\t network\n");
printf("\t%sarp-scan %s[-a/-x]%s Use ARP to scan local network for\n", green, cyan, normal);
printf("\t possible IP conflicts\n");
printf("\t Append %s-a%s to force scan on all\n", cyan, normal);
printf("\t interfaces\n");
printf("\t Append %s-x%s to force scan on all\n", cyan, normal);
printf("\t interfaces and scan 10x more often\n");
printf("\t%s--totp%s Generate valid TOTP token for 2FA\n", green, normal);
printf("\t authentication (if enabled)\n");
printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal);

View File

@@ -23,6 +23,7 @@ const char *cli_qst(void) __attribute__ ((const));
const char *cli_done(void) __attribute__ ((pure));
const char *cli_bold(void) __attribute__ ((pure));
const char *cli_normal(void) __attribute__ ((pure));
const char *cli_over(void) __attribute__ ((pure));
void test_dnsmasq_options(int argc, const char *argv[]);

View File

@@ -20,6 +20,47 @@
static const unsigned int capabilityIDs[] = { CAP_CHOWN , CAP_DAC_OVERRIDE , CAP_DAC_READ_SEARCH , CAP_FOWNER , CAP_FSETID , CAP_KILL , CAP_SETGID , CAP_SETUID , CAP_SETPCAP , CAP_LINUX_IMMUTABLE , CAP_NET_BIND_SERVICE , CAP_NET_BROADCAST , CAP_NET_ADMIN , CAP_NET_RAW , CAP_IPC_LOCK , CAP_IPC_OWNER , CAP_SYS_MODULE , CAP_SYS_RAWIO , CAP_SYS_CHROOT , CAP_SYS_PTRACE , CAP_SYS_PACCT , CAP_SYS_ADMIN , CAP_SYS_BOOT , CAP_SYS_NICE , CAP_SYS_RESOURCE , CAP_SYS_TIME , CAP_SYS_TTY_CONFIG , CAP_MKNOD , CAP_LEASE , CAP_AUDIT_WRITE , CAP_AUDIT_CONTROL , CAP_SETFCAP };
static const char* capabilityNames[] = {"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP"};
bool check_capability(const unsigned int cap)
{
// First assume header version 1
int capsize = 1; // VFS_CAP_U32_1
cap_user_data_t data = NULL;
cap_user_header_t hdr = calloc(sizeof(*hdr), capsize);
// Determine capabilities version used by the current kernel
capget(hdr, NULL);
// Check version
if (hdr->version != LINUX_CAPABILITY_VERSION_1)
{
// If unknown version, use largest supported version (3)
// Version 2 is deprecated according to linux/capability.h
if (hdr->version != LINUX_CAPABILITY_VERSION_2)
{
hdr->version = LINUX_CAPABILITY_VERSION_3;
capsize = 2; // VFS_CAP_U32_3
}
else
{
// Use version 2
capsize = 2; // VFS_CAP_U32_2
}
}
// Get current capabilities
data = calloc(sizeof(*data), capsize);
capget(hdr, data);
// Check if the capability is available
const bool available = ((data->permitted & (1 << cap)) && (data->effective & (1 << cap)));
// Free memory
free(hdr);
free(data);
return available;
}
bool check_capabilities(void)
{
// First assume header version 1

View File

@@ -10,6 +10,7 @@
#ifndef CAPABILITIES_H
#define CAPABILITIES_H
bool check_capability(const unsigned int cap);
bool check_capabilities(void);
#endif //CAPABILITIES_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.41.1"
#define SQLITE_VERSION_NUMBER 3041001
#define SQLITE_SOURCE_ID "2023-03-10 12:13:52 20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff"
#define SQLITE_VERSION "3.42.0"
#define SQLITE_VERSION_NUMBER 3042000
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -1655,20 +1655,23 @@ SQLITE_API int sqlite3_os_end(void);
** must ensure that no other SQLite interfaces are invoked by other
** threads while sqlite3_config() is running.</b>
**
** The sqlite3_config() interface
** may only be invoked prior to library initialization using
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
** [sqlite3_shutdown()] then it will return SQLITE_MISUSE.
** Note, however, that ^sqlite3_config() can be called as part of the
** implementation of an application-defined [sqlite3_os_init()].
**
** The first argument to sqlite3_config() is an integer
** [configuration option] that determines
** what property of SQLite is to be configured. Subsequent arguments
** vary depending on the [configuration option]
** in the first argument.
**
** For most configuration options, the sqlite3_config() interface
** may only be invoked prior to library initialization using
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
** The exceptional configuration options that may be invoked at any time
** are called "anytime configuration options".
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
** [sqlite3_shutdown()] with a first argument that is not an anytime
** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE.
** Note, however, that ^sqlite3_config() can be called as part of the
** implementation of an application-defined [sqlite3_os_init()].
**
** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
** ^If the option is unknown or SQLite is unable to set the option
** then this routine returns a non-zero [error code].
@@ -1776,6 +1779,23 @@ struct sqlite3_mem_methods {
** These constants are the available integer configuration options that
** can be passed as the first argument to the [sqlite3_config()] interface.
**
** Most of the configuration options for sqlite3_config()
** will only work if invoked prior to [sqlite3_initialize()] or after
** [sqlite3_shutdown()]. The few exceptions to this rule are called
** "anytime configuration options".
** ^Calling [sqlite3_config()] with a first argument that is not an
** anytime configuration option in between calls to [sqlite3_initialize()] and
** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE.
**
** The set of anytime configuration options can change (by insertions
** and/or deletions) from one release of SQLite to the next.
** As of SQLite version 3.42.0, the complete set of anytime configuration
** options is:
** <ul>
** <li> SQLITE_CONFIG_LOG
** <li> SQLITE_CONFIG_PCACHE_HDRSZ
** </ul>
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
** should check the return code from [sqlite3_config()] to make sure that
@@ -2122,28 +2142,28 @@ struct sqlite3_mem_methods {
** compile-time option is not set, then the default maximum is 1073741824.
** </dl>
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
@@ -2378,7 +2398,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_DQS_DML]]
** <dt>SQLITE_DBCONFIG_DQS_DML</td>
** <dt>SQLITE_DBCONFIG_DQS_DML</dt>
** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
** the legacy [double-quoted string literal] misfeature for DML statements
** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
@@ -2387,7 +2407,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_DQS_DDL]]
** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
** <dt>SQLITE_DBCONFIG_DQS_DDL</dt>
** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates
** the legacy [double-quoted string literal] misfeature for DDL statements,
** such as CREATE TABLE and CREATE INDEX. The
@@ -2396,7 +2416,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]]
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td>
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</dt>
** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to
** assume that database schemas are untainted by malicious content.
** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite
@@ -2416,7 +2436,7 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td>
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag. When activated, this flag causes all newly
** created database file to have a schema format version number (the 4-byte
@@ -2425,7 +2445,7 @@ struct sqlite3_mem_methods {
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
** newly created databases are generally not understandable by SQLite versions
** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there
** is now scarcely any need to generated database files that are compatible
** is now scarcely any need to generate database files that are compatible
** all the way back to version 3.0.0, and so this setting is of little
** practical use, but is provided so that SQLite can continue to claim the
** ability to generate new database files that are compatible with version
@@ -2436,6 +2456,38 @@ struct sqlite3_mem_methods {
** not considered a bug since SQLite versions 3.3.0 and earlier do not support
** either generated columns or decending indexes.
** </dd>
**
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt>
** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in
** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears
** a flag that enables collection of the sqlite3_stmt_scanstatus_v2()
** statistics. For statistics to be collected, the flag must be set on
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default. This option takes two arguments: an integer and a pointer to
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
** argument points to.
** </dd>
**
** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]]
** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt>
** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order
** in which tables and indexes are scanned so that the scans start at the end
** and work toward the beginning rather than starting at the beginning and
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
** same as setting [PRAGMA reverse_unordered_selects]. This option takes
** two arguments which are an integer and a pointer to an integer. The first
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
** reverse scan order flag, respectively. If the second argument is not NULL,
** then 0 or 1 is written into the integer that the second argument points to
** depending on if the reverse scan order flag is set after processing the
** first argument.
** </dd>
**
** </dl>
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
@@ -2456,7 +2508,9 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
@@ -6201,6 +6255,13 @@ SQLITE_API void sqlite3_activate_cerod(
** of the default VFS is not implemented correctly, or not implemented at
** all, then the behavior of sqlite3_sleep() may deviate from the description
** in the previous paragraphs.
**
** If a negative argument is passed to sqlite3_sleep() the results vary by
** VFS and operating system. Some system treat a negative argument as an
** instruction to sleep forever. Others understand it to mean do not sleep
** at all. ^In SQLite version 3.42.0 and later, a negative
** argument passed into sqlite3_sleep() is changed to zero before it is relayed
** down into the xSleep method of the VFS.
*/
SQLITE_API int sqlite3_sleep(int);
@@ -7828,9 +7889,9 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** is undefined if the mutex is not currently entered by the
** calling thread or is not currently allocated.
**
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
** sqlite3_mutex_leave() is a NULL pointer, then all three routines
** behave as no-ops.
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(),
** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer,
** then any of the four routines behaves as a no-op.
**
** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
*/
@@ -9564,18 +9625,28 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** identify that virtual table as being safe to use from within triggers
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
** virtual table can do no serious harm even if it is controlled by a
** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
** flag unless absolutely necessary.
** </dd>
**
** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]<dt>SQLITE_VTAB_USES_ALL_SCHEMAS</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** instruct the query planner to begin at least a read transaction on
** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the
** virtual table is used.
** </dd>
** </dl>
*/
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
#define SQLITE_VTAB_INNOCUOUS 2
#define SQLITE_VTAB_DIRECTONLY 3
#define SQLITE_VTAB_USES_ALL_SCHEMAS 4
/*
** CAPI3REF: Determine The Virtual Table Conflict Policy
@@ -10750,16 +10821,20 @@ SQLITE_API int sqlite3session_create(
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
/*
** CAPIREF: Conigure a Session Object
** CAPI3REF: Configure a Session Object
** METHOD: sqlite3_session
**
** This method is used to configure a session object after it has been
** created. At present the only valid value for the second parameter is
** [SQLITE_SESSION_OBJCONFIG_SIZE].
** created. At present the only valid values for the second parameter are
** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID].
**
** Arguments for sqlite3session_object_config()
*/
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
/*
** CAPI3REF: Options for sqlite3session_object_config
**
** The following values may passed as the the 4th parameter to
** The following values may passed as the the 2nd parameter to
** sqlite3session_object_config().
**
** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
@@ -10775,12 +10850,21 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
**
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
** the first table has been attached to the session object.
**
** <dt>SQLITE_SESSION_OBJCONFIG_ROWID <dd>
** This option is used to set, clear or query the flag that enables
** collection of data for tables with no explicit PRIMARY KEY.
**
** Normally, tables with no explicit PRIMARY KEY are simply ignored
** by the sessions module. However, if this flag is set, it behaves
** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted
** as their leftmost columns.
**
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
** the first table has been attached to the session object.
*/
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
/*
*/
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
#define SQLITE_SESSION_OBJCONFIG_ROWID 2
/*
** CAPI3REF: Enable Or Disable A Session Object
@@ -11913,9 +11997,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
** Invert the changeset before applying it. This is equivalent to inverting
** a changeset using sqlite3changeset_invert() before applying it. It is
** an error to specify this flag with a patchset.
**
** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd>
** Do not invoke the conflict handler callback for any changes that
** would not actually modify the database even if they were applied.
** Specifically, this means that the conflict handler is not invoked
** for:
** <ul>
** <li>a delete change if the row being deleted cannot be found,
** <li>an update change if the modified fields are already set to
** their new values in the conflicting row, or
** <li>an insert change if all fields of the conflicting row match
** the row being inserted.
** </ul>
*/
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
/*
** CAPI3REF: Constants Passed To The Conflict Handler

View File

@@ -26,7 +26,7 @@
#include "database/message-table.h"
// get_nprocs()
#include <sys/sysinfo.h>
// get_filepath_usage()
// get_path_usage()
#include "files.h"
// void calc_cpu_usage()
#include "daemon.h"
@@ -95,7 +95,7 @@ static int check_space(const char *file, unsigned int LastUsage)
log_debug(DEBUG_GC, "Checking free space at %s: %u%% %s %u%%", file, perc,
perc > config.misc.check.disk.v.ui ? ">" : "<=",
config.misc.check.disk.v.ui);
if(perc > config.misc.check.disk.v.ui && perc > LastUsage )
if(perc > config.misc.check.disk.v.ui && perc > LastUsage && perc <= 100.0)
log_resource_shortage(-1.0, 0, -1, perc, file, buffer);
return perc;

402
src/gravity-tools.c Normal file
View File

@@ -0,0 +1,402 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Gravity tools collection routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "gravity-tools.h"
#include "args.h"
#include <regex.h>
#include "database/sqlite3.h"
// Define valid domain patterns
// No need to include uppercase letters, as we convert to lowercase in gravity_ParseFileIntoDomains() already
// Adapted from https://stackoverflow.com/a/30007882
// - Added "(?:...)" to form non-capturing groups (slightly faster)
#define TLD_PATTERN "[a-z0-9][a-z0-9-]{0,61}[a-z0-9]"
#define SUBDOMAIN_PATTERN "([a-z0-9_-]{0,63}\\.)"
// supported exact style: subdomain.domain.tld
// SUBDOMAIN_PATTERN is mandatory for exact style, disallowing TLD blocking
#define VALID_DOMAIN_REXEX SUBDOMAIN_PATTERN"+"TLD_PATTERN
// supported ABP style: ||subdomain.domain.tlp^
// SUBDOMAIN_PATTERN is optional for ABP style, allowing TLD blocking: ||tld^
// See https://github.com/pi-hole/pi-hole/pull/5240
#define ABP_DOMAIN_REXEX "\\|\\|"SUBDOMAIN_PATTERN"*"TLD_PATTERN"\\^"
// Detects ABP extended CSS selectors ("##", "#!#", "#@#", "#?#") preceded by a letter
#define ABP_CSS_SELECTORS "[a-z]#[$?@]{0,1}#"
// A list of items of common local hostnames not to report as unusable
// Some lists (i.e StevenBlack's) contain these as they are supposed to be used as HOST files
// but flagging them as unusable causes more confusion than it's worth - so we suppress them from the output
#define FALSE_POSITIVES "localhost|localhost.localdomain|local|broadcasthost|localhost|ip6-localhost|ip6-loopback|lo0 localhost|ip6-localnet|ip6-mcastprefix|ip6-allnodes|ip6-allrouters|ip6-allhosts"
// Print progress for files larger than 10 MB
// This is to avoid printing progress for small files
// which would be printed too often as affect performance
#define PRINT_PROGRESS_THRESHOLD 10*1000*1000
// Number of invalid domains to print before skipping the rest
#define MAX_INVALID_DOMAINS 5
int gravity_parseList(const char *infile, const char *outfile, const char *adlistIDstr)
{
const char *info = cli_info();
const char *tick = cli_tick();
const char *cross = cli_cross();
const char *over = cli_over();
// Open input file
FILE *fpin = fopen(infile, "r");
if(fpin == NULL)
{
printf("%s %s Unable to open %s for reading\n", over, cross, infile);
return EXIT_FAILURE;
}
// Open output file
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
if(sqlite3_open_v2(outfile, &db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
{
printf("%s %s Unable to open database file %s for writing\n", over, cross, outfile);
fclose(fpin);
return EXIT_FAILURE;
}
// Get size of input file
fseek(fpin, 0L, SEEK_END);
const size_t fsize = ftell(fpin);
rewind(fpin);
// Compile regular expression to validate domains
regex_t exact_regex, abp_regex, false_positives_regex, abp_css_regex;
if(regcomp(&exact_regex, VALID_DOMAIN_REXEX, REG_EXTENDED) != 0)
{
printf("%s %s Unable to compile regular expression to validate exact domains\n",
over, cross);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(regcomp(&abp_regex, ABP_DOMAIN_REXEX, REG_EXTENDED) != 0)
{
printf("%s %s Unable to compile regular expression to validate ABP-style domains\n",
over, cross);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(regcomp(&false_positives_regex, FALSE_POSITIVES, REG_EXTENDED | REG_NOSUB) != 0)
{
printf("%s %s Unable to compile regular expression to identify false positives\n",
over, cross);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(regcomp(&abp_css_regex, ABP_CSS_SELECTORS, REG_EXTENDED) != 0)
{
printf("%s %s Unable to compile regular expression to validate ABP-style domains\n",
over, cross);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Begin transaction
if(sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK)
{
printf("%s %s Unable to begin transaction to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Prepare SQL statement
const char *sql = "INSERT INTO gravity (domain, adlist_id) VALUES (?, ?);";
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
{
printf("%s %s Unable to prepare SQL statement to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Bind adlistID
const int adlistID = atoi(adlistIDstr);
if(sqlite3_bind_int(stmt, 2, adlistID) != SQLITE_OK)
{
printf("%s %s Unable to bind adlistID to SQL statement to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Parse list file line by line
char *line = NULL;
size_t lineno = 0;
size_t len = 0;
ssize_t read = 0;
size_t total_read = 0;
int last_progress = 0;
char *invalid_domains_list[MAX_INVALID_DOMAINS] = { NULL };
unsigned int invalid_domains_list_len = 0;
unsigned int exact_domains = 0, abp_domains = 0, invalid_domains = 0;
while((read = getline(&line, &len, fpin)) != -1)
{
// Update total read bytes
total_read += read;
lineno++;
// Remove leading whitespace
// isspace() matches spaces, tabs, form feeds, line breaks,
// carriage returns, and vertical tabs
while(isspace(line[0]))
line++;
// Skip comments
// # is used for comments in HOSTS files
// ! is used for comments in AdBlock-style files
// [ is used for comments in AdGuard-style files and ABP headers
if(line[0] == '#' || line[0] == '!' || line[0] == '[')
continue;
// Remove trailing newline
if(line[read-1] == '\n')
line[--read] = '\0';
// Remove trailing carriage return (Windows)
if(line[read-1] == '\r')
line[--read] = '\0';
// Remove trailing dot (convert FQDN to domain)
if(line[read-1] == '.')
line[--read] = '\0';
// Skip empty lines
if(line[0] == '\0')
continue;
regmatch_t match = { 0 };
// Validate line
if(line[0] != '|' && // <- Not an ABP-style match
regexec(&exact_regex, line, 1, &match, 0) == 0 && // <- Regex match
match.rm_so == 0 && match.rm_eo == read) // <- Match covers entire line
{
// Exact match found
// Append domain to database using prepared statement
if(sqlite3_bind_text(stmt, 1, line, -1, SQLITE_STATIC) != SQLITE_OK)
{
printf("%s %s Unable to bind domain to SQL statement to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_step(stmt) != SQLITE_DONE)
{
printf("%s %s Unable to insert domain into database file %s\n", over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
sqlite3_reset(stmt);
// Increment counter
exact_domains++;
}
else if(line[0] == '|' && // <- ABP-style match
regexec(&abp_regex, line, 1, &match, 0) == 0 && // <- Regex match
match.rm_so == 0 && match.rm_eo == read) // <- Match covers entire line
{
// ABP-style match (see comments above)
// Skip lines containing ABP extended CSS selectors
// ("##", "#!#", "#@#", "#?#") preceded by a letter
// See https://github.com/pi-hole/pi-hole/pull/5247 for
// further information on why this is necessary
if(regexec(&abp_css_regex, line, 1, &match, 0) == 0)
continue;
// Append pattern to database using prepared statement
if(sqlite3_bind_text(stmt, 1, line, -1, SQLITE_STATIC) != SQLITE_OK)
{
printf("%s %s Unable to bind domain to SQL statement to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_step(stmt) != SQLITE_DONE)
{
printf("%s %s Unable to insert domain into database file %s\n", over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
sqlite3_reset(stmt);
abp_domains++;
}
else
{
// No match - add to list of invalid domains
if(invalid_domains_list_len < MAX_INVALID_DOMAINS)
{
// Check if we have this domain already
bool found = false;
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
{
if(strcmp(invalid_domains_list[i], line) == 0)
{
found = true;
break;
}
}
// If not found, check if this is a false
// positive and add it to the list if it is not
if(!found && regexec(&false_positives_regex, line, 0, NULL, 0) != 0)
invalid_domains_list[invalid_domains_list_len++] = strdup(line);
}
invalid_domains++;
}
// Print progress if the file is large enough every 100 lines
if(fsize > PRINT_PROGRESS_THRESHOLD && lineno % 100 == 1)
{
// Calculate progress
const int progress = (int)(100.0*total_read/fsize);
// Print progress if it has changed
if(progress > last_progress)
{
printf("%s %s Processed %i%% of downloaded list", over, info, progress);
fflush(stdout);
last_progress = progress;
}
}
}
// Finalize SQL statement
if(sqlite3_finalize(stmt) != SQLITE_OK)
{
printf("%s %s Unable to finalize SQL statement to insert domains into database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Update database properties
// Are ABP patterns used?
if(abp_domains > 0)
{
sql = "INSERT OR REPLACE INTO info (property,value) VALUES ('abp_domains',1);";
if(sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK)
{
printf("%s %s Unable to update database properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
}
// Update number of domains on this list
sql = "UPDATE adlist SET number = ?, invalid_domains = ? WHERE id = ?;";
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
{
printf("%s %s Unable to prepare SQL statement to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Update date
if(sqlite3_bind_int(stmt, 1, exact_domains) != SQLITE_OK)
{
printf("%s %s Unable to bind number of domains to SQL statement to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_bind_int(stmt, 2, invalid_domains) != SQLITE_OK)
{
printf("%s %s Unable to bind number of invalid domains to SQL statement to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_bind_int(stmt, 3, adlistID) != SQLITE_OK)
{
printf("%s %s Unable to bind adlist ID to SQL statement to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_step(stmt) != SQLITE_DONE)
{
printf("%s %s Unable to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
if(sqlite3_finalize(stmt) != SQLITE_OK)
{
printf("%s %s Unable to finalize SQL statement to update adlist properties in database file %s\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// End transaction
if(sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL) != SQLITE_OK)
{
printf("%s %s Unable to end transaction to insert domains into database file %s (database file may be corrupted)\n",
over, cross, outfile);
fclose(fpin);
sqlite3_close(db);
return EXIT_FAILURE;
}
// Print summary
printf("%s %s Parsed %u exact domains and %u ABP-style domains (ignored %u non-domain entries)\n",
over, tick, exact_domains, abp_domains, invalid_domains);
if(invalid_domains_list_len > 0)
{
puts(" Sample of non-domain entries:");
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
printf(" - \"%s\"\n", invalid_domains_list[i]);
puts("");
}
// Free memory
free(line);
regfree(&exact_regex);
regfree(&abp_regex);
regfree(&false_positives_regex);
regfree(&abp_css_regex);
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
if(invalid_domains_list[i] != NULL)
free(invalid_domains_list[i]);
// Close files
fclose(fpin);
sqlite3_close(db);
// Return success
return EXIT_SUCCESS;
}

13
src/gravity-tools.h Normal file
View File

@@ -0,0 +1,13 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Gravity tools collection prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
int gravity_parseList(const char *infile, const char *outfile, const char *adlistID);

View File

@@ -76,7 +76,7 @@ static int luaB_auxwrap (lua_State *L) {
if (l_unlikely(r < 0)) { /* error? */
int stat = lua_status(co);
if (stat != LUA_OK && stat != LUA_YIELD) { /* error in the coroutine? */
stat = lua_resetthread(co, L); /* close its tbc variables */
stat = lua_closethread(co, L); /* close its tbc variables */
lua_assert(stat != LUA_OK);
lua_xmove(co, L, 1); /* move error message to the caller */
}
@@ -172,7 +172,7 @@ static int luaB_close (lua_State *L) {
int status = auxstatus(L, co);
switch (status) {
case COS_DEAD: case COS_YIELD: {
status = lua_resetthread(co, L);
status = lua_closethread(co, L);
if (status == LUA_OK) {
lua_pushboolean(L, 1);
return 1;

View File

@@ -339,7 +339,7 @@ int luaE_resetthread (lua_State *L, int status) {
}
LUA_API int lua_resetthread (lua_State *L, lua_State *from) {
LUA_API int lua_closethread (lua_State *L, lua_State *from) {
int status;
lua_lock(L);
L->nCcalls = (from) ? getCcalls(from) : 0;
@@ -349,6 +349,14 @@ LUA_API int lua_resetthread (lua_State *L, lua_State *from) {
}
/*
** Deprecated! Use 'lua_closethread' instead.
*/
LUA_API int lua_resetthread (lua_State *L) {
return lua_closethread(L, NULL);
}
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i;
lua_State *L;

View File

@@ -18,10 +18,10 @@
#define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "4"
#define LUA_VERSION_RELEASE "5"
#define LUA_VERSION_RELEASE "6"
#define LUA_VERSION_NUM 504
#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 5)
#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 6)
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
@@ -163,7 +163,8 @@ extern const char lua_ident[];
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
LUA_API int (lua_resetthread) (lua_State *L, lua_State *from);
LUA_API int (lua_closethread) (lua_State *L, lua_State *from);
LUA_API int (lua_resetthread) (lua_State *L); /* Deprecated! */
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);

View File

@@ -32,9 +32,12 @@ ssize_t FTLrecvfrom(int sockfd, void *buf, size_t len, int flags, struct sockadd
// Backup errno value
const int _errno = errno;
// Final error checking (may have failed for some other reason then an
// EINTR = interrupted system call)
if(ret < 0)
// Final error checking. May have failed for some other reason then an
// EINTR = interrupted system call. In that case, log a warning However,
// if the error is EAGAIN, this is not an error, but just a non-blocking
// socket that has no data available or we ran into an (expected)
// timeout. In that case, do not log a warning
if(ret < 0 && errno != EAGAIN)
log_warn("Could not recvfrom() in %s() (%s:%i): %s",
func, file, line, strerror(errno));

View File

@@ -33,8 +33,9 @@ ssize_t FTLsendto(int sockfd, void *buf, size_t len, int flags, const struct soc
const int _errno = errno;
// Final error checking (may have failed for some other reason then an
// EINTR = interrupted system call)
if(ret < 0)
// EINTR = interrupted system call), also ignore EPROTONOSUPPORT (ARP scanning)
// and EPERM + ENOKEY (DHCP probing)
if(ret < 0 && errno != EPROTONOSUPPORT && errno != EPERM && errno != ENOKEY)
log_warn("Could not sendto() in %s() (%s:%i): %s",
func, file, line, strerror(errno));

20
src/tools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,20 @@
# Pi-hole: A black hole for Internet advertisements
# (c) 2020 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# FTL Engine
# /src/tools/CMakeList.txt
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
set(tools_sources
arp-scan.c
arp-scan.h
dhcp-discover.c
dhcp-discover.h
)
add_library(tools OBJECT ${tools_sources})
target_compile_options(tools PRIVATE "${EXTRAWARN}")
target_include_directories(tools PRIVATE ${PROJECT_SOURCE_DIR}/src)

747
src/tools/arp-scan.c Normal file
View File

@@ -0,0 +1,747 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* ARP scanning routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
// Inspired by https://stackoverflow.com/a/39287433 but heavily modified
#include "FTL.h"
#include "arp-scan.h"
#include "log.h"
// get_hardware_address()
#include "dhcp-discover.h"
// sleepms()
#include "timers.h"
// check_capability()
#include "capabilities.h"
#include <linux/capability.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
//htons etc
#include <arpa/inet.h>
// How many threads do we spawn at maximum?
// This is also the limit for interfaces
// we scan for DHCP activity.
#define MAXTHREADS 32
// How many MAC addresses do we store per IP address?
#define MAX_MACS 3
// How many ARP requests do we send per IP address?
#define NUM_SCANS 10
// How long do we wait for ARP replies in each scan [seconds]?
#define ARP_TIMEOUT 1
// Protocol definitions
#define PROTO_ARP 0x0806
#define ETH2_HEADER_LEN 14
#define HW_TYPE 1
#define MAC_LENGTH 6
#define IPV4_LENGTH 4
#define ARP_REQUEST 0x01
#define ARP_REPLY 0x02
#define BUF_SIZE 60
// ARP header struct
// See https://en.wikipedia.org/wiki/Address_Resolution_Protocol#Packet_structure
#pragma pack(push, 1)
struct arp_header {
unsigned short hardware_type;
unsigned short protocol_type;
unsigned char hardware_len;
unsigned char protocol_len;
unsigned short opcode;
unsigned char sender_mac[MAC_LENGTH];
unsigned char sender_ip[IPV4_LENGTH];
unsigned char target_mac[MAC_LENGTH];
unsigned char target_ip[IPV4_LENGTH];
};
#pragma pack(pop)
struct arp_result {
struct device {
unsigned char replied[NUM_SCANS];
unsigned char mac[MAC_LENGTH];
} device[MAX_MACS];
};
struct arp_result_extreme {
struct device_extreme {
// In extreme mode, we can scan up to 10x more often
unsigned char replied[10*NUM_SCANS];
unsigned char mac[MAC_LENGTH];
} device[MAX_MACS];
};
enum status {
STATUS_INITIALIZING = 0,
STATUS_SKIPPED_CIDR_MISMATCH,
STATUS_SCANNING,
STATUS_ERROR,
STATUS_COMPLETE
} __attribute__ ((packed));
struct thread_data {
bool scan_all :1;
bool extreme :1;
char iface[IF_NAMESIZE + 1];
char ipstr[INET_ADDRSTRLEN];
unsigned char mac[MAC_LENGTH];
enum status status;
int dst_cidr;
unsigned int num_scans;
unsigned int total_scans;
size_t result_size;
uint32_t scanned_addresses;
const char *error;
struct ifaddrs *ifa;
union {
struct arp_result_extreme *result_extreme;
struct arp_result *result;
};
struct sockaddr_in src_addr;
struct sockaddr_in dst_addr;
struct sockaddr_in mask;
};
// Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip.
// Iterates over all IP addresses in the range of dst_ip/cidr.
static int send_arps(const int fd, const int ifindex, struct thread_data *thread_data)
{
int err = -1;
unsigned char buffer[BUF_SIZE];
memset(buffer, 0, sizeof(buffer));
// Construct the Ethernet header
struct sockaddr_ll socket_address;
socket_address.sll_family = AF_PACKET;
socket_address.sll_protocol = htons(ETH_P_ARP);
socket_address.sll_ifindex = ifindex;
socket_address.sll_hatype = htons(ARPHRD_ETHER);
socket_address.sll_pkttype = PACKET_BROADCAST;
socket_address.sll_halen = MAC_LENGTH;
socket_address.sll_addr[6] = 0;
socket_address.sll_addr[7] = 0;
struct ethhdr *send_req = (struct ethhdr *) buffer;
struct arp_header *arp_req = (struct arp_header *) (buffer + ETH2_HEADER_LEN);
ssize_t ret;
// Destination is the broadcast address
memset(send_req->h_dest, 0xff, MAC_LENGTH);
// Target MAC is zero (we don't know it)
memset(arp_req->target_mac, 0x00, MAC_LENGTH);
// Source MAC to our own MAC address
memcpy(send_req->h_source, thread_data->mac, MAC_LENGTH);
memcpy(arp_req->sender_mac, thread_data->mac, MAC_LENGTH);
memcpy(socket_address.sll_addr, thread_data->mac, MAC_LENGTH);
// Protocol type is ARP
send_req->h_proto = htons(ETH_P_ARP);
// Create ARP request
arp_req->hardware_type = htons(HW_TYPE);
arp_req->protocol_type = htons(ETH_P_IP);
arp_req->hardware_len = MAC_LENGTH;
arp_req->protocol_len = IPV4_LENGTH;
arp_req->opcode = htons(ARP_REQUEST);
// Copy IP address to arp_req
memcpy(arp_req->sender_ip, &thread_data->src_addr.sin_addr.s_addr, sizeof(thread_data->src_addr.sin_addr.s_addr));
// Loop over all possible IP addresses in the range dst_ip/cidr
// We start at 1 because the first IP address has already been set above
struct in_addr dst_ip = thread_data->dst_addr.sin_addr;
for(unsigned int i = 0; i < (1u << (32 - thread_data->dst_cidr)); i++)
{
// Fill in target IP address
memcpy(arp_req->target_ip, &dst_ip.s_addr, sizeof(dst_ip.s_addr));
#ifdef DEBUG
printf("Sending ARP request for %s@%s\n", inet_ntoa(*dst_ip), iface);
#endif
// Send ARP request
ret = sendto(fd, buffer, 42, 0, (struct sockaddr *) &socket_address, sizeof(socket_address));
if (ret == -1)
{
err = errno;
thread_data->error = strerror(err);
goto out;
}
// Increment IP address
dst_ip.s_addr = htonl(ntohl(dst_ip.s_addr) + 1);
thread_data->scanned_addresses++;
}
err = 0;
out:
return err;
}
static int create_arp_socket(const int ifindex, const char *iface, const char **error)
{
// Create socket for ARP communications
const int arp_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if(arp_socket < 0)
{
*error = strerror(errno);
#ifdef DEBUG
printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, *error);
#endif
return -1;
}
// Bind socket to interface
struct sockaddr_ll sll;
memset(&sll, 0, sizeof(struct sockaddr_ll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
if (bind(arp_socket, (struct sockaddr*) &sll, sizeof(struct sockaddr_ll)) < 0)
{
*error = strerror(errno);
#ifdef DEBUG
printf("Unable to bind socket for ARP communications on interface %s: %s\n", iface, *error);
#endif
close(arp_socket);
return -1;
}
// Set timeout
struct timeval tv;
tv.tv_sec = ARP_TIMEOUT;
tv.tv_usec = 0;
if (setsockopt(arp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
{
*error = strerror(errno);
#ifdef DEBUG
printf("Unable to set timeout for ARP communications on interface %s: %s\n", iface, *error);
#endif
close(arp_socket);
return -1;
}
return arp_socket;
}
static void add_result(struct in_addr *rcv_ip, unsigned char *sender_mac,
struct thread_data *thread_data, const unsigned int scan_id)
{
// Check if we have already found this IP address
uint32_t i = ntohl(rcv_ip->s_addr) - ntohl(thread_data->dst_addr.sin_addr.s_addr);
if(i >= thread_data->result_size)
{
printf("Received IP address %s out of range for interface %s (%u >= %zu)\n",
inet_ntoa(*rcv_ip), thread_data->iface, i, thread_data->result_size);
return;
}
// Save MAC address
unsigned int j = 0;
for(; j < MAX_MACS; j++)
{
unsigned char *mac = thread_data->extreme ?
thread_data->result_extreme[i].device[j].mac :
thread_data->result[i].device[j].mac;
// Check if received MAC is already stored in result[i].device[j].mac
if(memcmp(mac, sender_mac, MAC_LENGTH) == 0)
{
break;
}
// Check if mac is all-zero
if(memcmp(mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0)
{
// Copy MAC address to mac
memcpy(mac, sender_mac, MAC_LENGTH);
break;
}
}
// Memorize that we have received a reply for this IP address
thread_data->extreme ?
thread_data->result_extreme[i].device[j].replied[scan_id]++ :
thread_data->result[i].device[j].replied[scan_id]++;
}
// Read all ARP responses
static ssize_t read_arp(const int fd, struct thread_data *thread_data)
{
ssize_t ret = 0;
unsigned char buffer[BUF_SIZE];
// Read ARP responses
while(ret >= 0)
{
ret = recvfrom(fd, buffer, BUF_SIZE, 0, NULL, NULL);
if (ret == -1)
{
if(errno == EAGAIN)
{
// Timeout
ret = 0;
break;
}
// Error
thread_data->error = strerror(errno);
printf("recvfrom(): %s", thread_data->error);
break;
}
struct ethhdr *rcv_resp = (struct ethhdr *) buffer;
struct arp_header *arp_resp = (struct arp_header *) (buffer + ETH2_HEADER_LEN);
if (ntohs(rcv_resp->h_proto) != PROTO_ARP)
{
#ifdef DEBUG
printf("Not an ARP packet");
#endif
continue;
}
if (ntohs(arp_resp->opcode) != ARP_REPLY)
{
#ifdef DEBUG
printf("Not an ARP reply");
#endif
continue;
}
#ifdef DEBUG
printf("received ARP len=%ld", ret);
#endif
struct in_addr sender_a;
memcpy(&sender_a.s_addr, arp_resp->sender_ip, sizeof(sender_a.s_addr));
#ifdef DEBUG
printf("%-16s %-20s\t%02x:%02x:%02x:%02x:%02x:%02x",
thread_data->iface, inet_ntoa(sender_a),
arp_resp->sender_mac[0],
arp_resp->sender_mac[1],
arp_resp->sender_mac[2],
arp_resp->sender_mac[3],
arp_resp->sender_mac[4],
arp_resp->sender_mac[5]);
#endif
add_result(&sender_a, arp_resp->sender_mac, thread_data, thread_data->num_scans);
}
return ret;
}
// Convert netmask to CIDR
static int netmask_to_cidr(struct in_addr *addr)
{
// Count the number of set bits in an unsigned integer
return __builtin_popcount(addr->s_addr);
}
static const char *get_hostname(const struct in_addr *addr)
{
// Get hostname
struct hostent *he = gethostbyaddr(&addr->s_addr, sizeof(addr->s_addr), AF_INET);
if(he == NULL)
return "N/A";
// Allow at most 24 characters for the hostname
static char hostname[25] = { 0 };
strncpy(hostname, he->h_name, 24);
// Return hostname
return hostname;
}
static void *arp_scan_iface(void *args)
{
// Get thread_data pointer
struct thread_data *thread_data = (struct thread_data*)args;
// Get interface details
struct ifaddrs *ifa = thread_data->ifa;
// Get interface name
const char *iface = thread_data->iface;
// Set interface name as thread name
prctl(PR_SET_NAME, iface, 0, 0, 0);
// Get interface netmask
memcpy(&thread_data->mask, ifa->ifa_netmask, sizeof(thread_data->mask));
// Convert subnet to CIDR
thread_data->dst_cidr = netmask_to_cidr(&thread_data->mask.sin_addr);
// Get interface index
const int ifindex = if_nametoindex(iface);
// Scan only interfaces with CIDR >= 24
if(thread_data->dst_cidr < 24 && !thread_data->scan_all)
{
thread_data->status = STATUS_SKIPPED_CIDR_MISMATCH;
#ifdef DEBUG
printf("Skipped interface %s (%s/%i)\n", iface, thread_data->ipstr, thread_data->dst_cidr);
#endif
pthread_exit(NULL);
}
#ifdef DEBUG
printf("Scanning interface %s (%s/%i)...\n", iface, thread_data->ipstr, thread_data->dst_cidr);
#endif
thread_data->status = STATUS_SCANNING;
// Create socket for ARP communications
const int arp_socket = create_arp_socket(ifindex, iface, &thread_data->error);
// Cannot create socket, likely a permission error
if(arp_socket < 0)
{
thread_data->status = STATUS_ERROR;
pthread_exit(NULL);
}
// Get hardware address of client machine
get_hardware_address(arp_socket, iface, thread_data->mac);
// Define destination IP address by masking source IP with netmask
thread_data->dst_addr.sin_addr.s_addr = thread_data->src_addr.sin_addr.s_addr & thread_data->mask.sin_addr.s_addr;
// Allocate memory for ARP response buffer
const size_t arp_result_len = 1 << (32 - thread_data->dst_cidr);
thread_data->result_size = arp_result_len;
if(thread_data->extreme)
{
// Allocate extreme memory for ARP response buffer
struct arp_result_extreme *result = calloc(arp_result_len, sizeof(struct arp_result_extreme));
if(result == NULL)
{
// Memory allocation failed due to insufficient memory being
// available
thread_data->status = STATUS_ERROR;
thread_data->error = strerror(ENOMEM);
pthread_exit(NULL);
}
thread_data->result_extreme = result;
}
else
{
// Allocate memory for ARP response buffer
struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result));
if(result == NULL)
{
// Memory allocation failed due to insufficient memory being
// available
thread_data->status = STATUS_ERROR;
thread_data->error = strerror(ENOMEM);
pthread_exit(NULL);
}
thread_data->result = result;
}
for(thread_data->num_scans = 0; thread_data->num_scans < thread_data->total_scans; thread_data->num_scans++)
{
#ifdef DEBUG
printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/thread_data->total_scans);
#endif
// Send ARP requests to all IPs in subnet
if(send_arps(arp_socket, ifindex, thread_data) != 0)
{
thread_data->status = STATUS_ERROR;
break;
}
// Read ARP responses
if(read_arp(arp_socket, thread_data) != 0)
{
thread_data->status = STATUS_ERROR;
break;
}
}
// Close socket
if(close(arp_socket) != 0)
thread_data->status = STATUS_ERROR;
if(thread_data->status != STATUS_ERROR)
thread_data->status = STATUS_COMPLETE;
pthread_exit(NULL);
}
static void print_results(struct thread_data *thread_data)
{
if(thread_data->status == STATUS_SKIPPED_CIDR_MISMATCH)
{
printf("Skipped interface %s (%s/%i) because of too large network (use -a or -x to force scanning this interface)\n\n",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr);
return;
}
if(thread_data->status == STATUS_ERROR)
{
printf("Error scanning interface %s (%s/%i)%s%s\n\n",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr,
thread_data->error ? ": " : "", thread_data->error ? thread_data->error : "");
return;
}
// Check if there are any results
bool any_replies = false;
for(unsigned int i = 0; i < thread_data->result_size; i++)
for(unsigned int j = 0; j < MAX_MACS; j++)
for(unsigned int k = 0; k < thread_data->total_scans; k++)
if(thread_data->extreme)
{
if(thread_data->result_extreme[i].device[j].replied[k])
{
any_replies = true;
break;
}
}
else
{
if(thread_data->result[i].device[j].replied[k])
{
any_replies = true;
break;
}
}
// Exit early if there are no results
if(!any_replies)
{
printf("No devices replied on interface %s (%s/%i)\n\n",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr);
return;
}
// If there is at least one result, print header
printf("ARP scan on interface %s (%s/%i) finished\n",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr);
printf("%-16s %-16s %-24s %-17s %s\n",
"IP address", "Interface", "Hostname", "MAC address", "Reply rate");
// Add our own IP address to the results so IP conflicts can be detected
// (our own IP address is not included in the ARP scan)
for(unsigned int i = 0; i < thread_data->total_scans; i++)
add_result(&thread_data->src_addr.sin_addr, thread_data->mac, thread_data, i);
// Print results
for(unsigned int i = 0; i < thread_data->result_size; i++)
{
unsigned int j = 0, replied_devices = 0;
// Print MAC addresses
for(j = 0; j < MAX_MACS; j++)
{
// Check if result[i].mac[j] is all-zero, if so, skip this entry
unsigned char *mac = thread_data->extreme ?
thread_data->result_extreme[i].device[j].mac :
thread_data->result[i].device[j].mac;
if(memcmp(mac, "\x00\x00\x00\x00\x00\x00", 6) == 0)
break;
bool replied = false;
unsigned char replies = 0u;
unsigned char multiple_replies = 0;
const unsigned char *rp = thread_data->extreme ?
thread_data->result_extreme[i].device[j].replied :
thread_data->result[i].device[j].replied;
// Check if IP address replied
for(unsigned int k = 0; k < thread_data->total_scans; k++)
{
replied |= rp[k] > 0;
replies += rp[k] > 0 ? 1 : 0;
multiple_replies += rp[k] > 1;
}
if(!replied)
continue;
// Check if IP address replied multiple times from different MAC address
replied_devices++;
// Convert IP address to string
struct in_addr ip = { 0 };
ip.s_addr = htonl(ntohl(thread_data->dst_addr.sin_addr.s_addr) + i);
inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN);
// Print MAC address
printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x %3u %%\n",
thread_data->ipstr, thread_data->iface,
get_hostname(&ip),
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
replies * 100 / thread_data->total_scans);
#ifdef DEBUG
for(unsigned int k = 0; k < thread_data->total_scans; k++)
printf(" %s", rp[k] > 0 ? "X" : "-");
#endif
if(multiple_replies > 0)
printf("INFO: Received multiple replies from %02x:%02x:%02x:%02x:%02x:%02x for %s in %i scan%s\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
thread_data->ipstr, multiple_replies, multiple_replies > 1 ? "s" : "");
}
// Print warning if we received multiple replies
if(replied_devices > 1)
printf("WARNING: Received replies for %s from %u devices\n",
thread_data->ipstr, replied_devices);
}
putc('\n', stdout);
}
int run_arp_scan(const bool scan_all, const bool extreme_mode)
{
// Check if we are capable of sending ARP packets
if(!check_capability(CAP_NET_RAW))
{
puts("Error: Insufficient permissions or capabilities (needs CAP_NET_RAW). Try running as root (sudo)");
return EXIT_FAILURE;
}
puts("Discovering IPv4 hosts on the network using the Address Resolution Protocol (ARP)...\n");
// Get interface names for available interfaces on this machine
// and launch a thread for each one
pthread_t scanthread[MAXTHREADS];
pthread_attr_t attr;
// Initialize thread attributes object with default attribute values
pthread_attr_init(&attr);
struct ifaddrs *addrs, *tmp;
getifaddrs(&addrs);
tmp = addrs;
// Loop until there are no more interfaces available
// or we reached the maximum number of threads
unsigned int tid = 0;
struct thread_data thread_data[MAXTHREADS] = {0};
while(tmp != NULL && tid < MAXTHREADS)
{
// Create a thread for interfaces of type AF_INET
if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET)
{
// Skip interface scan if ...
// - interface is not up
// - ARP is not supported
// - interface is loopback net
if(!(tmp->ifa_flags & IFF_UP) ||
(tmp->ifa_flags & IFF_NOARP) ||
(tmp->ifa_flags & IFF_LOOPBACK))
{
tmp = tmp->ifa_next;
continue;
}
thread_data[tid].ifa = tmp;
strncpy(thread_data[tid].iface, tmp->ifa_name, sizeof(thread_data[tid].iface) - 1);
// Get interface IPv4 address
memcpy(&thread_data[tid].src_addr, tmp->ifa_addr, sizeof(thread_data[tid].src_addr));
inet_ntop(AF_INET, &thread_data[tid].src_addr.sin_addr, thread_data[tid].ipstr, INET_ADDRSTRLEN);
thread_data[tid].extreme = extreme_mode;
thread_data[tid].scan_all = scan_all || extreme_mode;
thread_data[tid].total_scans = extreme_mode ? 10*NUM_SCANS : NUM_SCANS;
// Always skip the loopback interface
if(thread_data[tid].src_addr.sin_addr.s_addr != htonl(INADDR_LOOPBACK))
{
// Create thread
if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, &thread_data[tid] ) != 0)
{
printf("Unable to launch thread for interface %s, skipping...\n",
tmp->ifa_name);
}
// Increase thread ID
tid++;
}
}
// Advance to the next interface
tmp = tmp->ifa_next;
}
// Wait for all threads to finish scanning
bool all_done = false;
unsigned int progress = 0;
while(!all_done)
{
all_done = true;
uint64_t num_scans = 0, total_scans = 0;
for(unsigned int i = 0; i < tid; i++)
{
if(thread_data[i].status == STATUS_INITIALIZING ||
thread_data[i].status == STATUS_SCANNING)
{
// At least one thread is still scanning
all_done = false;
}
if(thread_data[i].status == STATUS_SCANNING ||
thread_data[i].status == STATUS_COMPLETE)
{
// Also add up scans for completed threads
num_scans += thread_data[i].scanned_addresses;
total_scans += thread_data[i].total_scans * thread_data[i].result_size;
}
}
if(!all_done)
{
// Calculate progress (total number of scans / total number of addresses)
// We add 1 to total_scans to avoid division by zero
const unsigned int new_progress = 100 * num_scans / (total_scans + 1);
if(new_progress > progress)
{
// Print progress
printf(" %u%% ", new_progress);
// Update progress
progress = new_progress;
}
putc('.', stdout);
// Flush stdout
fflush(stdout);
// Sleep for 1 second
sleepms(1000);
}
}
puts("100%\n");
// Wait for all threads to join back with us
for(unsigned int i = 0; i < tid; i++)
pthread_join(scanthread[i], NULL);
// Destroy the thread attributes object, since we are done with it
pthread_attr_destroy(&attr);
// Free linked-list of interfaces on this client
freeifaddrs(addrs);
// Loop over thread results and print them
for(unsigned int i = 0; i < tid; i++)
{
// Print results
print_results(&thread_data[i]);
// Free allocated memory
if(thread_data[i].result != NULL)
free(thread_data[i].result);
}
return EXIT_SUCCESS;
}

16
src/tools/arp-scan.h Normal file
View File

@@ -0,0 +1,16 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* ARP scanning prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef ARP_SCAN_H
#define ARP_SCAN_H
int run_arp_scan(const bool scan_all, const bool extreme_mode);
#endif // ARP_SCAN_H

View File

@@ -19,6 +19,8 @@
#include "config/config.h"
// cli_bold(), etc.
#include "args.h"
// check_capability()
#include "capabilities.h"
#include <sys/time.h>
// SIOCGIFHWADDR
@@ -60,6 +62,15 @@
// Global lock used by all threads
static pthread_mutex_t lock;
static void __attribute__((format(gnu_printf, 1, 2))) printf_locked(const char *format, ...)
{
va_list args;
va_start(args, format);
pthread_mutex_lock(&lock);
vprintf(format, args);
pthread_mutex_unlock(&lock);
va_end(args);
}
extern const struct opttab_t {
char *name;
@@ -84,17 +95,17 @@ static int create_dhcp_socket(const char *iname)
const int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock < 0)
{
printf("Error: Could not create socket for interface %s!!\n", iname);
printf_locked("Error: Could not create socket for interface %s!\n", iname);
return -1;
}
#ifdef DEBUG
printf("DHCP socket: %d\n", sock);
printf_locked("DHCP socket: %d\n", sock);
#endif
// set the reuse address flag so we don't get errors when restarting
if(setsockopt(sock,SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag))<0)
{
printf("Error: Could not set reuse address option on DHCP socket (%s)!\n", iname);
printf_locked("Error: Could not set reuse address option on DHCP socket (%s)!\n", iname);
close(sock);
return -1;
}
@@ -102,7 +113,7 @@ static int create_dhcp_socket(const char *iname)
// set the broadcast option - we need this to listen to DHCP broadcast messages
if(setsockopt(sock, SOL_SOCKET,SO_BROADCAST, (char *)&flag, sizeof flag) < 0)
{
printf("Error: Could not set broadcast option on DHCP socket (%s)!\n", iname);
printf_locked("Error: Could not set broadcast option on DHCP socket (%s)!\n", iname);
close(sock);
return -1;
}
@@ -111,8 +122,8 @@ static int create_dhcp_socket(const char *iname)
strncpy(interface.ifr_ifrn.ifrn_name, iname, IFNAMSIZ-1);
if(setsockopt(sock,SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface)) < 0)
{
printf("Error: Could not bind socket to interface %s (%s)\n ---> Check your privileges (run with sudo)!\n",
iname, strerror(errno));
printf_locked("Error: Could not bind socket to interface %s (%s)\n",
iname, strerror(errno));
close(sock);
return -1;
}
@@ -120,8 +131,8 @@ static int create_dhcp_socket(const char *iname)
// bind the socket
if(bind(sock, (struct sockaddr *)&dhcp_socket, sizeof(dhcp_socket)) < 0)
{
printf("Error: Could not bind to DHCP socket (interface %s, port %d, %s)\n ---> Check your privileges (run with sudo)!\n",
iname, DHCP_CLIENT_PORT, strerror(errno));
printf_locked("Error: Could not bind to DHCP socket (interface %s, port %d, %s)\n",
iname, DHCP_CLIENT_PORT, strerror(errno));
close(sock);
return -1;
}
@@ -130,120 +141,118 @@ static int create_dhcp_socket(const char *iname)
}
// determines hardware address on client machine
static int get_hardware_address(const int sock, const char *iname, unsigned char *mac)
int get_hardware_address(const int sock, const char *iname, unsigned char *mac)
{
struct ifreq ifr;
strncpy((char *)&ifr.ifr_name, iname, sizeof(ifr.ifr_name)-1);
// try and grab hardware address of requested interface
int ret = 0;
if((ret = ioctl(sock, SIOCGIFHWADDR, &ifr)) < 0){
printf(" Error: Could not get hardware address of interface '%s' (socket %d, error: %s)",
iname, sock, strerror(errno));
if((ret = ioctl(sock, SIOCGIFHWADDR, &ifr)) < 0)
{
printf_locked(" Error: Could not get hardware address of interface %s: %s\n", iname, strerror(errno));
return false;
}
memcpy(&mac[0], &ifr.ifr_hwaddr.sa_data, 6);
#ifdef DEBUG
printf("Hardware address of this interface: ");
printf_locked("Hardware address of this interface: ");
for (uint8_t i = 0; i < 6; ++i)
printf("%02x%s", mac[i], i < 5 ? ":" : "");
printf("\n");
printf_locked("%02x%s", mac[i], i < 5 ? ":" : "");
printf_locked("\n");
#endif
return true;
}
typedef struct dhcp_packet_struct
struct dhcp_packet_data
{
u_int8_t op; // packet type
u_int8_t htype; // type of hardware address for this machine (Ethernet, etc)
u_int8_t hlen; // length of hardware address (of this machine)
u_int8_t hops; // hops
u_int32_t xid; // random transaction id number - chosen by this machine
u_int16_t secs; // seconds used in timing
u_int16_t flags; // flags
struct in_addr ciaddr; // IP address of this machine (if we already have one)
struct in_addr yiaddr; // IP address of this machine (offered by the DHCP server)
struct in_addr siaddr; // IP address of DHCP server
struct in_addr giaddr; // IP address of DHCP relay
unsigned char chaddr [MAX_DHCP_CHADDR_LENGTH]; // hardware address of this machine
char sname [MAX_DHCP_SNAME_LENGTH]; // name of DHCP server
char file [MAX_DHCP_FILE_LENGTH]; // boot file name (used for diskless booting?)
char options[MAX_DHCP_OPTIONS_LENGTH]; // options
} dhcp_packet_data;
#define BOOTREQUEST 1
#define BOOTREPLY 2
u_int8_t op; // packet type
u_int8_t htype; // type of hardware address for this machine (Ethernet, etc)
u_int8_t hlen; // length of hardware address (of this machine)
u_int8_t hops; // hops
u_int32_t xid; // random transaction id number - chosen by this machine
u_int16_t secs; // seconds used in timing
u_int16_t flags; // flags
struct in_addr ciaddr; // IP address of this machine (if we already have one)
struct in_addr yiaddr; // IP address of this machine (offered by the DHCP server)
struct in_addr siaddr; // IP address of DHCP server
struct in_addr giaddr; // IP address of DHCP relay
unsigned char chaddr [MAX_DHCP_CHADDR_LENGTH]; // hardware address of this machine
char sname [MAX_DHCP_SNAME_LENGTH]; // name of DHCP server
char file [MAX_DHCP_FILE_LENGTH]; // boot file name (used for diskless booting?)
char options[MAX_DHCP_OPTIONS_LENGTH]; // options
};
// sends a DHCPDISCOVER message to the specified in an attempt to find DHCP servers
static bool send_dhcp_discover(const int sock, const uint32_t xid, const char *iface, unsigned char *mac, const in_addr_t addr)
static bool send_dhcp_discover(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
{
dhcp_packet_data discover_packet;
struct dhcp_packet_data discover_packet = { 0 };
// clear the packet data structure
memset(&discover_packet, 0, sizeof(discover_packet));
// Boot request flag (backward compatible with BOOTP servers)
discover_packet.op = 1; // BOOTREQUEST
// boot request flag (backward compatible with BOOTP servers)
discover_packet.op = BOOTREQUEST;
// Hardware address type
discover_packet.htype = 1; // ETHERNET_HARDWARE_ADDRESS
// hardware address type
discover_packet.htype = 1; // ETHERNET_HARDWARE_ADDRESS;
// length of our hardware address
discover_packet.hlen = 6; // ETHERNET_HARDWARE_ADDRESS_LENGTH;
// Length of our hardware address
discover_packet.hlen = 6; // ETHERNET_HARDWARE_ADDRESS_LENGTH
discover_packet.hops = 0;
// transaction id is supposed to be random
// Transaction id is supposed to be random
discover_packet.xid = htonl(xid);
//ntohl(discover_packet.xid);
discover_packet.secs = 0x00;
// tell server it should broadcast its response
// Tell server it should broadcast its response
discover_packet.flags = htons(32768); // DHCP_BROADCAST_FLAG
// our hardware address
// Our hardware address
memcpy(discover_packet.chaddr, mac, 6);
// first four bytes of options field is magic cookie (as per RFC 2132)
// First four bytes of options field are the magic cookie (as per RFC 2132)
discover_packet.options[0] = '\x63';
discover_packet.options[1] = '\x82';
discover_packet.options[2] = '\x53';
discover_packet.options[3] = '\x63';
// DHCP message type is embedded in options field
discover_packet.options[4] = 53; // DHCP message type option identifier
discover_packet.options[5] = '\x01'; // DHCP message option length in bytes
discover_packet.options[6] = 1; // DHCP message type code for DHCPDISCOVER
discover_packet.options[4] = 53; // DHCP message type option identifier
discover_packet.options[5] = 1; // DHCP message option length in bytes
discover_packet.options[6] = 1; // DHCP message type code for DHCPDISCOVER
// Place end option at the end of the options
discover_packet.options[7] = 255;
// send the DHCPDISCOVER packet to the specified address
struct sockaddr_in target;
// Send the DHCPDISCOVER packet to the specified address
struct sockaddr_in target = { 0 };
target.sin_family = AF_INET;
target.sin_port = htons(DHCP_SERVER_PORT);
target.sin_addr.s_addr = addr;
memset(&target.sin_zero, 0, sizeof(target.sin_zero));
target.sin_addr.s_addr = INADDR_BROADCAST;
#ifdef DEBUG
printf("Sending DHCPDISCOVER on interface %s:%s ...\n", iface, inet_ntoa(target.sin_addr));
printf("DHCPDISCOVER XID: %lu (0x%X)\n", (unsigned long) ntohl(discover_packet.xid), ntohl(discover_packet.xid));
printf("DHCDISCOVER ciaddr: %s\n", inet_ntoa(discover_packet.ciaddr));
printf("DHCDISCOVER yiaddr: %s\n", inet_ntoa(discover_packet.yiaddr));
printf("DHCDISCOVER siaddr: %s\n", inet_ntoa(discover_packet.siaddr));
printf("DHCDISCOVER giaddr: %s\n", inet_ntoa(discover_packet.giaddr));
printf_locked("Sending DHCPDISCOVER on interface %s@%s ... \n", inet_ntoa(target.sin_addr), iface);
printf_locked("DHCPDISCOVER XID: %lu (0x%X)\n", (unsigned long) ntohl(discover_packet.xid), ntohl(discover_packet.xid));
printf_locked("DHCDISCOVER ciaddr: %s\n", inet_ntoa(discover_packet.ciaddr));
printf_locked("DHCDISCOVER yiaddr: %s\n", inet_ntoa(discover_packet.yiaddr));
printf_locked("DHCDISCOVER siaddr: %s\n", inet_ntoa(discover_packet.siaddr));
printf_locked("DHCDISCOVER giaddr: %s\n", inet_ntoa(discover_packet.giaddr));
#endif
// send the DHCPDISCOVER packet
const int bytes = sendto(sock, (char *)&discover_packet, sizeof(discover_packet), 0, (struct sockaddr *)&target,sizeof(target));
if (bytes < 0)
const int bytes = sendto(sock, (char *)&discover_packet, sizeof(discover_packet), 0, (struct sockaddr *)&target, sizeof(target));
if(bytes < 0)
{
printf("Error: Could not send DHCPDISCOVER packet on interface %s (error: %s)\n\n", iface, strerror(errno));
// strerror() returns "Required key not available" for ENOKEY
// which is not helpful at all so we substitute a more
// meaningful error message for ENOKEY returned by wireguard interfaces
// (see https://www.wireguard.com/papers/wireguard.pdf, page 5)
const char *error = errno == ENOKEY ? "No route to host (no such peer available)" : strerror(errno);
printf_locked("Error: Could not send DHCPDISCOVER to %s@%s: %s\n",
inet_ntoa(target.sin_addr), iface, error);
return false;
}
#ifdef DEBUG
printf("Sent %d bytes\n", bytes);
#endif
return bytes > 0;
#ifdef DEBUG
printf_locked("Sent %d bytes\n", bytes);
#endif
return true;
}
#ifdef TEST_OPT_249
@@ -259,7 +268,7 @@ static void gen_249_test_data(dhcp_packet_data *offer_packet)
#endif
// adds a DHCP OFFER to list in memory
static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_packet)
static void print_dhcp_offer(struct in_addr source, struct dhcp_packet_data *offer_packet)
{
if(offer_packet == NULL)
return;
@@ -329,8 +338,9 @@ static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_pack
// We may need to escape this, buffer size: 4
// chars per control character plus room for
// possible "(empty)"
char *buffer = calloc(4*optlen + 9, sizeof(char));
binbuf_to_escaped_C_literal(&offer_packet->options[x], optlen, buffer, sizeof(buffer));
const size_t bufsiz = 4*optlen + 9;
char *buffer = calloc(bufsiz, sizeof(char));
binbuf_to_escaped_C_literal(&offer_packet->options[x], optlen, buffer, bufsiz);
printf("%s: \"%s\"\n", opttab[i].name, buffer);
free(buffer);
}
@@ -469,15 +479,15 @@ static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_pack
if(cidr == 0)
{
// default route (0.0.0.0/0)
printf(" %u: uefault via %u.%u.%u.%u\n", i,
router[0], router[1], router[2], router[3]);
printf(" %u: default via %u.%u.%u.%u\n\n", i,
router[0], router[1], router[2], router[3]);
}
else
{
// specific route
printf(" %u: %u.%u.%u.%u/%u via %u.%u.%u.%u\n", i,
addr[0], addr[1], addr[2], addr[3], cidr,
router[0], router[1], router[2], router[3]);
addr[0], addr[1], addr[2], addr[3], cidr,
router[0], router[1], router[2], router[3]);
}
}
}
@@ -523,15 +533,14 @@ static bool receive_dhcp_packet(void *buffer, int buffer_size, const char *iface
address_size = sizeof(struct sockaddr_in);
recv_result = recvfrom(sock, (char *)buffer, buffer_size, 0, (struct sockaddr *)address, &address_size);
pthread_mutex_lock(&lock);
printf("* Received %d bytes on %s%s%s @ %s\n",
recv_result, cli_bold(), iface, cli_normal(), inet_ntoa(address->sin_addr));
printf_locked("\n* Received %d bytes from %s @ %s\n", recv_result, inet_ntoa(address->sin_addr), iface);
#ifdef DEBUG
printf(" after waiting for %f seconds\n", difftime(time(NULL), start_time));
printf_locked(" after waiting for %f seconds\n", difftime(time(NULL), start_time));
#endif
// Return on error
if(recv_result == -1){
printf(" recvfrom() failed on %s, error: %s\n", iface, strerror(errno));
if(recv_result == -1)
{
printf_locked(" recvfrom() failed on %s, error: %s\n", iface, strerror(errno));
return false;
}
@@ -539,9 +548,9 @@ static bool receive_dhcp_packet(void *buffer, int buffer_size, const char *iface
}
// waits for a DHCPOFFER message from one or more DHCP servers
static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
static void get_dhcp_offer(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
{
dhcp_packet_data offer_packet;
struct dhcp_packet_data offer_packet;
struct sockaddr_in source;
unsigned int responses = 0;
unsigned int valid_responses = 0;
@@ -569,7 +578,7 @@ static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface
if(ntohl(offer_packet.xid) != xid)
{
printf(" DHCPOFFER XID (%lu) does not match our DHCPDISCOVER XID (%lu) - ignoring packet (not for us)\n",
(unsigned long) ntohl(offer_packet.xid), (unsigned long) xid);
(unsigned long) ntohl(offer_packet.xid), (unsigned long) xid);
pthread_mutex_unlock(&lock);
continue;
@@ -648,7 +657,11 @@ static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface
else
printf("DHCP packets received on %s%s%s: %u (%u seen for other machines)\n",
cli_bold(), iface, cli_normal(), valid_responses, responses);
return true;
#ifdef DEBUG
printf(" Responses seen while scanning: %d\n", responses);
printf(" Responses meant for this machine: %d\n\n", valid_responses);
#endif
}
static void *dhcp_discover_iface(void *args)
@@ -664,7 +677,7 @@ static void *dhcp_discover_iface(void *args)
// Cannot create socket, likely a permission error
if(dhcp_socket < 0)
pthread_exit(NULL);
goto end_dhcp_discover_iface;
// get hardware address of client machine
unsigned char mac[MAX_DHCP_CHADDR_LENGTH] = { 0 };
@@ -674,32 +687,31 @@ static void *dhcp_discover_iface(void *args)
srand((unsigned int)time(NULL));
const uint32_t xid = (uint32_t)random();
if(strcmp(iface, "lo") == 0)
{
// Probe a local server listening on this interface
// Send DHCPDISCOVER packet to interface address
struct sockaddr_in ifaddr = { 0 };
memcpy(&ifaddr, ((struct ifaddrs*)args)->ifa_addr, sizeof(ifaddr));
send_dhcp_discover(dhcp_socket, xid, iface, mac, ifaddr.sin_addr.s_addr);
}
else
{
// Probe distant servers
// Send DHCPDISCOVER packet to broadcast address
send_dhcp_discover(dhcp_socket, xid, iface, mac, INADDR_BROADCAST);
}
// Probe servers on this interface
if(!send_dhcp_discover(dhcp_socket, xid, iface, mac))
goto end_dhcp_discover_iface;
// wait for a DHCPOFFER packet
get_dhcp_offer(dhcp_socket, xid, iface, mac);
// close socket we created
close(dhcp_socket);
end_dhcp_discover_iface:
// Close socket if we created one
if(dhcp_socket > 0)
close(dhcp_socket);
pthread_exit(NULL);
}
int run_dhcp_discover(void)
{
// Check if we are capable of binding to port 67 (DHCP)
// DHCP uses normal UDP datagrams, so we cdon't need CAP_NET_RAW
if(!check_capability(CAP_NET_BIND_SERVICE))
{
puts("Error: Insufficient permissions or capabilities (needs CAP_NET_BIND_SERVICE). Try running as root (sudo)");
return EXIT_FAILURE;
}
// Disable terminal output during config config file parsing
log_ctrl(false, false);
// Process pihole-FTL.conf to get gravity.db
@@ -708,7 +720,7 @@ int run_dhcp_discover(void)
log_ctrl(false, true);
printf("Scanning all your interfaces for DHCP servers\n");
printf("Timeout: %d seconds\n\n", DHCPOFFER_TIMEOUT);
printf("Timeout: %d seconds\n", DHCPOFFER_TIMEOUT);
// Get interface names for available interfaces on this machine
// and launch a thread for each one
@@ -735,13 +747,27 @@ int run_dhcp_discover(void)
int tid = 0;
while(tmp != NULL && tid < MAXTHREADS)
{
// Create a thread for interfaces of type AF_PACKET
if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET)
// Create a thread for interfaces of type AF_INET (IPv4)
if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET)
{
// Skip interface scan if ...
// - interface is not up
// - broadcast is not supported
// - interface is loopback net
if(!(tmp->ifa_flags & IFF_UP) ||
!(tmp->ifa_flags & IFF_BROADCAST) ||
tmp->ifa_flags & IFF_LOOPBACK)
{
tmp = tmp->ifa_next;
continue;
}
// Create a probing thread for this interface
if(pthread_create(&scanthread[tid], &attr, dhcp_discover_iface, tmp ) != 0)
{
printf("Unable to launch thread for interface %s, skipping...\n",
tmp->ifa_name);
printf_locked("Unable to launch thread for interface %s, skipping...",
tmp->ifa_name);
tmp = tmp->ifa_next;
continue;
}

View File

@@ -12,5 +12,6 @@
#define DHCP_DISCOVER_H
int run_dhcp_discover(void);
int get_hardware_address(const int sock, const char *iname, unsigned char *mac);
#endif // DHCP_DISCOVER_H

View File

@@ -1139,7 +1139,7 @@
@test "Embedded SQLite3 shell available and functional" {
run bash -c './pihole-FTL sqlite3 -help'
printf "%s\n" "${lines[@]}"
[[ ${lines[0]} == "Usage: sqlite3 [OPTIONS] FILENAME [SQL]" ]]
[[ ${lines[0]} == "Usage: sqlite3 [OPTIONS] [FILENAME [SQL]]" ]]
}
@test "Embedded SQLite3 shell is called for .db file" {