Files
FTL/src/api/stats.c
T
DL6ER 2e8e7d1feb Add more API debugging output
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-10-27 19:57:22 +01:00

1205 lines
34 KiB
C

/* Pi-hole: A black hole for Internet advertisements
* (c) 2019 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 "../webserver/http-common.h"
#include "../webserver/json_macros.h"
#include "routes.h"
#include "../shmem.h"
#include "../datastructure.h"
// read_setupVarsconf()
#include "../setupVars.h"
// logg()
#include "../log.h"
// config struct
#include "../config.h"
// in_auditlist()
#include "../database/gravity-db.h"
// overTime data
#include "../overTime.h"
// enum REGEX
#include "../regex_r.h"
// my_sqrt()
#include "../math.h"
/* qsort comparision function (count field), sort ASC
static int __attribute__((pure)) cmpasc(const void *a, const void *b)
{
const int *elem1 = (int*)a;
const 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
static int __attribute__((pure)) cmpdesc(const void *a, const void *b)
{
const int *elem1 = (int*)a;
const int *elem2 = (int*)b;
if (elem1[1] > elem2[1])
return -1;
else if (elem1[1] < elem2[1])
return 1;
else
return 0;
}
int api_stats_summary(struct mg_connection *conn)
{
const int blocked = counters->blocked;
const int total = counters->queries;
float percent_blocked = 0.0f;
// Avoid 1/0 condition
if(total > 0)
percent_blocked = 1e2f*blocked/total;
// unique_clients: count only clients that have been active within the most recent 24 hours
int activeclients = 0;
for(int clientID=0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
if(client->count > 0)
activeclients++;
}
// Send response
cJSON *json = JSON_NEW_OBJ();
const bool blocking = get_blockingstatus();
JSON_OBJ_ADD_BOOL(json, "blocking", blocking); // same reply type as in /api/dns/status
JSON_OBJ_ADD_NUMBER(json, "gravity_size", counters->gravity);
JSON_OBJ_ADD_NUMBER(json, "blocked_queries", counters->blocked);
JSON_OBJ_ADD_NUMBER(json, "percent_blocked", percent_blocked);
JSON_OBJ_ADD_NUMBER(json, "unique_domains", counters->domains);
JSON_OBJ_ADD_NUMBER(json, "forwarded_queries", counters->forwarded);
JSON_OBJ_ADD_NUMBER(json, "cached_queries", counters->cached);
JSON_OBJ_ADD_NUMBER(json, "privacy_level", config.privacylevel);
JSON_OBJ_ADD_NUMBER(json, "total_clients", counters->clients);
JSON_OBJ_ADD_NUMBER(json, "active_clients", activeclients);
cJSON *total_queries = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(total_queries, "A", counters->querytype[TYPE_A]);
JSON_OBJ_ADD_NUMBER(total_queries, "AAAA", counters->querytype[TYPE_AAAA]);
JSON_OBJ_ADD_NUMBER(total_queries, "ANY", counters->querytype[TYPE_ANY]);
JSON_OBJ_ADD_NUMBER(total_queries, "SRV", counters->querytype[TYPE_SRV]);
JSON_OBJ_ADD_NUMBER(total_queries, "SOA", counters->querytype[TYPE_SOA]);
JSON_OBJ_ADD_NUMBER(total_queries, "PTR", counters->querytype[TYPE_PTR]);
JSON_OBJ_ADD_NUMBER(total_queries, "TXT", counters->querytype[TYPE_TXT]);
JSON_OBJ_ADD_NUMBER(total_queries, "NAPTR", counters->querytype[TYPE_NAPTR]);
JSON_OBJ_ADD_ITEM(json, "total_queries", total_queries);
JSON_OBJ_ADD_NUMBER(json, "sum_queries", counters->queries);
cJSON *reply_types = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(reply_types, "NODATA", counters->reply_NODATA);
JSON_OBJ_ADD_NUMBER(reply_types, "NXDOMAIN", counters->reply_NXDOMAIN);
JSON_OBJ_ADD_NUMBER(reply_types, "CNAME", counters->reply_CNAME);
JSON_OBJ_ADD_NUMBER(reply_types, "IP", counters->reply_IP);
JSON_OBJ_ADD_NUMBER(reply_types, "domain", counters->reply_domain);
JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types);
JSON_SEND_OBJECT(json);
}
int api_stats_overTime_history(struct mg_connection *conn)
{
int from = 0, until = OVERTIME_SLOTS;
bool found = false;
time_t mintime = overTime[0].timestamp;
// Start with the first non-empty overTime slot
for(int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
if((overTime[slot].total > 0 || overTime[slot].blocked > 0) &&
overTime[slot].timestamp >= mintime)
{
from = slot;
found = true;
break;
}
}
// End with last non-empty overTime slot
for(int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
if(overTime[slot].timestamp >= time(NULL))
{
until = slot;
break;
}
}
// If there is no data to be sent, we send back an empty array
// and thereby return early
if(!found)
{
cJSON *json = JSON_NEW_ARRAY();
cJSON *item = JSON_NEW_OBJ();
JSON_ARRAY_ADD_ITEM(json, item);
JSON_SEND_OBJECT(json);
}
cJSON *json = JSON_NEW_ARRAY();
for(int slot = from; slot < until; slot++)
{
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(item, "timestamp", overTime[slot].timestamp);
JSON_OBJ_ADD_NUMBER(item, "total_queries", overTime[slot].total);
JSON_OBJ_ADD_NUMBER(item, "blocked_queries", overTime[slot].blocked);
JSON_ARRAY_ADD_ITEM(json, item);
}
JSON_SEND_OBJECT(json);
}
int api_stats_top_domains(bool blocked, struct mg_connection *conn)
{
int temparray[counters->domains][2], show = 10;
bool audit = false;
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
// Exit before processing any data if requested via config setting
get_privacy_level(NULL);
if(config.privacylevel >= PRIVACY_HIDE_DOMAINS)
{
if(config.debug & DEBUG_API)
logg("Not returning top domains: Privacy level is set to %i",
config.privacylevel);
// Minimum structure is
// {"top_domains":[]}
cJSON *json = JSON_NEW_OBJ();
cJSON *top_domains = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "top_domains", top_domains);
JSON_SEND_OBJECT(json);
}
// /api/stats/top_domains?blocked=true is allowed as well
const struct mg_request_info *request = mg_get_request_info(conn);
if(request->query_string != NULL)
{
// Should blocked clients be shown?
get_bool_var(request->query_string, "blocked", &blocked);
// Does the user request a non-default number of replies?
// Note: We do not accept zero query requests here
get_int_var(request->query_string, "show", &show);
// Apply Audit Log filtering?
get_bool_var(request->query_string, "audit", &audit);
}
for(int domainID=0; domainID < counters->domains; domainID++)
{
// Get domain pointer
const domainsData* domain = getDomain(domainID, true);
if(domain == NULL)
continue;
temparray[domainID][0] = domainID;
if(blocked)
temparray[domainID][1] = domain->blockedcount;
else
// Count only permitted queries
temparray[domainID][1] = (domain->count - domain->blockedcount);
}
// Sort temporary array
qsort(temparray, counters->domains, sizeof(int[2]), cmpdesc);
// Get filter
const 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);
}
}
int n = 0;
cJSON *top_domains = JSON_NEW_ARRAY();
for(int i=0; i < counters->domains; i++)
{
// Get sorted index
const int domainID = temparray[i][0];
// Get domain pointer
const domainsData* domain = getDomain(domainID, true);
if(domain == NULL)
continue;
// Skip this domain if there is a filter on it
if(excludedomains != NULL && insetupVarsArray(getstr(domain->domainpos)))
continue;
// Skip this domain if already audited
if(audit && in_auditlist(getstr(domain->domainpos)) > 0)
{
if(config.debug & DEBUG_API)
logg("API: %s has been audited.", getstr(domain->domainpos));
continue;
}
// Hidden domain, probably due to privacy level. Skip this in the top lists
if(strcmp(getstr(domain->domainpos), HIDDEN_DOMAIN) == 0)
continue;
int count = -1;
if(blocked && showblocked && domain->blockedcount > 0)
{
count = domain->blockedcount;
n++;
}
else if(!blocked && showpermitted && (domain->count - domain->blockedcount) > 0)
{
count = domain->count - domain->blockedcount;
n++;
}
if(count > -1)
{
cJSON *domain_item = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(domain_item, "domain", getstr(domain->domainpos));
JSON_OBJ_ADD_NUMBER(domain_item, "count", count);
JSON_ARRAY_ADD_ITEM(top_domains, domain_item);
}
// Only count entries that are actually sent and return when we have send enough data
if(n == show)
break;
}
if(excludedomains != NULL)
clearSetupVarsArray();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "top_domains", top_domains);
if(blocked)
{
JSON_OBJ_ADD_NUMBER(json, "blocked_queries", counters->blocked);
}
else
{
const int total_queries = counters->forwarded + counters->cached + counters->blocked;
JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries);
}
JSON_SEND_OBJECT(json);
}
int api_stats_top_clients(bool blocked, struct mg_connection *conn)
{
int temparray[counters->clients][2], show = 10;
bool includezeroclients = false;
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
// Exit before processing any data if requested via config setting
get_privacy_level(NULL);
if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS)
{
if(config.debug & DEBUG_API)
logg("Not returning top clients: Privacy level is set to %i",
config.privacylevel);
// Minimum structure is
// {"top_clients":[]}
cJSON *json = JSON_NEW_OBJ();
cJSON *top_clients = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "top_clients", top_clients);
JSON_SEND_OBJECT(json);
}
// /api/stats/top_clients9?blocked=true is allowed as well
const struct mg_request_info *request = mg_get_request_info(conn);
if(request->query_string != NULL)
{
// Should blocked clients be shown?
get_bool_var(request->query_string, "blocked", &blocked);
// Does the user request a non-default number of replies?
// Note: We do not accept zero query requests here
get_int_var(request->query_string, "show", &show);
// Show also clients which have not been active recently?
get_bool_var(request->query_string, "withzero", &includezeroclients);
}
for(int clientID = 0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
temparray[clientID][0] = clientID;
// Use either blocked or total count based on request string
temparray[clientID][1] = blocked ? client->blockedcount : client->count;
}
// Sort temporary array
qsort(temparray, counters->clients, sizeof(int[2]), cmpdesc);
// Get clients which the user doesn't want to see
const char* excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS");
if(excludeclients != NULL)
{
getSetupVarsArray(excludeclients);
}
int n = 0;
cJSON *top_clients = JSON_NEW_ARRAY();
for(int i=0; i < counters->clients; i++)
{
// Get sorted indices and counter values (may be either total or blocked count)
const int clientID = temparray[i][0];
const int count = temparray[i][1];
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
// Skip this client if there is a filter on it
if(excludeclients != NULL &&
(insetupVarsArray(getstr(client->ippos)) || insetupVarsArray(getstr(client->namepos))))
continue;
// Hidden client, probably due to privacy level. Skip this in the top lists
if(strcmp(getstr(client->ippos), HIDDEN_CLIENT) == 0)
continue;
// Get client IP and name
const char *client_ip = getstr(client->ippos);
const char *client_name = getstr(client->namepos);
// 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 || count > 0)
{
cJSON *client_item = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(client_item, "name", client_name);
JSON_OBJ_REF_STR(client_item, "ip", client_ip);
JSON_OBJ_ADD_NUMBER(client_item, "count", count);
JSON_ARRAY_ADD_ITEM(top_clients, client_item);
n++;
}
if(n == show)
break;
}
if(excludeclients != NULL)
clearSetupVarsArray();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "top_clients", top_clients);
if(blocked)
{
JSON_OBJ_ADD_NUMBER(json, "blocked_queries", counters->blocked);
}
else
{
const int total_queries = counters->forwarded + counters->cached + counters->blocked;
JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries);
}
JSON_SEND_OBJECT(json);
}
int api_stats_upstreams(struct mg_connection *conn)
{
int temparray[counters->forwarded][2];
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
for(int upstreamID = 0; upstreamID < counters->upstreams; upstreamID++)
{
// Get forward pointer
const upstreamsData* forward = getUpstream(upstreamID, true);
if(forward == NULL)
continue;
temparray[upstreamID][0] = upstreamID;
temparray[upstreamID][1] = forward->count;
}
// Sort temporary array in descending order
qsort(temparray, counters->forwarded, sizeof(int[2]), cmpdesc);
// Loop over available forward destinations
cJSON *upstreams = JSON_NEW_ARRAY();
for(int i = -2; i < min(counters->upstreams, 8); i++)
{
int count = 0;
const char* ip, *name;
double responsetime = 0.0, uncertainty = 0.0;
if(i == -2)
{
// Blocked queries (local lists)
ip = "blocklist";
name = ip;
count = counters->blocked;
}
else if(i == -1)
{
// Local cache
ip = "cache";
name = ip;
count = counters->cached;
}
else
{
// Regular forward destionation
// Get sorted indices
const int upstreamID = temparray[i][0];
// Get forward pointer
const upstreamsData* forward = getUpstream(upstreamID, true);
if(forward == NULL)
continue;
// Get IP and host name of forward destination if available
ip = getstr(forward->ippos);
name = getstr(forward->namepos);
// Get percentage
count = forward->count;
// Compute average response time and uncertainty (unit: seconds)
if(forward->responses > 0)
{
// Wehave to multiply runcertainty by 1e-4 to get seconds
responsetime = 1e-4 * forward->rtime / forward->responses;
}
if(forward->responses > 1)
{
// The actual value will be somewhere in a neighborhood around the mean value.
// This neighborhood of values is the uncertainty in the mean.
// Wehave to multiply runcertainty by (1e-4)^2 to get seconds
uncertainty = my_sqrt(1e-8 * forward->rtuncertainty / forward->responses / (forward->responses-1));
}
}
// Send data:
// - always if i < 0 (special upstreams: blocklist and cache)
// - only if there are any queries for all others (i > 0)
if(count > 0 || i < 0)
{
cJSON *upstream = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(upstream, "name", name);
JSON_OBJ_REF_STR(upstream, "ip", ip);
JSON_OBJ_ADD_NUMBER(upstream, "count", count);
JSON_OBJ_ADD_NUMBER(upstream, "responsetime", responsetime);
JSON_OBJ_ADD_NUMBER(upstream, "uncertainty", uncertainty);
JSON_ARRAY_ADD_ITEM(upstreams, upstream);
}
}
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "upstreams", upstreams);
JSON_OBJ_ADD_NUMBER(json, "forwarded_queries", counters->forwarded);
const int total_queries = counters->forwarded + counters->cached + counters->blocked;
JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries);
JSON_SEND_OBJECT(json);
}
int api_stats_query_types(struct mg_connection *conn)
{
cJSON *json = JSON_NEW_ARRAY();
for(int i = TYPE_A; i < TYPE_MAX; i++)
{
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(item, "name", querytypes[i]);
JSON_OBJ_ADD_NUMBER(item, "count", counters->querytype[i]);
JSON_ARRAY_ADD_ITEM(json, item);
}
JSON_SEND_OBJECT(json);
}
int api_stats_history(struct mg_connection *conn)
{
// Exit before processing any data if requested via config setting
get_privacy_level(NULL);
if(config.privacylevel >= PRIVACY_MAXIMUM)
{
// Minimum structure is
// {"history":[{}]}
cJSON *json = JSON_NEW_OBJ();
cJSON *history = JSON_NEW_ARRAY();
cJSON *item = JSON_NEW_OBJ();
JSON_ARRAY_ADD_ITEM(history, item);
JSON_OBJ_ADD_ITEM(json, "history", history);
// There are no more queries available, send NULL cursor
JSON_OBJ_ADD_NULL(json, "cursor");
JSON_SEND_OBJECT(json);
}
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
// Do we want a more specific version of this command (domain/client/time interval filtered)?
unsigned int from = 0, until = 0;
char *domainname = NULL;
bool filterdomainname = false;
int domainid = -1;
char *clientname = NULL;
bool filterclientname = false;
int clientid = -1;
int querytype = 0;
char *forwarddest = NULL;
bool filterforwarddest = false;
int forwarddestid = 0;
// We start with the most recent query at the beginning (until the cursor is changed)
unsigned int cursor = counters->queries;
// We send 200 queries (until the API is asked for a different limit)
unsigned int show = 200u;
const struct mg_request_info *request = mg_get_request_info(conn);
if(request->query_string != NULL)
{
// Time filtering?
get_uint_var(request->query_string, "from", &from);
get_uint_var(request->query_string, "until", &until);
// Query type filtering?
int num;
if(get_int_var(request->query_string, "querytype", &num) && num < TYPE_MAX)
querytype = num;
// Does the user request a non-default number of replies?
// Note: We do not accept zero query requests here
get_uint_var(request->query_string, "show", &show);
// Forward destination filtering?
char buffer[256] = { 0 };
if(GET_VAR("forward", buffer, request->query_string) > 0)
{
forwarddest = calloc(256, sizeof(char));
if(forwarddest == NULL)
{
return false;
}
sscanf(buffer, "%255s", forwarddest);
filterforwarddest = true;
if(strcmp(forwarddest, "cache") == 0)
{
forwarddestid = -1;
}
else if(strcmp(forwarddest, "blocklist") == 0)
{
forwarddestid = -2;
}
else
{
// Iterate through all known forward destinations
forwarddestid = -3;
for(int i = 0; i < counters->forwarded; i++)
{
// Get forward pointer
const upstreamsData* forward = getUpstream(i, true);
if(forward == NULL)
{
continue;
}
// Try to match the requested string against their IP addresses and
// (if available) their host names
if(strcmp(getstr(forward->ippos), forwarddest) == 0 ||
(forward->namepos != 0 &&
strcmp(getstr(forward->namepos), forwarddest) == 0))
{
forwarddestid = i;
break;
}
}
if(forwarddestid < 0)
{
// Requested upstream has not been found, we directly
// tell the user here as there is no data to be returned
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_COPY_STR(json, "upstream", forwarddest);
free(forwarddest);
return send_json_error(conn, 400,
"bad_request",
"Requested upstream not found",
json);
}
}
}
// Domain filtering?
if(GET_VAR("domain", buffer, request->query_string) > 0)
{
domainname = calloc(512, sizeof(char));
if(domainname == NULL)
{
return false;
}
sscanf(buffer, "%511s", domainname);
filterdomainname = true;
// Iterate through all known domains
for(int domainID = 0; domainID < counters->domains; domainID++)
{
// Get domain pointer
const domainsData* domain = getDomain(domainID, true);
if(domain == NULL)
{
continue;
}
// Try to match the requested string
if(strcmp(getstr(domain->domainpos), domainname) == 0)
{
domainid = domainID;
break;
}
}
if(domainid < 0)
{
// Requested domain has not been found, we directly
// tell the user here as there is no data to be returned
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_COPY_STR(json, "domain", domainname);
free(domainname);
return send_json_error(conn, 400,
"bad_request",
"Requested domain not found",
json);
}
}
// Client filtering?
if(GET_VAR("client", buffer, request->query_string) > 0)
{
clientname = calloc(512, sizeof(char));
if(clientname == NULL)
{
return false;
}
sscanf(buffer, "%511s", clientname);
filterclientname = true;
// Iterate through all known clients
for(int i = 0; i < counters->clients; i++)
{
// Get client pointer
const clientsData* client = getClient(i, true);
if(client == NULL)
{
continue;
}
// Try to match the requested string
if(strcmp(getstr(client->ippos), clientname) == 0 ||
(client->namepos != 0 &&
strcmp(getstr(client->namepos), clientname) == 0))
{
clientid = i;
break;
}
}
if(clientid < 0)
{
// Requested client has not been found, we directly
// tell the user here as there is no data to be returned
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_COPY_STR(json, "client", clientname);
free(clientname);
return send_json_error(conn, 400,
"bad_request",
"Requested client not found",
json);
}
}
unsigned int unum = 0u;
if(GET_VAR("cursor", buffer, request->query_string) > 0 &&
sscanf(buffer, "%u", &unum) > 0)
{
// Do not start at the most recent, but at an older query
if(unum < (unsigned int)counters->queries)
{
cursor = unum;
}
else
{
// Cursors larger than the current known number
// of queries are invalid
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "cursor", unum);
JSON_OBJ_ADD_NUMBER(json, "maxval", counters->queries);
free(clientname);
return send_json_error(conn, 400,
"bad_request",
"Requested cursor larger than number of queries",
json);
}
}
}
// Compute limits for the main for-loop
// Default: Show the most recent 200 queries
unsigned int ibeg = cursor;
// 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();
cJSON *history = JSON_NEW_ARRAY();
unsigned int added = 0u;
unsigned int lastID = 0u;
for(unsigned int i = ibeg; i > 0u; i--)
{
const unsigned int queryID = i-1u;
const queriesData* query = getQuery(queryID, true);
// Check if this query has been create while in maximum privacy mode
if(query == NULL || query->privacylevel >= PRIVACY_MAXIMUM)
continue;
// Verify query type
if(query->type >= TYPE_MAX)
continue;
// 1 = gravity.list, 4 = wildcard, 5 = black.list
if((query->status == QUERY_GRAVITY ||
query->status == QUERY_REGEX ||
query->status == QUERY_BLACKLIST ||
query->status == QUERY_GRAVITY_CNAME ||
query->status == QUERY_REGEX_CNAME ||
query->status == QUERY_BLACKLIST_CNAME) && !showblocked)
continue;
// 2 = forwarded, 3 = cached
if((query->status == QUERY_FORWARDED ||
query->status == QUERY_CACHE) && !showpermitted)
continue;
// Skip those entries which so not meet the requested timeframe
if((from > (unsigned int)query->timestamp && from != 0) || ((unsigned int)query->timestamp > until && until != 0))
continue;
// Skip if domain is not identical with what the user wants to see
if(filterdomainname && query->domainID != domainid)
continue;
if(filterdomainname)
{
// Check direct match
if(query->domainID == domainid)
{
// Get this query
}
// If the domain of this query did not match, the CNAME
// domain may still match - we have to check it in
// addition if this query is of CNAME blocked type
else if((query->status == QUERY_GRAVITY_CNAME ||
query->status == QUERY_BLACKLIST_CNAME ||
query->status == QUERY_REGEX_CNAME) &&
query->CNAME_domainID == domainid)
{
// Get this query
}
else
{
// Skip this query
continue;
}
}
// Skip if client name and IP are not identical with what the user wants to see
if(filterclientname && query->clientID != clientid)
continue;
// Skip if query type is not identical with what the user wants to see
if(querytype != 0 && querytype != query->type)
continue;
if(filterforwarddest)
{
// Does the user want to see queries answered from blocking lists?
if(forwarddestid == -2 && query->status != QUERY_GRAVITY
&& query->status != QUERY_REGEX
&& query->status != QUERY_BLACKLIST
&& query->status != QUERY_GRAVITY_CNAME
&& query->status != QUERY_REGEX_CNAME
&& query->status != QUERY_BLACKLIST_CNAME)
continue;
// Does the user want to see queries answered from local cache?
else if(forwarddestid == -1 && query->status != QUERY_CACHE)
continue;
// Does the user want to see queries answered by an upstream server?
else if(forwarddestid >= 0 && forwarddestid != query->upstreamID)
continue;
}
// Ask subroutine for domain. It may return "hidden" depending on
// the privacy settings at the time the query was made
const char *domain = getDomainString(query);
// Similarly for the client
const char *clientIPName = NULL;
// Get client pointer
const clientsData* client = getClient(query->clientID, true);
if(domain == NULL || client == NULL)
continue;
if(strlen(getstr(client->namepos)) > 0)
clientIPName = getClientNameString(query);
else
clientIPName = getClientIPString(query);
unsigned long delay = query->response;
// Check if received (delay should be smaller than 30min)
if(delay > 1.8e7)
delay = 0;
// Get domain blocked during deep CNAME inspection, if applicable
const char *CNAME_domain = "N/A";
if(query->CNAME_domainID > -1)
{
CNAME_domain = getCNAMEDomainString(query);
}
// Get ID of blocking regex, if applicable
int regex_idx = -1;
if (query->status == QUERY_REGEX || query->status == QUERY_REGEX_CNAME)
{
unsigned int cacheID = findCacheID(query->domainID, query->clientID, query->type);
DNSCacheData *dns_cache = getDNSCache(cacheID, true);
if(dns_cache != NULL)
regex_idx = dns_cache->black_regex_idx;
}
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(item, "timestamp", query->timestamp);
JSON_OBJ_ADD_NUMBER(item, "type", query->type);
JSON_OBJ_ADD_NUMBER(item, "status", query->status);
JSON_OBJ_COPY_STR(item, "domain", domain);
JSON_OBJ_COPY_STR(item, "client", clientIPName);
JSON_OBJ_ADD_NUMBER(item, "dnssec", query->dnssec);
JSON_OBJ_ADD_NUMBER(item, "reply", query->reply);
JSON_OBJ_ADD_NUMBER(item, "response_time", delay);
JSON_OBJ_COPY_STR(item, "CNAME_domain", CNAME_domain);
JSON_OBJ_ADD_NUMBER(item, "regex_idx", regex_idx);
if(config.debug & DEBUG_API)
JSON_OBJ_ADD_NUMBER(item, "queryID", queryID);
JSON_ARRAY_ADD_ITEM(history, item);
if(++added > show)
{
break;
}
lastID = queryID;
}
// Free allocated memory
if(filterclientname)
free(clientname);
if(filterdomainname)
free(domainname);
if(filterforwarddest)
free(forwarddest);
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "history", history);
if(lastID > 0)
{
// There are more queries available, send cursor pointing
// onto the next older query so the API can request it if
// needed
JSON_OBJ_ADD_NUMBER(json, "cursor", lastID);
}
else
{
// There are no more queries available, send NULL cursor
JSON_OBJ_ADD_NULL(json, "cursor");
}
JSON_SEND_OBJECT(json);
}
int api_stats_recentblocked(struct mg_connection *conn)
{
unsigned int show = 1;
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
// Exit before processing any data if requested via config setting
get_privacy_level(NULL);
if(config.privacylevel >= PRIVACY_HIDE_DOMAINS)
{
// Minimum structure is
// {"blocked":null}
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NULL(json, "blocked");
JSON_SEND_OBJECT(json);
}
const struct mg_request_info *request = mg_get_request_info(conn);
if(request->query_string != NULL)
{
// Does the user request a non-default number of replies?
// Note: We do not accept zero query requests here
get_uint_var(request->query_string, "show", &show);
}
// Find most recently blocked query
unsigned int found = 0;
cJSON *blocked = JSON_NEW_ARRAY();
for(int queryID = counters->queries - 1; queryID > 0 ; queryID--)
{
const queriesData* query = getQuery(queryID, true);
if(query == NULL)
{
continue;
}
if(query->status == QUERY_GRAVITY ||
query->status == QUERY_REGEX ||
query->status == QUERY_BLACKLIST ||
query->status == QUERY_EXTERNAL_BLOCKED_IP ||
query->status == QUERY_EXTERNAL_BLOCKED_NULL ||
query->status == QUERY_EXTERNAL_BLOCKED_NXRA ||
query->status == QUERY_GRAVITY_CNAME ||
query->status == QUERY_REGEX_CNAME ||
query->status == QUERY_BLACKLIST_CNAME)
{
found++;
// Ask subroutine for domain. It may return "hidden" depending on
// the privacy settings at the time the query was made
const char *domain = getDomainString(query);
if(domain == NULL)
{
continue;
}
JSON_ARRAY_REF_STR(blocked, domain);
}
if(found >= show)
break;
}
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "blocked", blocked);
JSON_SEND_OBJECT(json);
}
int api_stats_overTime_clients(struct mg_connection *conn)
{
int sendit = -1, until = OVERTIME_SLOTS;
// Verify requesting client is allowed to see this ressource
if(check_client_auth(conn) < 0)
{
return send_json_unauthorized(conn);
}
// Exit before processing any data if requested via config setting
get_privacy_level(NULL);
if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS)
{
// Minimum structure is
// {"over_time":[], "clients":[]}
cJSON *json = JSON_NEW_OBJ();
cJSON *over_time = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "over_time", over_time);
cJSON *clients = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "clients", clients);
JSON_SEND_OBJECT(json);
}
// Find minimum ID to send
for(int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
if((overTime[slot].total > 0 || overTime[slot].blocked > 0) &&
overTime[slot].timestamp >= overTime[0].timestamp)
{
sendit = slot;
break;
}
}
if(sendit < 0)
{
// Minimum structure is
// {"over_time":[], "clients":[]}
cJSON *json = JSON_NEW_OBJ();
cJSON *over_time = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "over_time", over_time);
cJSON *clients = JSON_NEW_ARRAY();
JSON_OBJ_ADD_ITEM(json, "clients", clients);
JSON_SEND_OBJECT(json);
}
// Find minimum ID to send
for(int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
if(overTime[slot].timestamp >= time(NULL))
{
until = slot;
break;
}
}
// 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(int clientID=0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
// Check if this client should be skipped
if(insetupVarsArray(getstr(client->ippos)) ||
insetupVarsArray(getstr(client->namepos)))
skipclient[clientID] = true;
}
}
cJSON *over_time = JSON_NEW_ARRAY();
// Main return loop
for(int slot = sendit; slot < until; slot++)
{
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(item, "timestamp", overTime[slot].timestamp);
// Loop over clients to generate output to be sent to the client
cJSON *data = JSON_NEW_ARRAY();
for(int clientID = 0; clientID < counters->clients; clientID++)
{
if(skipclient[clientID])
continue;
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
const int thisclient = client->overTime[slot];
JSON_ARRAY_ADD_NUMBER(data, thisclient);
}
JSON_OBJ_ADD_ITEM(item, "data", data);
JSON_ARRAY_ADD_ITEM(over_time, item);
}
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "over_time", over_time);
cJSON *clients = JSON_NEW_ARRAY();
// Loop over clients to generate output to be sent to the client
for(int clientID = 0; clientID < counters->clients; clientID++)
{
if(skipclient[clientID])
continue;
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
const char *client_ip = getstr(client->ippos);
const char *client_name = getstr(client->namepos);
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(item, "name", client_name);
JSON_OBJ_REF_STR(item, "ip", client_ip);
JSON_ARRAY_ADD_ITEM(clients, item);
}
JSON_OBJ_ADD_ITEM(json, "clients", clients);
if(excludeclients != NULL)
clearSetupVarsArray();
JSON_SEND_OBJECT(json);
}