diff --git a/FTL.h b/FTL.h index 0f457aff..34108199 100644 --- a/FTL.h +++ b/FTL.h @@ -72,6 +72,9 @@ // Default -60 (one minute before a full hour) #define GCdelay (-60) +// How many client connection do we accept at once? +#define MAXCONNS 20 + // Static structs typedef struct { const char* conf; @@ -207,7 +210,6 @@ typedef struct { enum { DATABASE_WRITE_TIMER, EXIT_TIMER }; enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; -enum { SOCKET }; enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED, DNSSEC_UNKNOWN }; // Used to check memory integrity in various structs @@ -257,3 +259,4 @@ bool DBdeleteoldqueries; bool rereadgravity; long int lastDBimportedtimestamp; bool ipv4telnet, ipv6telnet; +bool istelnet[MAXCONNS]; diff --git a/Makefile b/Makefile index 35367075..e662a53c 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -DEPS = FTL.h routines.h version.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o +DEPS = FTL.h routines.h api.h version.h +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o msgpack.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') diff --git a/api.c b/api.c new file mode 100644 index 00000000..dbc6add3 --- /dev/null +++ b/api.c @@ -0,0 +1,1245 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2017 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API Implementation +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "api.h" +#include "version.h" + +#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + +/* qsort comparision function (count field), sort ASC */ +int cmpasc(const void *a, const void *b) +{ + int *elem1 = (int*)a; + int *elem2 = (int*)b; + + if (elem1[1] < elem2[1]) + return -1; + else if (elem1[1] > elem2[1]) + return 1; + else + return 0; +} + +// qsort subroutine, sort DESC +int cmpdesc(const void *a, const void *b) +{ + int *elem1 = (int*)a; + int *elem2 = (int*)b; + + if (elem1[1] > elem2[1]) + return -1; + else if (elem1[1] < elem2[1]) + return 1; + else + return 0; +} + +void getStats(int *sock) +{ + int blocked = counters.blocked + counters.wildcardblocked; + int total = counters.queries - counters.invalidqueries; + float percentage = 0.0; + + // Avoid 1/0 condition + if(total > 0) + percentage = 1e2*blocked/total; + + // Send domains being blocked + if(istelnet[*sock]) { + switch(blockingstatus) { + case 0: // Blocking disabled + ssend(*sock, "domains_being_blocked N/A\n"); + break; + default: // Blocking enabled or unknown + ssend(*sock, "domains_being_blocked %i\n", counters.gravity); + break; + } + + } + else + pack_int32(*sock, counters.gravity); + + // unique_clients: count only clients that have been active within the most recent 24 hours + int i, activeclients = 0; + for(i=0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + if(clients[i].count > 0) + activeclients++; + } + + if(istelnet[*sock]) { + ssend(*sock, "dns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", + total, blocked, percentage); + ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", + counters.domains, counters.forwardedqueries, counters.cached); + ssend(*sock, "clients_ever_seen %i\n", counters.clients); + ssend(*sock, "unique_clients %i\n", activeclients); + } + else + { + pack_int32(*sock, total); + pack_int32(*sock, blocked); + pack_float(*sock, percentage); + pack_int32(*sock, counters.domains); + pack_int32(*sock, counters.forwardedqueries); + pack_int32(*sock, counters.cached); + pack_int32(*sock, counters.clients); + pack_int32(*sock, activeclients); + } + + // Send status + if(istelnet[*sock]) { + switch(blockingstatus) { + case 0: // Blocking disabled + ssend(*sock, "status disabled\n"); + break; + case 1: // Blocking enabled + ssend(*sock, "status enabled\n"); + break; + default: // Unknown status + ssend(*sock, "status unknown\n"); + break; + } + } + else + pack_uint8(*sock, blockingstatus); + + if(debugclients) + logg("Sent stats data to client, ID: %i", *sock); +} + +void getOverTime(int *sock) +{ + int i, j = 9999999; + + // Start with the first non-empty overTime slot + for(i=0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(overTime[i].total > 0 || overTime[i].blocked > 0) + { + j = i; + break; + } + } + + if(istelnet[*sock]) + { + for(i = j; i < counters.overTime; i++) + { + ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); + } + } + else + { + // We can use the map16 type because there should only be about 288 time slots (TIMEFRAME set to "yesterday") + // and map16 can hold up to (2^16)-1 = 65535 pairs + + // Send domains over time + pack_map16_start(*sock, (uint16_t) (counters.overTime - j)); + for(i = j; i < counters.overTime; i++) { + pack_int32(*sock, overTime[i].timestamp); + pack_int32(*sock, overTime[i].total); + } + + // Send ads over time + pack_map16_start(*sock, (uint16_t) (counters.overTime - j)); + for(i = j; i < counters.overTime; i++) { + pack_int32(*sock, overTime[i].timestamp); + pack_int32(*sock, overTime[i].blocked); + } + } + + if(debugclients) + logg("Sent overTime data to client, ID: %i", *sock); +} + +void getTopDomains(char *client_message, int *sock) +{ + int i, temparray[counters.domains][2], count=10, num; + bool blocked, audit = false, asc = false; + + blocked = command(client_message, ">top-ads"); + + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Match both top-domains and top-ads + // example: >top-domains (15) + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + count = num; + } + + // Apply Audit Log filtering? + // example: >top-domains for audit + if(command(client_message, " for audit")) + audit = true; + + // Sort in ascending order? + // example: >top-domains asc + if(command(client_message, " asc")) + asc = true; + + for(i=0; i < counters.domains; i++) + { + validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); + temparray[i][0] = i; + if(blocked) + temparray[i][1] = domains[i].blockedcount; + else + // Count only permitted queries + temparray[i][1] = (domains[i].count - domains[i].blockedcount); + } + + // Sort temporary array + if(asc) + qsort(temparray, counters.domains, sizeof(int[2]), cmpasc); + else + qsort(temparray, counters.domains, sizeof(int[2]), cmpdesc); + + + // Get filter + char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); + bool showpermitted = true, showblocked = true; + if(filter != NULL) + { + if((strcmp(filter, "permittedonly")) == 0) + showblocked = false; + else if((strcmp(filter, "blockedonly")) == 0) + showpermitted = false; + else if((strcmp(filter, "nothing")) == 0) + { + showpermitted = false; + showblocked = false; + } + } + clearSetupVarsArray(); + + // Get domains which the user doesn't want to see + char * excludedomains = NULL; + if(!audit) + { + excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); + if(excludedomains != NULL) + { + getSetupVarsArray(excludedomains); + + if(debugclients) + logg("Excluding %i domains from being displayed", setupVarsElements); + } + } + + if(!istelnet[*sock]) + { + // Send the data required to get the percentage each domain has been blocked / queried + if(blocked) + pack_int32(*sock, counters.blocked); + else + pack_int32(*sock, counters.queries - counters.invalidqueries); + } + + int n = 0; + for(i=0; i < counters.domains; i++) + { + // Get sorted indices + int j = temparray[i][0]; + validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); + + // Skip this domain if there is a filter on it + if(excludedomains != NULL && insetupVarsArray(domains[j].domain)) + continue; + + // Skip this domain if already included in audit + if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) + continue; + + if(blocked && showblocked && domains[j].blockedcount > 0) + { + if(audit && domains[j].wildcard) + { + if(istelnet[*sock]) + ssend(*sock, "%i %i %s wildcard\n", n, domains[j].blockedcount, domains[j].domain); + else { + char *fancyWildcard = calloc(3 + strlen(domains[j].domain), sizeof(char)); + sprintf(fancyWildcard, "*.%s", domains[j].domain); + + if(!pack_str32(*sock, fancyWildcard)) + return; + + pack_int32(*sock, domains[j].blockedcount); + free(fancyWildcard); + } + } + else + { + if(istelnet[*sock]) + ssend(*sock, "%i %i %s\n", n, domains[j].blockedcount, domains[j].domain); + else { + if(!pack_str32(*sock, domains[j].domain)) + return; + + pack_int32(*sock, domains[j].blockedcount); + } + } + n++; + } + else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) + { + if(istelnet[*sock]) + ssend(*sock,"%i %i %s\n",n,(domains[j].count - domains[j].blockedcount),domains[j].domain); + else + { + if(!pack_str32(*sock, domains[j].domain)) + return; + + pack_int32(*sock, domains[j].count - domains[j].blockedcount); + } + n++; + } + + // Only count entries that are actually sent and return when we have send enough data + if(n == count) + break; + } + + if(excludedomains != NULL) + clearSetupVarsArray(); + + if(debugclients) + { + if(blocked) + logg("Sent top ads list data to client, ID: %i", *sock); + else + logg("Sent top domains list data to client, ID: %i", *sock); + } +} + +void getTopClients(char *client_message, int *sock) +{ + int i, temparray[counters.clients][2], count=10, num; + + // Match both top-domains and top-ads + // example: >top-clients (15) + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + count = num; + } + + // Show also clients which have not been active recently? + // This option can be combined with existing options, + // i.e. both >top-clients withzero" and ">top-clients withzero (123)" are valid + bool includezeroclients = false; + if(command(client_message, " withzero")) + includezeroclients = true; + + for(i=0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + temparray[i][0] = i; + temparray[i][1] = clients[i].count; + } + + // Sort in ascending order? + // example: >top-clients asc + bool asc = false; + if(command(client_message, " asc")) + asc = true; + + // Sort temporary array + if(asc) + qsort(temparray, counters.clients, sizeof(int[2]), cmpasc); + else + qsort(temparray, counters.clients, sizeof(int[2]), cmpdesc); + + // Get clients which the user doesn't want to see + char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); + if(excludeclients != NULL) + { + getSetupVarsArray(excludeclients); + + if(debugclients) + logg("Excluding %i clients from being displayed", setupVarsElements); + } + + if(!istelnet[*sock]) + { + // Send the total queries so they can make percentages from this data + pack_int32(*sock, counters.queries - counters.invalidqueries); + } + + int n = 0; + for(i=0; i < counters.clients; i++) + { + // Get sorted indices + int j = temparray[i][0]; + validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); + + // Skip this client if there is a filter on it + if(excludeclients != NULL && (insetupVarsArray(clients[j].ip) || insetupVarsArray(clients[j].name))) + continue; + + // Return this client if either + // - "withzero" option is set, and/or + // - the client made at least one query within the most recent 24 hours + if(includezeroclients || clients[j].count > 0) + { + if(istelnet[*sock]) + ssend(*sock,"%i %i %s %s\n",n,clients[j].count,clients[j].ip,clients[j].name); + else + { + if(!pack_str32(*sock, clients[j].name) || !pack_str32(*sock, clients[j].ip)) + return; + + pack_int32(*sock, clients[j].count); + } + n++; + } + + if(n == count) + break; + } + + if(excludeclients != NULL) + clearSetupVarsArray(); + + if(debugclients) + logg("Sent top clients data to client, ID: %i", *sock); +} + + +void getForwardDestinations(char *client_message, int *sock) +{ + bool allocated = false, sort = true; + int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; + + if(command(client_message, "unsorted")) + sort = false; + + for(i=0; i < counters.forwarded; i++) { + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + // Compute forwardedsum + forwardedsum += forwarded[i].count; + + // If we want to print a sorted output, we fill the temporary array with + // the values we will use for sorting afterwards + if(sort) { + temparray[i][0] = i; + temparray[i][1] = forwarded[i].count; + } + } + + if(sort) { + // Add "local " forward destination + temparray[counters.forwarded][0] = counters.forwarded; + temparray[counters.forwarded][1] = counters.cached + counters.blocked; + + // Sort temporary array in descending order + qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); + } + + totalqueries = counters.forwardedqueries + counters.cached + counters.blocked; + + // Loop over available forward destinations + for(i=0; i < min(counters.forwarded+1, 10); i++) + { + char *name, *ip; + double percentage; + + // Get sorted indices + int j; + if(sort) + j = temparray[i][0]; + else + j = i; + + // Is this the "local" forward destination? + if(j == counters.forwarded) + { + ip = calloc(4,1); + strcpy(ip, "::1"); + name = calloc(6,1); + strcpy(name, "local"); + + if(totalqueries > 0) + // Whats the percentage of (cached + blocked) queries on the total amount of queries? + percentage = 1e2 * (counters.cached + counters.blocked) / totalqueries; + else + percentage = 0.0; + + allocated = true; + } + else + { + validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); + ip = forwarded[j].ip; + name = forwarded[j].name; + + // Math explanation: + // A single query may result in requests being forwarded to multiple destinations + // Hence, in order to be able to give percentages here, we have to normalize the + // number of forwards to each specific destination by the total number of forward + // events. This term is done by + // a = forwarded[j].count / forwardedsum + // + // The fraction a describes now how much share an individual forward destination + // has on the total sum of sent requests. + // We also know the share of forwarded queries on the total number of queries + // b = counters.forwardedqueries / c + // where c is the number of valid queries, + // c = counters.forwardedqueries + counters.cached + counters.blocked + // + // To get the total percentage of a specific query on the total number of queries, + // we simply have to scale b by a which is what we do in the following. + if(forwardedsum > 0 && totalqueries > 0) + percentage = 1e2 * forwarded[j].count / forwardedsum * counters.forwardedqueries / totalqueries; + else + percentage = 0.0; + + allocated = false; + } + + // Send data if count > 0 + if(percentage > 0.0) + { + if(istelnet[*sock]) + ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); + else + { + if(!pack_str32(*sock, name) || !pack_str32(*sock, ip)) + return; + + pack_float(*sock, (float) percentage); + } + } + + // Free previously allocated memory only if we allocated it + if(allocated) + { + free(ip); + free(name); + } + } + + if(debugclients) + logg("Sent forward destination data to client, ID: %i", *sock); +} + + +void getQueryTypes(int *sock) +{ + int total = counters.IPv4 + counters.IPv6; + double percentageIPv4 = 0.0, percentageIPv6 = 0.0; + + // Prevent floating point exceptions by checking if the divisor is != 0 + if(total > 0) { + percentageIPv4 = 1e2*counters.IPv4/total; + percentageIPv6 = 1e2*counters.IPv6/total; + } + + if(istelnet[*sock]) + ssend(*sock,"A (IPv4): %.2f\nAAAA (IPv6): %.2f\n", percentageIPv4, percentageIPv6); + else { + pack_float(*sock, (float) percentageIPv4); + pack_float(*sock, (float) percentageIPv6); + } + + if(debugclients) + logg("Sent query type data to client, ID: %i", *sock); +} + + +void getAllQueries(char *client_message, int *sock) +{ + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Do we want a more specific version of this command (domain/client/time interval filtered)? + int from = 0, until = 0; + + char *domainname = NULL; + bool filterdomainname = false; + + char *clientname = NULL; + bool filterclientname = false; + + // Time filtering? + if(command(client_message, ">getallqueries-time")) { + sscanf(client_message, ">getallqueries-time %i %i",&from, &until); + } + // Domain filtering? + if(command(client_message, ">getallqueries-domain")) { + // Get domain name we want to see only (limit length to 255 chars) + domainname = calloc(256, sizeof(char)); + sscanf(client_message, ">getallqueries-domain %255s", domainname); + if(debugclients) + logg("Showing only queries with domain %s", domainname); + filterdomainname = true; + } + // Client filtering? + if(command(client_message, ">getallqueries-client")) { + clientname = calloc(256, sizeof(char)); + // Get client name we want to see only (limit length to 255 chars) + sscanf(client_message, ">getallqueries-client %255s", clientname); + if(debugclients) + logg("Showing only queries with client %s", clientname); + filterclientname = true; + } + + int ibeg = 0, num; + // Test for integer that specifies number of entries to be shown + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; + } + + // Get potentially existing filtering flags + char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); + bool showpermitted = true, showblocked = true; + if(filter != NULL) + { + if((strcmp(filter, "permittedonly")) == 0) + showblocked = false; + else if((strcmp(filter, "blockedonly")) == 0) + showpermitted = false; + else if((strcmp(filter, "nothing")) == 0) + { + showpermitted = false; + showblocked = false; + } + } + clearSetupVarsArray(); + + // Get privacy mode flag + char * privacy = read_setupVarsconf("API_PRIVACY_MODE"); + bool privacymode = false; + + if(privacy != NULL) + if(getSetupVarsBool(privacy)) + privacymode = true; + + clearSetupVarsArray(); + + if(debugclients) + { + if(showpermitted) + logg("Showing permitted queries"); + else + logg("Hiding permitted queries"); + + if(showblocked) + logg("Showing blocked queries"); + else + logg("Hiding blocked queries"); + + if(privacymode) + logg("Privacy mode enabled"); + } + + int i; + for(i=ibeg; i < counters.queries; i++) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(!queries[i].valid) continue; + + validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); + validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); + + char qtype[5]; + if(queries[i].type == 1) + strcpy(qtype,"IPv4"); + else + strcpy(qtype,"IPv6"); + + if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) + continue; + if((queries[i].status == 2 || queries[i].status == 3) && !showpermitted) + continue; + + // Skip those entries which so not meet the requested timeframe + if((from > queries[i].timestamp && from != 0) || (queries[i].timestamp > until && until != 0)) + continue; + + if(filterdomainname) + { + // Skip if domain name is not identical with what the user wants to see + if(strcmp(domains[queries[i].domainID].domain, domainname) != 0) + continue; + } + + if(filterclientname) + { + // Skip if client name and IP are not identical with what the user wants to see + if((strcmp(clients[queries[i].clientID].ip, clientname) != 0) && + (strcmp(clients[queries[i].clientID].name, clientname) != 0)) + continue; + } + + if(istelnet[*sock]) + { + if(!privacymode) + { + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); + else + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); + } + else + ssend(*sock,"%i %s %s hidden %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); + } + else + { + char *client; + + if(!privacymode) { + if(strlen(clients[queries[i].clientID].name) > 0) + client = clients[queries[i].clientID].name; + else + client = clients[queries[i].clientID].ip; + } + else + client = "hidden"; + + pack_int32(*sock, queries[i].timestamp); + + // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) + if(!pack_fixstr(*sock, qtype)) + return; + + // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) + if(!pack_str32(*sock, domains[queries[i].domainID].domain) || !pack_str32(*sock, client)) + return; + + pack_uint8(*sock, queries[i].status); + pack_uint8(*sock, domains[queries[i].domainID].dnssec); + } + } + + // Free allocated memory + if(filterclientname) + free(clientname); + + if(filterdomainname) + free(domainname); + + if(debugclients) + logg("Sent all queries data to client, ID: %i", *sock); +} + +void getRecentBlocked(char *client_message, int *sock) +{ + int i, num=1; + + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Test for integer that specifies number of entries to be shown + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; + } + + // Find most recent query with either status 1 (blocked) + // or status 4 (wildcard blocked) + int found = 0; + for(i = counters.queries - 1; i > 0 ; i--) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(!queries[i].valid) continue; + + if(queries[i].status == 1 || queries[i].status == 4) + { + found++; + + if(istelnet[*sock]) + ssend(*sock,"%s\n", domains[queries[i].domainID].domain); + else if(!pack_str32(*sock, domains[queries[i].domainID].domain)) + return; + } + + if(found >= num) + break; + } +} + +void getMemoryUsage(int *sock) +{ + unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); + char *structprefix = calloc(2, sizeof(char)); + double formated = 0.0; + format_memory_size(structprefix, structbytes, &formated); + + if(istelnet[*sock]) + ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); + else + pack_uint64(*sock, structbytes); + free(structprefix); + + unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; + char *dynamicprefix = calloc(2, sizeof(char)); + format_memory_size(dynamicprefix, dynamicbytes, &formated); + + if(istelnet[*sock]) + ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); + else + pack_uint64(*sock, dynamicbytes); + free(dynamicprefix); + + unsigned long int totalbytes = structbytes + dynamicbytes; + char *totalprefix = calloc(2, sizeof(char)); + format_memory_size(totalprefix, totalbytes, &formated); + + if(istelnet[*sock]) + ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); + else + pack_uint64(*sock, totalbytes); + free(totalprefix); + + if(debugclients) + logg("Sent memory data to client, ID: %i", *sock); +} + +void getForwardDestinationsOverTime(int *sock) +{ + int i, sendit = -1; + + for(i = 0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if((overTime[i].total > 0 || overTime[i].blocked > 0)) + { + sendit = i; + break; + } + } + + // Send the number of forward destinations (number of items for each timestamp), names, and IPs + if(!istelnet[*sock]) { + // Add one to include the local forwarded category + pack_int32(*sock, counters.forwarded + 1); + + for(i = 0; i < counters.forwarded + 1; i++) { + char *name, *ip; + + if(i == counters.forwarded) { + name = "local"; + ip = "::1"; + } + else { + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + name = forwarded[i].name; + ip = forwarded[i].ip; + } + + if(!pack_str32(*sock, name) || !pack_str32(*sock, ip)) + return; + } + } + + if(sendit > -1) + { + for(i = sendit; i < counters.overTime; i++) + { + float percentage; + + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(istelnet[*sock]) + { + ssend(*sock, "%i", overTime[i].timestamp); + } + else + { + pack_int32(*sock, overTime[i].timestamp); + } + + int j, forwardedsum = 0; + + // Compute forwardedsum used for later normalization + for(j = 0; j < overTime[i].forwardnum; j++) + { + forwardedsum += overTime[i].forwarddata[j]; + } + + // Loop over forward destinations to generate output to be sent to the client + for(j = 0; j < counters.forwarded; j++) + { + int thisforward = 0; + + if(j < overTime[i].forwardnum) { + // This forward destination does already exist at this timestamp + // -> use counter of requests sent to this destination + thisforward = overTime[i].forwarddata[j]; + } + // else + // { + // This forward destination does not yet exist at this timestamp + // -> use zero as number of requests sent to this destination + // thisforward = 0; + // } + + // Avoid floating point exceptions + if(forwardedsum > 0 && overTime[i].total > 0 && thisforward > 0) { + // A single query may result in requests being forwarded to multiple destinations + // Hence, in order to be able to give percentages here, we have to normalize the + // number of forwards to each specific destination by the total number of forward + // events. This is done by + // a = thisforward / forwardedsum + // The fraction a describes how much share an individual forward destination + // has on the total sum of sent requests. + // + // We also know the share of forwarded queries on the total number of queries + // b = forwardedqueries/overTime[i].total + // where the number of forwarded queries in this time interval is given by + // forwardedqueries = overTime[i].total - (overTime[i].cached + // + overTime[i].blocked) + // + // To get the total percentage of a specific forward destination on the total + // number of queries, we simply have to multiply a and b as done below: + percentage = (float) (1e2 * thisforward / forwardedsum * (overTime[i].total - (overTime[i].cached + overTime[i].blocked)) / overTime[i].total); + } + else + percentage = 0.0; + + if(istelnet[*sock]) + ssend(*sock, " %.2f", percentage); + else + pack_float(*sock, (float) percentage); + } + + // Avoid floating point exceptions + if(overTime[i].total > 0) + // Forward count for destination "local" is cached + blocked normalized by total: + percentage = (float) (1e2 * (overTime[i].cached + overTime[i].blocked) / overTime[i].total); + else + percentage = 0.0; + + if(istelnet[*sock]) + ssend(*sock, " %.2f\n", percentage); + else + pack_float(*sock, percentage); + } + } + + if(debugclients) + logg("Sent overTime forwarded data to client, ID: %i", *sock); +} + +void getClientID(int *sock) +{ + if(istelnet[*sock]) + ssend(*sock,"%i\n", *sock); + else + pack_int32(*sock, *sock); + + if(debugclients) + logg("Sent client ID to client, ID: %i", *sock); +} + +void getQueryTypesOverTime(int *sock) +{ + int i, sendit = -1; + for(i = 0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if((overTime[i].total > 0 || overTime[i].blocked > 0)) + { + sendit = i; + break; + } + } + + if(sendit > -1) + { + for(i = sendit; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + + float percentageIPv4 = 0.0, percentageIPv6 = 0.0; + int sum = overTime[i].querytypedata[0] + overTime[i].querytypedata[1]; + + if(sum > 0) { + percentageIPv4 = (float) (1e2 * overTime[i].querytypedata[0] / sum); + percentageIPv6 = (float) (1e2 * overTime[i].querytypedata[1] / sum); + } + + if(istelnet[*sock]) + ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6); + else { + pack_int32(*sock, overTime[i].timestamp); + pack_float(*sock, percentageIPv4); + pack_float(*sock, percentageIPv6); + } + } + } + + if(debugclients) + logg("Sent overTime query types data to client, ID: %i", *sock); +} + +void getVersion(int *sock) +{ + const char * commit = GIT_HASH; + const char * tag = GIT_TAG; + + if(strlen(tag) > 1) { + if(istelnet[*sock]) + ssend(*sock, "version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, tag, GIT_BRANCH, GIT_DATE); + else { + if(!pack_str32(*sock, GIT_VERSION) || + !pack_str32(*sock, (char *) tag) || + !pack_str32(*sock, GIT_BRANCH) || + !pack_str32(*sock, GIT_DATE)) + return; + } + } + else { + char hash[8]; + // Extract first 7 characters of the hash + strncpy(hash, commit, 7); hash[7] = 0; + + if(istelnet[*sock]) + ssend(*sock, "version vDev-%s\ntag %s\nbranch %s\ndate %s\n", hash, tag, GIT_BRANCH, GIT_DATE); + else { + char *hashVersion = calloc(6 + strlen(hash), sizeof(char)); + sprintf(hashVersion, "vDev-%s", hash); + + if(!pack_str32(*sock, hashVersion) || + !pack_str32(*sock, (char *) tag) || + !pack_str32(*sock, GIT_BRANCH) || + !pack_str32(*sock, GIT_DATE)) + return; + + free(hashVersion); + } + } + + if(debugclients) + logg("Sent version info to client, ID: %i", *sock); +} + +void getDBstats(int *sock) +{ + // Get file details + struct stat st; + long int filesize = 0; + if(stat(FTLfiles.db, &st) != 0) + // stat() failed (maybe the file does not exist?) + filesize = -1; + else + filesize = st.st_size; + + char *prefix = calloc(2, sizeof(char)); + double formated = 0.0; + format_memory_size(prefix, filesize, &formated); + + if(istelnet[*sock]) + ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); + else { + pack_int32(*sock, get_number_of_queries_in_DB()); + pack_int64(*sock, filesize); + + if(!pack_str32(*sock, (char *) sqlite3_libversion())) + return; + } + + if(debugclients) + logg("Sent DB info to client, ID: %i", *sock); +} + +void getClientsOverTime(int *sock) +{ + int i, sendit = -1; + + for(i = 0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if((overTime[i].total > 0 || overTime[i].blocked > 0)) + { + sendit = i; + break; + } + } + if(sendit < 0) + return; + + // Get clients which the user doesn't want to see + char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); + // Array of clients to be skipped in the output + // if skipclient[i] == true then this client should be hidden from + // returned data. We initialize it with false + bool skipclient[counters.clients]; + memset(skipclient, false, counters.clients*sizeof(bool)); + + if(excludeclients != NULL) + { + getSetupVarsArray(excludeclients); + + for(i=0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this client should be skipped + if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) + { + skipclient[i] = true; + } + } + } + + // Main return loop + for(i = sendit; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + + if(istelnet[*sock]) + ssend(*sock, "%i", overTime[i].timestamp); + else + pack_int32(*sock, overTime[i].timestamp); + + // Loop over forward destinations to generate output to be sent to the client + int j; + for(j = 0; j < counters.clients; j++) + { + int thisclient = 0; + + if(skipclient[j]) + continue; + + if(j < overTime[i].clientnum) + { + // This client entry does already exist at this timestamp + // -> use counter of requests sent to this destination + thisclient = overTime[i].clientdata[j]; + } + + if(istelnet[*sock]) + ssend(*sock, " %i", thisclient); + else + pack_int32(*sock, thisclient); + } + + if(istelnet[*sock]) + ssend(*sock, "\n"); + else + pack_int32(*sock, -1); + } + + if(excludeclients != NULL) + clearSetupVarsArray(); +} + +void getClientNames(int *sock) +{ + int i; + + // Get clients which the user doesn't want to see + char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); + // Array of clients to be skipped in the output + // if skipclient[i] == true then this client should be hidden from + // returned data. We initialize it with false + bool skipclient[counters.clients]; + memset(skipclient, false, counters.clients*sizeof(bool)); + + if(excludeclients != NULL) + { + getSetupVarsArray(excludeclients); + + for(i=0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this client should be skipped + + } + } + + // Loop over clients to generate output to be sent to the client + for(i = 0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) + continue; + + if(istelnet[*sock]) + ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); + else { + if(!pack_str32(*sock, clients[i].name) || !pack_str32(*sock, clients[i].ip)) + return; + + pack_int32(*sock, clients[i].count); + } + } + + if(excludeclients != NULL) + clearSetupVarsArray(); +} + +void getUnknownQueries(int *sock) +{ + int i; + for(i=0; i < counters.queries; i++) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(queries[i].status != 0 && queries[i].complete) continue; + + char type[5]; + if(queries[i].type == 1) + { + strcpy(type,"IPv4"); + } + else + { + strcpy(type,"IPv6"); + } + + validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); + validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); + + + char *client; + + if(strlen(clients[queries[i].clientID].name) > 0) + client = clients[queries[i].clientID].name; + else + client = clients[queries[i].clientID].ip; + + if(istelnet[*sock]) + ssend(*sock, "%i %i %i %s %s %s %i %s\n", queries[i].timestamp, i, queries[i].id, type, domains[queries[i].domainID].domain, client, queries[i].status, queries[i].complete ? "true" : "false"); + else { + pack_int32(*sock, queries[i].timestamp); + pack_int32(*sock, queries[i].id); + + // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) + if(!pack_fixstr(*sock, type)) + return; + + // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) + if(!pack_str32(*sock, domains[queries[i].domainID].domain) || !pack_str32(*sock, client)) + return; + + pack_uint8(*sock, queries[i].status); + pack_bool(*sock, queries[i].complete); + } + } + + if(debugclients) + logg("Sent unknown queries data to client, ID: %i", *sock); +} diff --git a/api.h b/api.h new file mode 100644 index 00000000..c34747f3 --- /dev/null +++ b/api.h @@ -0,0 +1,42 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2017 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API commands and MessagePack helpers +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +// Statistic methods +void getStats(int *sock); +void getOverTime(int *sock); +void getTopDomains(char *client_message, int *sock); +void getTopClients(char *client_message, int *sock); +void getForwardDestinations(char *client_message, int *sock); +void getQueryTypes(int *sock); +void getAllQueries(char *client_message, int *sock); +void getRecentBlocked(char *client_message, int *sock); +void getForwardDestinationsOverTime(int *sock); +void getQueryTypesOverTime(int *sock); +void getClientsOverTime(int *sock); +void getClientNames(int *sock); + +// FTL methods +void getMemoryUsage(int *sock); +void getClientID(int *sock); +void getVersion(int *sock); +void getDBstats(int *sock); +void getUnknownQueries(int *sock); + +// MessagePack serialization helpers +void pack_eom(int sock); +void pack_bool(int sock, bool value); +void pack_uint8(int sock, uint8_t value); +void pack_uint64(int sock, uint64_t value); +void pack_int32(int sock, int32_t value); +void pack_int64(int sock, int64_t value); +void pack_float(int sock, float value); +bool pack_fixstr(int sock, char *string); +bool pack_str32(int sock, char *string); +void pack_map16_start(int sock, uint16_t length); diff --git a/grep.c b/grep.c index 17e27fa4..bc828836 100644 --- a/grep.c +++ b/grep.c @@ -109,8 +109,12 @@ void readWildcardsList() return; } + + // Trim off the newline (could even be CR-LF) + linebuffer[strcspn(linebuffer, "\r\n")] = 0; + // Try to read up to 511 characters - if(sscanf(linebuffer, "address=/%511[^/]/%*[^\n]\n", buffer) > 0) + if(sscanf(linebuffer, "address=/%511[^/]/", buffer) > 0) { unsigned long int addrbuffer = 0; // Skip leading '.' by incrementing memory location step by step until the first diff --git a/main.c b/main.c index 3448460f..244080b2 100644 --- a/main.c +++ b/main.c @@ -67,6 +67,7 @@ int main (int argc, char* argv[]) { // the system without the need for another thread to join with the terminated thread pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + // Start log analyzing thread pthread_t piholelogthread; if(pthread_create( &piholelogthread, &attr, pihole_log_thread, NULL ) != 0) { @@ -77,6 +78,7 @@ int main (int argc, char* argv[]) { // Bind to sockets after initial log parsing bind_sockets(); + // Start TELNET IPv4 thread pthread_t telnet_listenthreadv4; if(ipv4telnet && pthread_create( &telnet_listenthreadv4, &attr, telnet_listening_thread_IPv4, NULL ) != 0) { @@ -84,6 +86,7 @@ int main (int argc, char* argv[]) { killed = 1; } + // Start TELNET IPv6 thread pthread_t telnet_listenthreadv6; if(ipv6telnet && pthread_create( &telnet_listenthreadv6, &attr, telnet_listening_thread_IPv6, NULL ) != 0) { @@ -91,6 +94,7 @@ int main (int argc, char* argv[]) { killed = 1; } + // Start SOCKET thread pthread_t socket_listenthread; if(pthread_create( &socket_listenthread, &attr, socket_listening_thread, NULL ) != 0) { diff --git a/msgpack.c b/msgpack.c new file mode 100644 index 00000000..0fe622de --- /dev/null +++ b/msgpack.c @@ -0,0 +1,118 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2017 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* MessagePack serialization +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "api.h" + +void pack_eom(int sock) { + // This byte is explicitly never used in the MessagePack spec, so it is perfect to use as an EOM for this API. + uint8_t eom = 0xc1; + swrite(sock, &eom, sizeof(eom)); +} + +void pack_basic(int sock, uint8_t format, void *value, size_t size) { + swrite(sock, &format, sizeof(format)); + swrite(sock, value, size); +} + +uint64_t leToBe64(uint64_t value) { + char *ptr = (char *) &value; + uint32_t part1, part2; + + // Copy the two halves of the 64 bit input into uint32_t's so we can use htonl + memcpy(&part1, ptr, 4); + memcpy(&part2, ptr + 4, 4); + + // Flip each half around + part1 = htonl(part1); + part2 = htonl(part2); + + // Arrange them to form the big-endian version of the original input + return (uint64_t) part1 << 32 | part2; +} + +void pack_bool(int sock, bool value) { + uint8_t packed = (uint8_t) (value ? 0xc3 : 0xc2); + swrite(sock, &packed, sizeof(packed)); +} + +void pack_uint8(int sock, uint8_t value) { + pack_basic(sock, 0xcc, &value, sizeof(value)); +} + +void pack_uint64(int sock, uint64_t value) { + uint64_t bigEValue = leToBe64(value); + pack_basic(sock, 0xcf, &bigEValue, sizeof(bigEValue)); +} + +void pack_int32(int sock, int32_t value) { + uint32_t bigEValue = htonl((uint32_t) value); + pack_basic(sock, 0xd2, &bigEValue, sizeof(bigEValue)); +} + +void pack_int64(int sock, int64_t value) { + // Need to use memcpy to do a direct copy without reinterpreting the bytes (making negatives into positives). + // It should get optimized away. + uint64_t bigEValue; + memcpy(&bigEValue, &value, sizeof(bigEValue)); + bigEValue = leToBe64(bigEValue); + pack_basic(sock, 0xd3, &bigEValue, sizeof(bigEValue)); +} + +void pack_float(int sock, float value) { + // Need to use memcpy to do a direct copy without reinterpreting the bytes. It should get optimized away. + uint32_t bigEValue; + memcpy(&bigEValue, &value, sizeof(bigEValue)); + bigEValue = htonl(bigEValue); + pack_basic(sock, 0xca, &bigEValue, sizeof(bigEValue)); +} + +// Return true if successful +bool pack_fixstr(int sock, char *string) { + // Make sure that the length is less than 32 + size_t length = strlen(string); + + if(length >= 32) { + logg("Tried to send a fixstr longer than 31 bytes!"); + return false; + } + + uint8_t format = (uint8_t) (0xA0 | length); + swrite(sock, &format, sizeof(format)); + swrite(sock, string, length); + + return true; +} + +// Return true if successful +bool pack_str32(int sock, char *string) { + // Make sure that the length is less than 4294967296 + size_t length = strlen(string); + + if(length >= 2147483648) { + logg("Tried to send a str32 longer than 2147483647 bytes!"); + return false; + } + + uint8_t format = 0xdb; + swrite(sock, &format, sizeof(format)); + uint32_t bigELength = htonl((uint32_t) length); + swrite(sock, &bigELength, sizeof(bigELength)); + swrite(sock, string, length); + + return true; +} + +void pack_map16_start(int sock, uint16_t length) { + uint8_t format = 0xde; + swrite(sock, &format, sizeof(format)); + uint16_t bigELength = htons(length); + swrite(sock, &bigELength, sizeof(bigELength)); +} diff --git a/request.c b/request.c index 923199f5..ad6c53d2 100644 --- a/request.c +++ b/request.c @@ -9,30 +9,11 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "version.h" +#include "api.h" -// Private -#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) -#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) - -// Local prototypes -void getStats(int *sock); -void getOverTime(int *sock); -void getTopDomains (char *client_message, int *sock); -void getTopClients(char *client_message, int *sock); -void getForwardDestinations(char *client_message, int *sock); -void getQueryTypes(int *sock); -void getAllQueries(char *client_message, int *sock); -void getRecentBlocked(char *client_message, int *sock); -void getMemoryUsage(int *sock); -void getForwardDestinationsOverTime(int *sock); -void getClientID(int *sock); -void getQueryTypesOverTime(int *sock); -void getVersion(int *sock); -void getDBstats(int *sock); -void getClientsOverTime(int *sock); -void getClientNames(int *sock); -void getUnknownQueries(int *sock); +bool command(char *client_message, const char* cmd) { + return strstr(client_message, cmd) != NULL; +} void process_request(char *client_message, int *sock) { @@ -40,6 +21,7 @@ void process_request(char *client_message, int *sock) EOT[0] = 0x04; EOT[1] = 0x00; bool processed = false; + if(command(client_message, ">stats")) { processed = true; @@ -162,1028 +144,3 @@ void process_request(char *client_message, int *sock) seom(*sock); } } - -bool command(char *client_message, const char* cmd) -{ - if(strstr(client_message,cmd) != NULL) - return true; - else - return false; -} - -// void formatNumber(bool raw, int n, char* buffer) -// { -// if(raw) -// { -// // Don't change number, echo string -// sprintf(buffer, "%d", n); -// } -// else -// { -// // Insert thousand separator -// if(n < 0) { -// sprintf(buffer, "-"); -// n = -n; -// } -// else -// { -// // Empty buffer -// buffer[0] = '\0'; -// } - -// int a[20] = { 0 }; -// int *pa = a; -// while(n > 0) { -// *++pa = n % 1000; -// n /= 1000; -// } -// sprintf(buffer, "%s%d", buffer, *pa); -// while(pa > a + 1) { -// sprintf(buffer, "%s,%03d", buffer, *--pa); -// } -// } -// } - -/* qsort comparision function (count field), sort ASC */ -int cmpasc(const void *a, const void *b) -{ - int *elem1 = (int*)a; - int *elem2 = (int*)b; - - if (elem1[1] < elem2[1]) - return -1; - else if (elem1[1] > elem2[1]) - return 1; - else - return 0; -} - -// qsort subroutine, sort DESC -int cmpdesc(const void *a, const void *b) -{ - int *elem1 = (int*)a; - int *elem2 = (int*)b; - - if (elem1[1] > elem2[1]) - return -1; - else if (elem1[1] < elem2[1]) - return 1; - else - return 0; -} - -void getStats(int *sock) -{ - int blocked = counters.blocked + counters.wildcardblocked; - int total = counters.queries - counters.invalidqueries; - float percentage = 0.0; - // Avoid 1/0 condition - if(total > 0) - { - percentage = 1e2*blocked/total; - } - switch(blockingstatus) - { - case 0: // Blocking disabled - ssend(*sock,"domains_being_blocked N/A\n"); - break; - default: // Either unknown or enabled - ssend(*sock,"domains_being_blocked %i\n",counters.gravity); - break; - } - ssend(*sock,"dns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ - total,blocked,percentage); - ssend(*sock,"unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ - counters.domains,counters.forwardedqueries,counters.cached); - - // clients_ever_seen: all clients ever seen by FTL - ssend(*sock,"clients_ever_seen %i\n", \ - counters.clients); - - // unique_clients: count only clients that have been active within the most recent 24 hours - int i, activeclients = 0; - for(i=0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - if(clients[i].count > 0) - activeclients++; - } - ssend(*sock,"unique_clients %i\n", \ - activeclients); - - switch(blockingstatus) - { - case 0: // Blocking disabled - ssend(*sock,"status disabled\n"); - break; - case 1: // Blocking Enabled - ssend(*sock,"status enabled\n"); - break; - default: // Unknown status - ssend(*sock,"status unknown\n"); - break; - } - - if(debugclients) - logg("Sent stats data to client, ID: %i", *sock); -} - -void getOverTime(int *sock) -{ - int i; - bool sendit = false; - for(i=0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0) && !sendit) - { - sendit = true; - } - if(sendit) - { - ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); - } - } - if(debugclients) - logg("Sent overTime data to client, ID: %i", *sock); -} - -void getTopDomains(char *client_message, int *sock) -{ - int i, temparray[counters.domains][2], count=10, num; - bool blocked = command(client_message, ">top-ads"), audit = false, asc = false; - - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - - // Match both top-domains and top-ads - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - - // Apply Audit Log filtering? - if(command(client_message, " for audit")) - { - audit = true; - } - - // Sort in descending order? - if(command(client_message, " asc")) - { - asc = true; - } - - for(i=0; i < counters.domains; i++) - { - validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); - temparray[i][0] = i; - if(blocked) - temparray[i][1] = domains[i].blockedcount; - else - // Count only permitted queries - temparray[i][1] = (domains[i].count - domains[i].blockedcount); - } - - // Sort temporary array - if(asc) - qsort(temparray, counters.domains, sizeof(int[2]), cmpasc); - else - qsort(temparray, counters.domains, sizeof(int[2]), cmpdesc); - - - // Get filter - char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); - bool showpermitted = true, showblocked = true; - if(filter != NULL) - { - if((strcmp(filter, "permittedonly")) == 0) - { - showblocked = false; - } - else if((strcmp(filter, "blockedonly")) == 0) - { - showpermitted = false; - } - else if((strcmp(filter, "nothing")) == 0) - { - showpermitted = false; - showblocked = false; - } - } - clearSetupVarsArray(); - - // Get domains which the user doesn't want to see - char * excludedomains = NULL; - if(!audit) - { - excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); - if(excludedomains != NULL) - { - getSetupVarsArray(excludedomains); - if(debugclients) - logg("Excluding %i domains from being displayed", setupVarsElements); - } - } - - int n = 0; - for(i=0; i < counters.domains; i++) - { - // Get sorted indices - int j = temparray[i][0]; - validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); - - // Skip this domain if there is a filter on it - if(excludedomains != NULL && insetupVarsArray(domains[j].domain)) - continue; - - // Skip this domain if already included in audit - if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) - continue; - - if(blocked && showblocked && domains[j].blockedcount > 0) - { - if(audit && domains[j].wildcard) - ssend(*sock,"%i %i %s wildcard\n", n, domains[j].blockedcount, domains[j].domain); - else - ssend(*sock,"%i %i %s\n", n ,domains[j].blockedcount, domains[j].domain); - n++; - } - else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) - { - ssend(*sock,"%i %i %s\n", n, (domains[j].count - domains[j].blockedcount), domains[j].domain); - n++; - } - - // Only count entries that are actually sent and return when we have send enough data - if(n == count) - break; - } - if(excludedomains != NULL) - clearSetupVarsArray(); - if(debugclients) - { - if(blocked) - logg("Sent top ads list data to client, ID: %i", *sock); - else - logg("Sent top domains list data to client, ID: %i", *sock); - } -} - -void getTopClients(char *client_message, int *sock) -{ - int i, temparray[counters.clients][2], count=10, num; - - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - - // Show also clients which have not been active recently? - // This option can be combined with existing options, - // i.e. both >top-clients withzero" and ">top-clients withzero (123)" are valid - bool includezeroclients = false; - if(command(client_message, " withzero")) - { - includezeroclients = true; - } - - for(i=0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - temparray[i][0] = i; - temparray[i][1] = clients[i].count; - } - - // Sort in ascending order? - bool asc = false; - if(command(client_message, " asc")) - { - asc = true; - } - - // Sort temporary array - if(asc) - qsort(temparray, counters.clients, sizeof(int[2]), cmpasc); - else - qsort(temparray, counters.clients, sizeof(int[2]), cmpdesc); - - // Get clients which the user doesn't want to see - char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - if(excludeclients != NULL) - { - getSetupVarsArray(excludeclients); - if(debugclients) - logg("Excluding %i clients from being displayed", setupVarsElements); - } - - int n = 0; - for(i=0; i < counters.clients; i++) - { - // Get sorted indices - int j = temparray[i][0]; - validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); - - // Skip this client if there is a filter on it - if(excludeclients != NULL && (insetupVarsArray(clients[j].ip) || insetupVarsArray(clients[j].name))) - continue; - - // Return this client if either - // - "withzero" option is set, and/or - // - the client made at least one query within the most recent 24 hours - if(includezeroclients || clients[j].count > 0) - { - ssend(*sock, "%i %i %s %s\n", n, clients[j].count, clients[j].ip, clients[j].name); - n++; - } - - // Only count entries that are actually sent and return when we have send enough data - if(n == count) - break; - } - if(excludeclients != NULL) - clearSetupVarsArray(); - if(debugclients) - logg("Sent top clients data to client, ID: %i", *sock); -} - - -void getForwardDestinations(char *client_message, int *sock) -{ - bool allocated = false, sort = true; - int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; - - if(command(client_message, "unsorted")) - sort = false; - - for(i=0; i < counters.forwarded; i++) - { - validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); - // Compute forwardedsum - forwardedsum += forwarded[i].count; - - // If we want to print a sorted output, we fill the temporary array with - // the values we will use for sorting afterwards - if(sort) - { - temparray[i][0] = i; - temparray[i][1] = forwarded[i].count; - } - } - - if(sort) - { - // Add "local " forward destination - temparray[counters.forwarded][0] = counters.forwarded; - temparray[counters.forwarded][1] = counters.cached + counters.blocked; - - // Sort temporary array in descending order - qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); - } - - totalqueries = counters.forwardedqueries + counters.cached + counters.blocked; - - // Loop over available forward destinations - for(i=0; i < min(counters.forwarded+1, 10); i++) - { - char *name, *ip; - double percentage; - - // Get sorted indices - int j; - if(sort) - j = temparray[i][0]; - else - j = i; - - // Is this the "local" forward destination? - if(j == counters.forwarded) - { - ip = calloc(4,1); - strcpy(ip, "::1"); - name = calloc(6,1); - strcpy(name, "local"); - if(totalqueries > 0) - // Whats the percentage of (cached + blocked) queries on the total amount of queries? - percentage = 1e2 * (counters.cached + counters.blocked) / totalqueries; - else - percentage = 0.0; - allocated = true; - } - else - { - validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); - ip = forwarded[j].ip; - name = forwarded[j].name; - // Math explanation: - // A single query may result in requests being forwarded to multiple destinations - // Hence, in order to be able to give percentages here, we have to normalize the - // number of forwards to each specific destination by the total number of forward - // events. This term is done by - // a = forwarded[j].count / forwardedsum - // - // The fraction a describes now how much share an individual forward destination - // has on the total sum of sent requests. - // We also know the share of forwarded queries on the total number of queries - // b = counters.forwardedqueries / c - // where c is the number of valid queries, - // c = counters.forwardedqueries + counters.cached + counters.blocked - // - // To get the total percentage of a specific query on the total number of queries, - // we simply have to scale b by a which is what we do in the following. - if(forwardedsum > 0 && totalqueries > 0) - percentage = 1e2 * forwarded[j].count / forwardedsum * counters.forwardedqueries / totalqueries; - else - percentage = 0.0; - allocated = false; - } - - // Send data if count > 0 - if(percentage > 0.0) - { - ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); - } - - // Free previously allocated memory only if we allocated it - if(allocated) - { - free(ip); - free(name); - } - } - if(debugclients) - logg("Sent forward destination data to client, ID: %i", *sock); -} - -void getQueryTypes(int *sock) -{ - int total = counters.IPv4 + counters.IPv6; - double percentageIPv4 = 0.0, percentageIPv6 = 0.0; - - // Prevent floating point exceptions by checking if the divisor is != 0 - if(total > 0) - { - percentageIPv4 = 1e2*counters.IPv4/total; - percentageIPv6 = 1e2*counters.IPv6/total; - } - - ssend(*sock, "A (IPv4): %.2f\nAAAA (IPv6): %.2f\n", percentageIPv4, percentageIPv6); - - if(debugclients) - logg("Sent query type data to client, ID: %i", *sock); -} - - -void getAllQueries(char *client_message, int *sock) -{ - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - // Do we want a more specific version of this command (domain/client/time interval filtered)? - int from = 0, until = 0; - bool filtertime = false; - if(command(client_message, ">getallqueries-time")) - { - // Get from to until boundaries - sscanf(client_message, ">getallqueries-time %i %i",&from, &until); - if(debugclients) - { - logg("Showing only limited time interval starting at ",from); - logg("Showing only limited time interval ending at ",until); - } - filtertime = true; - } - - char *domainname; - bool filterdomainname = false; - if(command(client_message, ">getallqueries-domain")) - { - domainname = calloc(128, sizeof(char)); - // Get domain name we want to see only (limit length to 127 chars) - sscanf(client_message, ">getallqueries-domain %127s", domainname); - if(debugclients) - logg("Showing only queries with domain %s", domainname); - filterdomainname = true; - } - - char *clientname; - bool filterclientname = false; - if(command(client_message, ">getallqueries-client")) - { - clientname = calloc(128, sizeof(char)); - // Get client name we want to see only (limit length to 127 chars) - sscanf(client_message, ">getallqueries-client %127s", clientname); - if(debugclients) - logg("Showing only queries with client %s", clientname); - filterclientname = true; - } - - int ibeg = 0, num; - // Test for integer that specifies number of entries to be shown - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - if(debugclients) - logg("Showing only limited amount of queries: ",num); - } - - // Get potentially existing filtering flags - char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); - bool showpermitted = true, showblocked = true; - if(filter != NULL) - { - if((strcmp(filter, "permittedonly")) == 0) - { - showblocked = false; - } - else if((strcmp(filter, "blockedonly")) == 0) - { - showpermitted = false; - } - else if((strcmp(filter, "nothing")) == 0) - { - showpermitted = false; - showblocked = false; - } - } - clearSetupVarsArray(); - - // Get privacy mode flag - char * privacy = read_setupVarsconf("API_PRIVACY_MODE"); - bool privacymode = false; - if(privacy != NULL) - if(getSetupVarsBool(privacy)) - privacymode = true; - clearSetupVarsArray(); - - if(debugclients) - { - if(showpermitted) - logg("Showing permitted queries"); - else - logg("Hiding permitted queries"); - - if(showblocked) - logg("Showing blocked queries"); - else - logg("Hiding blocked queries"); - - if(privacymode) - logg("Privacy mode enabled"); - } - - int i; - for(i=ibeg; i < counters.queries; i++) - { - validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this query has been removed due to garbage collection - if(!queries[i].valid) continue; - - validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); - validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); - - char type[5]; - if(queries[i].type == 1) - { - strcpy(type,"IPv4"); - } - else - { - strcpy(type,"IPv6"); - } - - if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) - continue; - if((queries[i].status == 2 || queries[i].status == 3) && !showpermitted) - continue; - - if(filtertime) - { - // Skip those entries which so not meet the requested timeframe - if(from > queries[i].timestamp || queries[i].timestamp > until) - continue; - } - - if(filterdomainname) - { - // Skip if domain name is not identical with what the user wants to see - if(strcmp(domains[queries[i].domainID].domain, domainname) != 0) - continue; - } - - if(filterclientname) - { - // Skip if client name and IP are not identical with what the user wants to see - if((strcmp(clients[queries[i].clientID].ip, clientname) != 0) && - (strcmp(clients[queries[i].clientID].name, clientname) != 0)) - continue; - } - - if(!privacymode) - { - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock, "%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); - else - ssend(*sock, "%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); - } - else - { - ssend(*sock, "%i %s %s hidden %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); - } - } - - // Free allocated memory - if(filterclientname) - free(clientname); - if(filterdomainname) - free(domainname); - - if(debugclients) - logg("Sent all queries data to client, ID: %i", *sock); -} - -void getRecentBlocked(char *client_message, int *sock) -{ - int i, num=1; - - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - // Test for integer that specifies number of entries to be shown - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - - if(debugclients) - logg("Showing several blocked domains ",num); - } - // Find most recent query with either status 1 (blocked) - // or status 4 (wildcard blocked) - int found = 0; - for(i = counters.queries - 1; i > 0 ; i--) - { - validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this query has been removed due to garbage collection - if(!queries[i].valid) continue; - - if(queries[i].status == 1 || queries[i].status == 4) - { - found++; - ssend(*sock,"%s\n",domains[queries[i].domainID].domain); - } - - if(found >= num) - { - break; - } - } -} - -void getMemoryUsage(int *sock) -{ - unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); - char *structprefix = calloc(2, sizeof(char)); - double formated = 0.0; - format_memory_size(structprefix, structbytes, &formated); - ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); - free(structprefix); - - unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; - char *dynamicprefix = calloc(2, sizeof(char)); - format_memory_size(dynamicprefix, dynamicbytes, &formated); - ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); - free(dynamicprefix); - - unsigned long int totalbytes = structbytes + dynamicbytes; - char *totalprefix = calloc(2, sizeof(char)); - format_memory_size(totalprefix, totalbytes, &formated); - ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); - free(totalprefix); - - if(debugclients) - logg("Sent memory data to client, ID: %i", *sock); -} - -void getForwardDestinationsOverTime(int *sock) -{ - int i, sendit = -1; - - for(i = 0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0)) - { - sendit = i; - break; - } - } - if(sendit > -1) - { - for(i = sendit; i < counters.overTime; i++) - { - double percentage; - - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - ssend(*sock, "%i", overTime[i].timestamp); - - int j, forwardedsum = 0; - - // Compute forwardedsum used for later normalization - for(j = 0; j < overTime[i].forwardnum; j++) - { - forwardedsum += overTime[i].forwarddata[j]; - } - - // Loop over forward destinations to generate output to be sent to the client - for(j = 0; j < counters.forwarded; j++) - { - int thisforward = 0; - - if(j < overTime[i].forwardnum) - { - // This forward destination does already exist at this timestamp - // -> use counter of requests sent to this destination - thisforward = overTime[i].forwarddata[j]; - } - // else - // { - // This forward destination does not yet exist at this timestamp - // -> use zero as number of requests sent to this destination - // thisforward = 0; - // } - - // Avoid floating point exceptions - if(forwardedsum > 0 && overTime[i].total > 0 && thisforward > 0) - { - // A single query may result in requests being forwarded to multiple destinations - // Hence, in order to be able to give percentages here, we have to normalize the - // number of forwards to each specific destination by the total number of forward - // events. This is done by - // a = thisforward / forwardedsum - // The fraction a describes how much share an individual forward destination - // has on the total sum of sent requests. - // - // We also know the share of forwarded queries on the total number of queries - // b = forwardedqueries/overTime[i].total - // where the number of forwarded queries in this time interval is given by - // forwardedqueries = overTime[i].total - (overTime[i].cached - // + overTime[i].blocked) - // - // To get the total percentage of a specific forward destination on the total - // number of queries, we simply have to multiply a and b as done below: - percentage = 1e2 * thisforward / forwardedsum * (overTime[i].total - (overTime[i].cached + overTime[i].blocked)) / overTime[i].total; - } - else - { - percentage = 0.0; - } - - ssend(*sock, " %.2f", percentage); - } - - // Avoid floating point exceptions - if(overTime[i].total > 0) - // Forward count for destination "local" is cached + blocked normalized by total: - percentage = 1e2 * (overTime[i].cached + overTime[i].blocked) / overTime[i].total; - else - percentage = 0.0; - - ssend(*sock, " %.2f\n", percentage); - } - } - if(debugclients) - logg("Sent overTime forwarded data to client, ID: %i", *sock); -} - -void getClientID(int *sock) -{ - ssend(*sock,"%i\n", *sock); - - if(debugclients) - logg("Sent client ID to client, ID: %i", *sock); -} - -void getQueryTypesOverTime(int *sock) -{ - int i, sendit = -1; - for(i = 0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0)) - { - sendit = i; - break; - } - } - if(sendit > -1) - { - for(i = sendit; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - double percentageIPv4 = 0.0, percentageIPv6 = 0.0; - int sum = overTime[i].querytypedata[0] + overTime[i].querytypedata[1]; - if(sum > 0) - { - percentageIPv4 = 1e2*overTime[i].querytypedata[0] / sum; - percentageIPv6 = 1e2*overTime[i].querytypedata[1] / sum; - } - ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6); - } - } - if(debugclients) - logg("Sent overTime query types data to client, ID: %i", *sock); -} - -void getVersion(int *sock) -{ - const char * commit = GIT_HASH; - const char * tag = GIT_TAG; - if(strlen(tag) > 1) - { - ssend(*sock, "version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, tag, GIT_BRANCH, GIT_DATE); - } - else - { - char hash[8]; - // Extract first 7 characters of the hash - strncpy(hash, commit, 7); hash[7] = 0; - ssend(*sock, "version vDev-%s\ntag %s\nbranch %s\ndate %s\n", hash, tag, GIT_BRANCH, GIT_DATE); - } - - if(debugclients) - logg("Sent version info to client, ID: %i", *sock); -} - -void getDBstats(int *sock) -{ - // Get file details - struct stat st; - long int filesize = 0; - if(stat(FTLfiles.db, &st) != 0) - // stat() failed (maybe the file does not exist?) - filesize = -1; - else - filesize = st.st_size; - - char *prefix = calloc(2, sizeof(char)); - double formated = 0.0; - format_memory_size(prefix, filesize, &formated); - - ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); - - if(debugclients) - logg("Sent DB info to client, ID: %i", *sock); -} - -void getClientsOverTime(int *sock) -{ - int i, sendit = -1; - - for(i = 0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0)) - { - sendit = i; - break; - } - } - if(sendit < 0) - return; - - // Get clients which the user doesn't want to see - char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - // Array of clients to be skipped in the output - // if skipclient[i] == true then this client should be hidden from - // returned data. We initialize it with false - bool skipclient[counters.clients]; - memset(skipclient, false, counters.clients*sizeof(bool)); - - if(excludeclients != NULL) - { - getSetupVarsArray(excludeclients); - - for(i=0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this client should be skipped - if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) - { - skipclient[i] = true; - } - } - } - - // Main return loop - for(i = sendit; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - ssend(*sock, "%i", overTime[i].timestamp); - - // Loop over forward destinations to generate output to be sent to the client - int j; - for(j = 0; j < counters.clients; j++) - { - int thisclient = 0; - - if(skipclient[j]) - continue; - - if(j < overTime[i].clientnum) - { - // This client entry does already exist at this timestamp - // -> use counter of requests sent to this destination - thisclient = overTime[i].clientdata[j]; - } - - ssend(*sock, " %i", thisclient); - } - - ssend(*sock, "\n"); - } - - if(excludeclients != NULL) - clearSetupVarsArray(); -} - -void getClientNames(int *sock) -{ - int i; - - // Get clients which the user doesn't want to see - char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - // Array of clients to be skipped in the output - // if skipclient[i] == true then this client should be hidden from - // returned data. We initialize it with false - bool skipclient[counters.clients]; - memset(skipclient, false, counters.clients*sizeof(bool)); - - if(excludeclients != NULL) - { - getSetupVarsArray(excludeclients); - - for(i=0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this client should be skipped - - } - } - - // Loop over clients to generate output to be sent to the client - for(i = 0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) - continue; - - ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); - } - - if(excludeclients != NULL) - clearSetupVarsArray(); -} - -void getUnknownQueries(int *sock) -{ - int i; - for(i=0; i < counters.queries; i++) - { - validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this query has been removed due to garbage collection - if(queries[i].status != 0 && queries[i].complete) continue; - - char type[5]; - if(queries[i].type == 1) - { - strcpy(type,"IPv4"); - } - else - { - strcpy(type,"IPv6"); - } - - validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); - validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); - - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock, "%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,queries[i].complete ?"true":"false"); - else - ssend(*sock, "%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,queries[i].complete?"true":"false"); - } - - if(debugclients) - logg("Sent unknown queries data to client, ID: %i", *sock); -} diff --git a/routines.h b/routines.h index 1a3d28e4..cf151cd1 100644 --- a/routines.h +++ b/routines.h @@ -44,11 +44,11 @@ void memory_check(int which); void close_telnet_socket(void); void close_unix_socket(void); -void swrite(char server_message[], int sock); -void *telnet_listening_thread_IPv4(void *args); -void *telnet_listening_thread_IPv6(void *args); void seom(int sock); void ssend(int sock, const char *format, ...); +void swrite(int sock, void *value, size_t size); +void *telnet_listening_thread_IPv4(void *args); +void *telnet_listening_thread_IPv6(void *args); void *socket_listening_thread(void *args); bool ipv6_available(void); @@ -56,7 +56,7 @@ void bind_sockets(void); void process_request(char *client_message, int *sock); bool command(char *client_message, const char* cmd); -void formatNumber(bool raw, int n, char* buffer); +bool matchesEndpoint(char *client_message, const char *cmd); void read_gravity_files(void); int countlines(const char* fname); diff --git a/socket.c b/socket.c index c86d7884..444a6a90 100644 --- a/socket.c +++ b/socket.c @@ -9,6 +9,7 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" +#include "api.h" // The backlog argument defines the maximum length // to which the queue of pending connections for @@ -24,6 +25,7 @@ int socketfd, telnetfd4 = 0, telnetfd6 = 0; bool dualstack = false; bool ipv4telnet = false, ipv6telnet = false; +bool istelnet[MAXCONNS]; void saveport(void) { @@ -40,7 +42,7 @@ void saveport(void) } } -bool bind_to_telnet_port_IPv4(char type, int *socketdescriptor) +bool bind_to_telnet_port_IPv4(int *socketdescriptor) { // IPv4 socket *socketdescriptor = socket(AF_INET, SOCK_STREAM, 0); @@ -65,7 +67,7 @@ bool bind_to_telnet_port_IPv4(char type, int *socketdescriptor) memset(&serv_addr4, 0, sizeof(serv_addr4)); serv_addr4.sin_family = AF_INET; - if(config.socket_listenlocal && type == SOCKET) + if(config.socket_listenlocal) serv_addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); else serv_addr4.sin_addr.s_addr = INADDR_ANY; @@ -85,11 +87,11 @@ bool bind_to_telnet_port_IPv4(char type, int *socketdescriptor) return false; } - logg("Listening on port %i for incoming IPv4 connections", config.port); + logg("Listening on port %i for incoming IPv4 telnet connections", config.port); return true; } -bool bind_to_telnet_port_IPv6(char type, int *socketdescriptor) +bool bind_to_telnet_port_IPv6(int *socketdescriptor) { // IPv6 socket *socketdescriptor = socket(AF_INET6, SOCK_STREAM, 0); @@ -120,7 +122,7 @@ bool bind_to_telnet_port_IPv6(char type, int *socketdescriptor) memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin6_family = AF_INET6; - if(config.socket_listenlocal && type == SOCKET) + if(config.socket_listenlocal) serv_addr.sin6_addr = in6addr_loopback; else serv_addr.sin6_addr = in6addr_any; @@ -147,7 +149,7 @@ bool bind_to_telnet_port_IPv6(char type, int *socketdescriptor) return false; } - logg("Listening on port %i for incoming IPv6 connections", config.port); + logg("Listening on port %i for incoming IPv6 telnet connections", config.port); return true; } @@ -200,7 +202,10 @@ void removeport(void) void seom(int sock) { - ssend(sock, "---EOM---\n\n"); + if(istelnet[sock]) + ssend(sock, "---EOM---\n\n"); + else + pack_eom(sock); } void ssend(int sock, const char *format, ...) @@ -218,33 +223,58 @@ void ssend(int sock, const char *format, ...) } } +void swrite(int sock, void *value, size_t size) { + if(write(sock, value, size) == -1) + logg("WARNING: Socket write returned error code %i", errno); +} + +int checkClientLimit(int socket) { + if(socket < MAXCONNS) + { + if(debugclients) + logg("Client connected: %i", socket); + return socket; + } + else + { + if(debugclients) + logg("Client denied (at max capacity of %i): %i", MAXCONNS, socket); + + close(socket); + return -1; + } +} + int listener(int sockfd, char type) { struct sockaddr_un un_addr; struct sockaddr_in in4_addr; struct sockaddr_in6 in6_addr; socklen_t socklen = 0; + int socket; switch(type) { case 0: // Unix socket - memset(&un_addr, 0, sizeof(un_addr)); - socklen = sizeof(un_addr); - return accept(sockfd, (struct sockaddr *) &un_addr, &socklen); + memset(&un_addr, 0, sizeof(un_addr)); + socklen = sizeof(un_addr); + return accept(sockfd, (struct sockaddr *) &un_addr, &socklen); case 4: // Internet socket (IPv4) - memset(&in4_addr, 0, sizeof(in4_addr)); - socklen = sizeof(un_addr); - return accept(sockfd, (struct sockaddr *) &in4_addr, &socklen); + memset(&in4_addr, 0, sizeof(in4_addr)); + socklen = sizeof(un_addr); + socket = accept(sockfd, (struct sockaddr *) &in4_addr, &socklen); + return checkClientLimit(socket); case 6: // Internet socket (IPv6) - memset(&in6_addr, 0, sizeof(in6_addr)); - socklen = sizeof(un_addr); - return accept(sockfd, (struct sockaddr *) &in6_addr, &socklen); + memset(&in6_addr, 0, sizeof(in6_addr)); + socklen = sizeof(un_addr); + socket = accept(sockfd, (struct sockaddr *) &in6_addr, &socklen); + return checkClientLimit(socket); default: // Should not happen - logg("Cannot listen on type %i connection, code error!", type); - exit(EXIT_FAILURE); + logg("Cannot listen on type %i connection, code error!", type); + exit(EXIT_FAILURE); } } @@ -270,6 +300,9 @@ void *telnet_connection_handler_thread(void *socket_desc) { //Get the socket descriptor int sock = *(int*)socket_desc; + // Set connection type to telnet + istelnet[sock] = true; + // Store copy only for displaying the debug messages int sockID = sock; char client_message[SOCKETBUFFERLEN] = ""; @@ -328,6 +361,8 @@ void *socket_connection_handler_thread(void *socket_desc) { //Get the socket descriptor int sock = *(int*)socket_desc; + // Set connection type to not telnet + istelnet[sock] = false; // Store copy only for displaying the debug messages int sockID = sock; char client_message[SOCKETBUFFERLEN] = ""; @@ -383,13 +418,13 @@ void *socket_connection_handler_thread(void *socket_desc) void bind_sockets(void) { // Initialize IPv4 telnet socket - if(bind_to_telnet_port_IPv4(SOCKET, &telnetfd4)) + if(bind_to_telnet_port_IPv4(&telnetfd4)) ipv4telnet = true; // Initialize IPv6 telnet socket // only if IPv6 interfaces are available if(ipv6_available()) - if(bind_to_telnet_port_IPv6(SOCKET, &telnetfd6)) + if(bind_to_telnet_port_IPv6(&telnetfd6)) ipv6telnet = true; saveport(); @@ -496,6 +531,7 @@ void *socket_listening_thread(void *args) { // Look for new clients that want to connect int csck = listener(socketfd, 0); + if(csck < 0) continue; // Allocate memory used to transport client socket ID to client listening thread int *newsock; diff --git a/socket_client.c b/socket_client.c index 080bf107..5f6c7e17 100644 --- a/socket_client.c +++ b/socket_client.c @@ -16,13 +16,16 @@ #include #include #include +#include + #define BUF 1024 int main (int argc, char **argv) { int socketfd; char *buffer = malloc (BUF); struct sockaddr_un address; - int size, ret; + ssize_t size; + int ret; // Create socket socketfd = socket(PF_LOCAL, SOCK_STREAM, 0); @@ -36,41 +39,50 @@ int main (int argc, char **argv) { // Set socket family to local socket (not an Internet socket) address.sun_family = AF_LOCAL; - // Set socket file location (respect special location on the CI system Travis) - if(argc == 2 && strcmp(argv[1], "travis") == 0) - strcpy(address.sun_path,"pihole-FTL.sock"); - else - strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); + char *command = ">stats"; + strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); + + int i; + for(i = 1; i < argc; i++) { + // Get command + if(strstr(argv[i], ">") == argv[i]) { + command = argv[i]; + continue; + } + + // Set socket file location (respect special location on the CI system Travis) + if(strcmp(argv[i], "travis") == 0) + strcpy(address.sun_path,"pihole-FTL.sock"); + } // Connect to the socket provided by pihole-FTL ret = connect(socketfd, (struct sockaddr *) &address, sizeof (address)); if (ret != 0) { - printf("Error establishing connection!\n"); + printf("Error establishing connection! %s\n", strerror(errno)); exit(EXIT_FAILURE); } printf("Connection established\n"); // As an example, we query the current statistics from FTL through the socket here - sprintf(buffer, ">stats"); + sprintf(buffer, command); send(socketfd, buffer, strlen (buffer), 0); // Try to receive data until either recv() fails or we see "--EOM--" while((size = recv(socketfd, buffer, BUF-1, 0)) > -1) { - // Zero-terminate incoming message - if(size > 0) - buffer[size] = '\0'; - // Print received data to stdout - printf("%s", buffer); + for(i = 0; i < size; ++i) { + printf("%02x ", (unsigned char) buffer[i]); + } // Exit on End Of Message - if(strstr(buffer, "--EOM--") != NULL) + if((unsigned char) buffer[size-1] == 0xc1) break; - } + printf("\n"); + // Close Unix socket connection close(socketfd); return EXIT_SUCCESS; diff --git a/test/run.sh b/test/run.sh index a4658026..e03fc3f0 100755 --- a/test/run.sh +++ b/test/run.sh @@ -62,6 +62,9 @@ n=0 until [ $n -ge 45 ]; do nc -vv -z -w 30 127.0.0.1 4711 && break n=$[$n+1] + echo "..." + tail -n2 pihole-FTL.log + echo "..." sleep 1 done diff --git a/test/test_suite.sh b/test/test_suite.sh index ed05c68d..8fcd85af 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -235,17 +235,7 @@ load 'libs/bats-support/load' echo "output: ${lines[@]}" [[ ${lines[0]} == "Socket created" ]] [[ ${lines[1]} == "Connection established" ]] - [[ ${lines[2]} == "domains_being_blocked -1" ]] - [[ ${lines[3]} == "dns_queries_today 7" ]] - [[ ${lines[4]} == "ads_blocked_today 2" ]] - [[ ${lines[5]} == "ads_percentage_today 28.571428" ]] - [[ ${lines[6]} == "unique_domains 6" ]] - [[ ${lines[7]} == "queries_forwarded 3" ]] - [[ ${lines[8]} == "queries_cached 2" ]] - [[ ${lines[9]} == "clients_ever_seen 3" ]] - [[ ${lines[10]} == "unique_clients 3" ]] - [[ ${lines[11]} == "status unknown" ]] - [[ ${lines[12]} == "---EOM---" ]] + [[ ${lines[2]} == "d2 ff ff ff ff d2 00 00 00 07 d2 00 00 00 02 ca 41 e4 92 49 d2 00 00 00 06 d2 00 00 00 03 d2 00 00 00 02 d2 00 00 00 03 d2 00 00 00 03 cc 02 c1 " ]] } @test "Final part of the tests: Killing pihole-FTL process" {