mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
Remove HTTP API specific code so it can be replaced with a new protocol
Adjusted tests to fit the current lack of output on the unix socket. Added TELNET enum and used it in place of the old SOCKET to better fit with the rest of the code base. Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
This commit is contained in:
@@ -203,8 +203,8 @@ typedef struct {
|
||||
int querytypedata;
|
||||
} memoryStruct;
|
||||
|
||||
enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD, AUTHDATA };
|
||||
enum { SOCKET, API, APIH };
|
||||
enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD };
|
||||
enum { TELNET, SOCKET };
|
||||
enum { WHITELIST, BLACKLIST, WILDLIST };
|
||||
enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED, DNSSEC_UNKNOWN };
|
||||
|
||||
|
||||
@@ -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 api.h version.h cJSON.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 api_stats.o api_dns.o cJSON.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 api_stats.o api_dns.o
|
||||
|
||||
# Get git commit version and date
|
||||
GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p')
|
||||
|
||||
@@ -10,181 +10,6 @@
|
||||
|
||||
#include "FTL.h"
|
||||
#include "api.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
void sendAPIResponse(int sock, char type, char http_code) {
|
||||
sendAPIResponseWithCookie(sock, type, http_code, NULL);
|
||||
}
|
||||
|
||||
void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session) {
|
||||
char *http_status;
|
||||
|
||||
switch(http_code) {
|
||||
default:
|
||||
case OK:
|
||||
http_status = "200 OK";
|
||||
break;
|
||||
case BAD_REQUEST:
|
||||
http_status = "400 Bad Request";
|
||||
break;
|
||||
case INTERNAL_ERROR:
|
||||
http_status = "500 Internal Server Error";
|
||||
break;
|
||||
case NOT_FOUND:
|
||||
http_status = "404 Not Found";
|
||||
break;
|
||||
case UNAUTHORIZED:
|
||||
http_status = "401 Unauthorized";
|
||||
break;
|
||||
}
|
||||
|
||||
// Send header only for full HTTP requests
|
||||
if(type == APIH)
|
||||
{
|
||||
if(session == NULL) {
|
||||
// No cookie to send
|
||||
ssend(sock,
|
||||
"HTTP/1.0 %s\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n"
|
||||
"Content-Type: application/json\n\n{", http_status);
|
||||
}
|
||||
else {
|
||||
// Send cookie
|
||||
ssend(sock,
|
||||
"HTTP/1.0 %s\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n"
|
||||
"Set-Cookie: FTL_SESSION=%ld\nContent-Type: application/json\n\n{", http_status, *session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// session will have the client's valid session written to, if it's not unauthorized
|
||||
enum Auth authenticate(char *with_headers, char *payload, long *session, int sock) {
|
||||
// First figure out if the client has authenticated before.
|
||||
char *sessionStr;
|
||||
AuthData *auth = NULL;
|
||||
|
||||
// Find the cookie header (will contain a long int value)
|
||||
if(strstr(with_headers, "Cookie: ") != NULL && (sessionStr = strstr(with_headers, "FTL_SESSION=")) != NULL) {
|
||||
// Find the start of the cookie (strtol will stop once it gets to a non-numeric character)
|
||||
sessionStr += 12;
|
||||
|
||||
// Convert to int
|
||||
*session = strtol(sessionStr, NULL, 10);
|
||||
|
||||
if(errno == ERANGE) {
|
||||
logg("Failed to decode the authentication cookie");
|
||||
return AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i = 0; i < authLength; i++) {
|
||||
// Check if the authentication is still valid, has the same session token, and is coming from the same IP
|
||||
if(authData[i].valid && authData[i].session == *session && strcmp(clientip[sock], authData[i].ip) == 0) {
|
||||
auth = &authData[i];
|
||||
time_t currentTime = time(NULL);
|
||||
|
||||
// Check to see if the session had expired (24 minutes)
|
||||
if(currentTime > auth->lastQueryTime + 1440) {
|
||||
authData[i].valid = false;
|
||||
return AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
auth->lastQueryTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
// auth will be null if we didn't find a matching session
|
||||
if(auth == NULL)
|
||||
return AUTH_UNAUTHORIZED;
|
||||
return AUTH_PREVIOUS;
|
||||
}
|
||||
|
||||
// Otherwise, check if they are trying to authenticate
|
||||
cJSON *input_root = cJSON_Parse(payload);
|
||||
cJSON *password_json = cJSON_GetObjectItemCaseSensitive(input_root, "password");
|
||||
|
||||
if(!cJSON_IsString(password_json)) {
|
||||
cJSON_Delete(input_root);
|
||||
return AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
char *password = password_json->valuestring;
|
||||
|
||||
// todo: use real password
|
||||
if(strcmp(password, "password") == 0) {
|
||||
auth = malloc(sizeof(AuthData));
|
||||
|
||||
auth->lastQueryTime = time(NULL);
|
||||
auth->ip = strdup(clientip[sock]);
|
||||
|
||||
// Find a unique session number
|
||||
while(true) {
|
||||
auth->session = random();
|
||||
|
||||
bool unique = true;
|
||||
int i;
|
||||
for(i = 0; i < authLength; i++) {
|
||||
if(authData[i].session == auth->session) {
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Found a unique session number
|
||||
if(unique)
|
||||
break;
|
||||
}
|
||||
|
||||
auth->valid = true;
|
||||
|
||||
// Add to auth storage
|
||||
bool found = false;
|
||||
int i;
|
||||
for(i = 0; i < authLength; i++) {
|
||||
if(!authData[i].valid) {
|
||||
// Found an invalid auth we can reuse
|
||||
found = true;
|
||||
free(authData[i].ip);
|
||||
authData[i] = *auth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
// Couldn't reuse any existing auth structures
|
||||
memory_check(AUTHDATA);
|
||||
authData[authLength] = *auth;
|
||||
authLength++;
|
||||
}
|
||||
|
||||
*session = auth->session;
|
||||
free(auth);
|
||||
cJSON_Delete(input_root);
|
||||
|
||||
return AUTH_NEW;
|
||||
}
|
||||
|
||||
cJSON_Delete(input_root);
|
||||
|
||||
return AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
char* getPayload(char *http_message) {
|
||||
char *data_start;
|
||||
char *unix_newline = strstr(http_message, "\n\n");
|
||||
char *win_newline = strstr(http_message, "\r\n\r\n");
|
||||
|
||||
if(unix_newline != NULL)
|
||||
data_start = unix_newline + 2;
|
||||
else if(win_newline != NULL)
|
||||
data_start = win_newline + 4;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
if(strlen(data_start) == 0)
|
||||
return NULL;
|
||||
|
||||
return data_start;
|
||||
}
|
||||
|
||||
bool matchesRegex(char *regex_expression, char *input) {
|
||||
regex_t regex;
|
||||
|
||||
@@ -8,50 +8,33 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
// Endpoints under /stats/
|
||||
// Statistic methods
|
||||
void getStats(int *sock, char type);
|
||||
void getOverTime(int *sock, char type);
|
||||
void getTopDomains (char *client_message, int *sock, char type);
|
||||
void getTopClients(char *client_message, int *sock, char type);
|
||||
void getForwardDestinations(char *client_message, int *sock, char type);
|
||||
|
||||
void getQueryTypes(int *sock, char type);
|
||||
void getAllQueries(char *client_message, int *sock, char type);
|
||||
void getRecentBlocked(char *client_message, int *sock, char type);
|
||||
void getMemoryUsage(int *sock, char type);
|
||||
void getForwardDestinationsOverTime(int *sock, char type);
|
||||
void getClientID(int *sock, char type);
|
||||
void getQueryTypesOverTime(int *sock, char type);
|
||||
void getVersion(int *sock, char type);
|
||||
void getDBstats(int *sock, char type);
|
||||
void getClientsOverTime(int *sock);
|
||||
void getClientNames(int *sock);
|
||||
|
||||
// FTL methods
|
||||
void getMemoryUsage(int *sock, char type);
|
||||
void getClientID(int *sock, char type);
|
||||
void getVersion(int *sock, char type);
|
||||
void getDBstats(int *sock, char type);
|
||||
void getUnknownQueries(int *sock);
|
||||
|
||||
// Endpoints under /dns/
|
||||
// DNS methods
|
||||
void getList(int *sock, char type, char list_type);
|
||||
void addList(int *sock, char type, char list_type, char *data);
|
||||
void removeList(int *sock, char type, char list_type, char *client_message);
|
||||
void getPiholeStatus(int *sock, char type);
|
||||
|
||||
// HTTP Response Codes
|
||||
enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND, UNAUTHORIZED };
|
||||
|
||||
// Authentication
|
||||
typedef struct {
|
||||
time_t lastQueryTime;
|
||||
long session;
|
||||
char *ip;
|
||||
bool valid;
|
||||
} AuthData;
|
||||
AuthData *authData;
|
||||
int authLength;
|
||||
enum Auth { AUTH_UNAUTHORIZED, AUTH_PREVIOUS, AUTH_NEW };
|
||||
|
||||
// General API commands
|
||||
enum Auth authenticate(char *with_headers, char *payload, long *session, int sock);
|
||||
char* getPayload(char *http_message);
|
||||
void sendAPIResponse(int sock, char type, char http_code);
|
||||
void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session);
|
||||
bool matchesRegex(char *regex_expression, char *input);
|
||||
bool isValidDomain(char *domain);
|
||||
|
||||
@@ -32,7 +32,7 @@ void getList(int *sock, char type, char list_type) {
|
||||
name = "wildlist";
|
||||
}
|
||||
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"%s\":[", name);
|
||||
|
||||
if((fp = fopen(file, "r")) != NULL)
|
||||
@@ -111,28 +111,28 @@ void getList(int *sock, char type, char list_type) {
|
||||
|
||||
void getPiholeStatus(int *sock, char type) {
|
||||
int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf);
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1);
|
||||
}
|
||||
|
||||
void addList(int *sock, char type, char list_type, char *data) {
|
||||
cJSON *input_root = cJSON_Parse(data);
|
||||
cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain");
|
||||
// cJSON *input_root = cJSON_Parse(data);
|
||||
// cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain");
|
||||
char *domain;
|
||||
|
||||
// Validate domain
|
||||
if(!cJSON_IsString(domain_json)) {
|
||||
// No domain found
|
||||
sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
ssend(*sock, "\"status\":\"no_domain\"");
|
||||
return;
|
||||
}
|
||||
// if(!cJSON_IsString(domain_json)) {
|
||||
// // No domain found
|
||||
// sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
// ssend(*sock, "\"status\":\"no_domain\"");
|
||||
// return;
|
||||
// }
|
||||
|
||||
domain = domain_json->valuestring;
|
||||
// domain = domain_json->valuestring;
|
||||
|
||||
if(!isValidDomain(domain)) {
|
||||
// Invalid domain
|
||||
sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
// sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
ssend(*sock, "\"status\":\"invalid_domain\"");
|
||||
return;
|
||||
}
|
||||
@@ -160,16 +160,16 @@ void addList(int *sock, char type, char list_type, char *data) {
|
||||
|
||||
if(return_code == 0) {
|
||||
// Successfully added to list
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"status\":\"success\"");
|
||||
}
|
||||
else {
|
||||
// Failed to add to list
|
||||
sendAPIResponse(*sock, type, INTERNAL_ERROR);
|
||||
// sendAPIResponse(*sock, type, INTERNAL_ERROR);
|
||||
ssend(*sock, "\"status\":\"unknown_error\"");
|
||||
}
|
||||
|
||||
cJSON_Delete(input_root);
|
||||
// cJSON_Delete(input_root);
|
||||
}
|
||||
|
||||
void removeList(int *sock, char type, char list_type, char *client_message) {
|
||||
@@ -178,7 +178,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) {
|
||||
// Validate domain
|
||||
if(domain == NULL) {
|
||||
// No domain found
|
||||
sendAPIResponse(*sock, type, NOT_FOUND);
|
||||
// sendAPIResponse(*sock, type, NOT_FOUND);
|
||||
ssend(*sock, "\"status\":\"not_found\"");
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) {
|
||||
if(!strstr(client_message, expected_route)) {
|
||||
// Invalid route
|
||||
free(expected_route);
|
||||
sendAPIResponse(*sock, type, NOT_FOUND);
|
||||
// sendAPIResponse(*sock, type, NOT_FOUND);
|
||||
ssend(*sock, "\"status\":\"not_found\"");
|
||||
return;
|
||||
}
|
||||
@@ -217,7 +217,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) {
|
||||
|
||||
if(!isValidDomain(domain)) {
|
||||
// Invalid domain
|
||||
sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
// sendAPIResponse(*sock, type, BAD_REQUEST);
|
||||
ssend(*sock, "\"status\":\"invalid_domain\"");
|
||||
return;
|
||||
}
|
||||
@@ -241,12 +241,12 @@ void removeList(int *sock, char type, char list_type, char *client_message) {
|
||||
|
||||
if(return_code == 0) {
|
||||
// Successfully removed from list
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"status\":\"success\"");
|
||||
}
|
||||
else {
|
||||
// Failed to remove from list
|
||||
sendAPIResponse(*sock, type, INTERNAL_ERROR);
|
||||
// sendAPIResponse(*sock, type, INTERNAL_ERROR);
|
||||
ssend(*sock, "\"status\":\"unknown_error\"");
|
||||
}
|
||||
}
|
||||
|
||||
+173
-173
@@ -59,7 +59,7 @@ void getStats(int *sock, char type)
|
||||
switch(blockingstatus)
|
||||
{
|
||||
case 0: // Blocking disabled
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
strncpy(domains_blocked, "N/A", 4);
|
||||
else
|
||||
strncpy(domains_blocked, "\"N/A\"", 6);
|
||||
@@ -85,7 +85,7 @@ void getStats(int *sock, char type)
|
||||
activeclients++;
|
||||
}
|
||||
|
||||
if(type == SOCKET) {
|
||||
if(type == TELNET) {
|
||||
ssend(*sock, "domains_being_blocked %s\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n",
|
||||
domains_blocked, total, blocked, percentage);
|
||||
ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n",
|
||||
@@ -96,30 +96,30 @@ void getStats(int *sock, char type)
|
||||
}
|
||||
else
|
||||
{
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(
|
||||
*sock,
|
||||
"\"domains_being_blocked\":%s,"
|
||||
"\"dns_queries_today\":%i,"
|
||||
"\"ads_blocked_today\":%i,"
|
||||
"\"ads_percentage_today\":%.4f,"
|
||||
"\"unique_domains\":%i,"
|
||||
"\"queries_forwarded\":%i,"
|
||||
"\"queries_cached\":%i,"
|
||||
"\"clients_ever_seen\":%i,"
|
||||
"\"unique_clients\":%i,"
|
||||
"\"status\":\"%s\"",
|
||||
domains_blocked,
|
||||
total,
|
||||
blocked,
|
||||
percentage,
|
||||
counters.domains,
|
||||
counters.forwardedqueries,
|
||||
counters.cached,
|
||||
counters.clients,
|
||||
activeclients,
|
||||
status
|
||||
);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(
|
||||
// *sock,
|
||||
// "\"domains_being_blocked\":%s,"
|
||||
// "\"dns_queries_today\":%i,"
|
||||
// "\"ads_blocked_today\":%i,"
|
||||
// "\"ads_percentage_today\":%.4f,"
|
||||
// "\"unique_domains\":%i,"
|
||||
// "\"queries_forwarded\":%i,"
|
||||
// "\"queries_cached\":%i,"
|
||||
// "\"clients_ever_seen\":%i,"
|
||||
// "\"unique_clients\":%i,"
|
||||
// "\"status\":\"%s\"",
|
||||
// domains_blocked,
|
||||
// total,
|
||||
// blocked,
|
||||
// percentage,
|
||||
// counters.domains,
|
||||
// counters.forwardedqueries,
|
||||
// counters.cached,
|
||||
// counters.clients,
|
||||
// activeclients,
|
||||
// status
|
||||
// );
|
||||
}
|
||||
|
||||
if(debugclients)
|
||||
@@ -142,7 +142,7 @@ void getOverTime(int *sock, char type)
|
||||
}
|
||||
|
||||
// Send data in socket format if requested
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
for(i = j; i < counters.overTime; i++)
|
||||
{
|
||||
@@ -151,25 +151,25 @@ void getOverTime(int *sock, char type)
|
||||
}
|
||||
else
|
||||
{
|
||||
// First send header with unspecified content-length outside of the for-loop
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock,"\"domains_over_time\":{");
|
||||
|
||||
// Send "domains_over_time" data
|
||||
for(i = j; i < counters.overTime; i++)
|
||||
{
|
||||
if(i != j) ssend(*sock, ",");
|
||||
ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total);
|
||||
}
|
||||
ssend(*sock,"},\"ads_over_time\":{");
|
||||
|
||||
// Send "ads_over_time" data
|
||||
for(i = j; i < counters.overTime; i++)
|
||||
{
|
||||
if(i != j) ssend(*sock, ",");
|
||||
ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked);
|
||||
}
|
||||
ssend(*sock,"}");
|
||||
// // First send header with unspecified content-length outside of the for-loop
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock,"\"domains_over_time\":{");
|
||||
//
|
||||
// // Send "domains_over_time" data
|
||||
// for(i = j; i < counters.overTime; i++)
|
||||
// {
|
||||
// if(i != j) ssend(*sock, ",");
|
||||
// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total);
|
||||
// }
|
||||
// ssend(*sock,"},\"ads_over_time\":{");
|
||||
//
|
||||
// // Send "ads_over_time" data
|
||||
// for(i = j; i < counters.overTime; i++)
|
||||
// {
|
||||
// if(i != j) ssend(*sock, ",");
|
||||
// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked);
|
||||
// }
|
||||
// ssend(*sock,"}");
|
||||
}
|
||||
|
||||
if(debugclients)
|
||||
@@ -181,7 +181,7 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
int i, temparray[counters.domains][2], count=10, num;
|
||||
bool blocked, audit = false, desc = false;
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
blocked = command(client_message, ">top-ads");
|
||||
else
|
||||
blocked = command(client_message, "/top_ads");
|
||||
@@ -191,9 +191,9 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
return;
|
||||
|
||||
// Match both top-domains and top-ads
|
||||
// SOCKET: >top-domains (15)
|
||||
// TELNET: >top-domains (15)
|
||||
// API: /stats/top_domains?limit=15
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(sscanf(client_message, "%*[^(](%i)", &num) > 0)
|
||||
{
|
||||
@@ -215,19 +215,19 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
}
|
||||
|
||||
// Apply Audit Log filtering?
|
||||
// SOCKET: >top-domains for audit
|
||||
// TELNET: >top-domains for audit
|
||||
// API: /stats/top_domains?audit
|
||||
if(type == SOCKET && command(client_message, " for audit"))
|
||||
if(type == TELNET && command(client_message, " for audit"))
|
||||
audit = true;
|
||||
else if(type != SOCKET && command(client_message, "audit"))
|
||||
else if(type != TELNET && command(client_message, "audit"))
|
||||
audit = true;
|
||||
|
||||
// Sort in descending order?
|
||||
// SOCKET: >top-domains desc
|
||||
// TELNET: >top-domains desc
|
||||
// API: /stats/top_domains?order=desc
|
||||
if(type == SOCKET && command(client_message, " desc"))
|
||||
if(type == TELNET && command(client_message, " desc"))
|
||||
desc = true;
|
||||
else if(type != SOCKET && command(client_message, "order=desc"))
|
||||
else if(type != TELNET && command(client_message, "order=desc"))
|
||||
desc = true;
|
||||
|
||||
for(i=0; i < counters.domains; i++)
|
||||
@@ -279,15 +279,15 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
if(type != TELNET)
|
||||
{
|
||||
// First send header with unspecified content-length outside of the for-loop
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
|
||||
if(blocked)
|
||||
ssend(*sock, "\"top_ads\":{");
|
||||
else
|
||||
ssend(*sock, "\"top_domains\":{");
|
||||
// // First send header with unspecified content-length outside of the for-loop
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
//
|
||||
// if(blocked)
|
||||
// ssend(*sock, "\"top_ads\":{");
|
||||
// else
|
||||
// ssend(*sock, "\"top_domains\":{");
|
||||
}
|
||||
|
||||
int skip = 0; bool first = true;
|
||||
@@ -316,7 +316,7 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
|
||||
if(blocked && showblocked && domains[j].blockedcount > 0)
|
||||
{
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(audit && domains[j].wildcard)
|
||||
ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain);
|
||||
@@ -325,32 +325,32 @@ void getTopDomains(char *client_message, int *sock, char type)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock,",");
|
||||
first = false;
|
||||
ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount);
|
||||
// if(!first) ssend(*sock,",");
|
||||
// first = false;
|
||||
// ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount);
|
||||
}
|
||||
}
|
||||
else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0)
|
||||
{
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock,",");
|
||||
first = false;
|
||||
ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount));
|
||||
// if(!first) ssend(*sock,",");
|
||||
// first = false;
|
||||
// ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
if(type != TELNET)
|
||||
{
|
||||
if(blocked)
|
||||
ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked);
|
||||
else
|
||||
ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries));
|
||||
// if(blocked)
|
||||
// ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked);
|
||||
// else
|
||||
// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries));
|
||||
}
|
||||
|
||||
if(excludedomains != NULL)
|
||||
@@ -370,9 +370,9 @@ void getTopClients(char *client_message, int *sock, char type)
|
||||
int i, temparray[counters.clients][2], count=10, num;
|
||||
|
||||
// Match both top-domains and top-ads
|
||||
// SOCKET: >top-clients (15)
|
||||
// TELNET: >top-clients (15)
|
||||
// API: /stats/top_clients?limit=15
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(sscanf(client_message, "%*[^(](%i)", &num) > 0)
|
||||
{
|
||||
@@ -397,7 +397,7 @@ void getTopClients(char *client_message, int *sock, char type)
|
||||
// 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(type == SOCKET) {
|
||||
if(type == TELNET) {
|
||||
if(command(client_message, " withzero")) {
|
||||
includezeroclients = true;
|
||||
}
|
||||
@@ -425,11 +425,11 @@ void getTopClients(char *client_message, int *sock, char type)
|
||||
logg("Excluding %i clients from being displayed", setupVarsElements);
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
if(type != TELNET)
|
||||
{
|
||||
// First send header with unspecified content-length outside of the for-loop
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"top_clients\":{");
|
||||
// // First send header with unspecified content-length outside of the for-loop
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock, "\"top_clients\":{");
|
||||
}
|
||||
|
||||
int skip = 0; bool first = true;
|
||||
@@ -454,24 +454,24 @@ void getTopClients(char *client_message, int *sock, char type)
|
||||
// - "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(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock,",");
|
||||
first = false;
|
||||
if(strlen(clients[j].name) > 0)
|
||||
ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count);
|
||||
else
|
||||
ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count);
|
||||
// if(!first) ssend(*sock,",");
|
||||
// first = false;
|
||||
// if(strlen(clients[j].name) > 0)
|
||||
// ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count);
|
||||
// else
|
||||
// ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries));
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries));
|
||||
|
||||
if(excludeclients != NULL)
|
||||
clearSetupVarsArray();
|
||||
@@ -486,7 +486,7 @@ void getForwardDestinations(char *client_message, int *sock, char type)
|
||||
bool allocated = false, first = true, sort = true;
|
||||
int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0;
|
||||
|
||||
if(type == SOCKET && command(client_message, "unsorted"))
|
||||
if(type == TELNET && command(client_message, "unsorted"))
|
||||
sort = false;
|
||||
else if(strstr(client_message, "unsorted"))
|
||||
sort = false;
|
||||
@@ -516,11 +516,11 @@ void getForwardDestinations(char *client_message, int *sock, char type)
|
||||
totalqueries = counters.forwardedqueries + counters.cached + counters.blocked;
|
||||
|
||||
// Send HTTP headers with unknown content length
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
|
||||
// Send initial JSON output
|
||||
if(type != SOCKET)
|
||||
ssend(*sock, "\"forward_destinations\":{");
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock, "\"forward_destinations\":{");
|
||||
|
||||
// Loop over available forward destinations
|
||||
for(i=0; i < min(counters.forwarded+1, 10); i++)
|
||||
@@ -584,19 +584,19 @@ void getForwardDestinations(char *client_message, int *sock, char type)
|
||||
// Send data if count > 0
|
||||
if(percentage > 0.0)
|
||||
{
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock, ",");
|
||||
first = false;
|
||||
|
||||
if(strlen(name) > 0)
|
||||
ssend(*sock, "\"%s|%s\":%.2f", name, ip, percentage);
|
||||
else
|
||||
ssend(*sock, "\"%s\":%.2f", ip, percentage);
|
||||
// if(!first) ssend(*sock, ",");
|
||||
// first = false;
|
||||
//
|
||||
// if(strlen(name) > 0)
|
||||
// ssend(*sock, "\"%s|%s\":%.2f", name, ip, percentage);
|
||||
// else
|
||||
// ssend(*sock, "\"%s\":%.2f", ip, percentage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,8 +608,8 @@ void getForwardDestinations(char *client_message, int *sock, char type)
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
ssend(*sock, "}");
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock, "}");
|
||||
|
||||
if(debugclients)
|
||||
logg("Sent forward destination data to client, ID: %i", *sock);
|
||||
@@ -627,11 +627,11 @@ void getQueryTypes(int *sock, char type)
|
||||
percentageIPv6 = 1e2*counters.IPv6/total;
|
||||
}
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
ssend(*sock,"A (IPv4): %.2f\nAAAA (IPv6): %.2f\n", percentageIPv4, percentageIPv6);
|
||||
else {
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"query_types\":{\"A (IPv4)\":%.2f,\"AAAA (IPv6)\":%.2f}", percentageIPv4, percentageIPv6);
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock, "\"query_types\":{\"A (IPv4)\":%.2f,\"AAAA (IPv6)\":%.2f}", percentageIPv4, percentageIPv6);
|
||||
}
|
||||
|
||||
if(debugclients)
|
||||
@@ -655,7 +655,7 @@ void getAllQueries(char *client_message, int *sock, char type)
|
||||
char *clientname = NULL;
|
||||
bool filterclientname = false;
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
// Time filtering?
|
||||
if(command(client_message, ">getallqueries-time"))
|
||||
@@ -724,7 +724,7 @@ void getAllQueries(char *client_message, int *sock, char type)
|
||||
|
||||
int ibeg = 0, num;
|
||||
// Test for integer that specifies number of entries to be shown
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(sscanf(client_message, "%*[^(](%i)", &num) > 0)
|
||||
{
|
||||
@@ -794,11 +794,11 @@ void getAllQueries(char *client_message, int *sock, char type)
|
||||
logg("Privacy mode enabled");
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
{
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"history\":[");
|
||||
}
|
||||
// if(type != TELNET)
|
||||
// {
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock, "\"history\":[");
|
||||
// }
|
||||
|
||||
int i; bool first = true;
|
||||
for(i=ibeg; i < counters.queries; i++)
|
||||
@@ -840,7 +840,7 @@ void getAllQueries(char *client_message, int *sock, char type)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(!privacymode)
|
||||
{
|
||||
@@ -854,24 +854,24 @@ void getAllQueries(char *client_message, int *sock, char type)
|
||||
}
|
||||
else
|
||||
{
|
||||
// {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1],
|
||||
if(!first) ssend(*sock, ",");
|
||||
first = false;
|
||||
|
||||
if(!privacymode)
|
||||
{
|
||||
if(strlen(clients[queries[i].clientID].name) > 0)
|
||||
ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",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]",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]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec);
|
||||
// // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1],
|
||||
// if(!first) ssend(*sock, ",");
|
||||
// first = false;
|
||||
//
|
||||
// if(!privacymode)
|
||||
// {
|
||||
// if(strlen(clients[queries[i].clientID].name) > 0)
|
||||
// ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",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]",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]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec);
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
ssend(*sock, "]");
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock, "]");
|
||||
|
||||
// Free allocated memory
|
||||
if(filterclientname)
|
||||
@@ -893,7 +893,7 @@ void getRecentBlocked(char *client_message, int *sock, char type)
|
||||
return;
|
||||
|
||||
// Test for integer that specifies number of entries to be shown
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
if(sscanf(client_message, "%*[^(](%i)", &num) > 0)
|
||||
{
|
||||
@@ -916,11 +916,11 @@ void getRecentBlocked(char *client_message, int *sock, char type)
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
{
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock, "\"recent_blocked\":[");
|
||||
}
|
||||
// if(type != TELNET)
|
||||
// {
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock, "\"recent_blocked\":[");
|
||||
// }
|
||||
|
||||
// Find most recent query with either status 1 (blocked)
|
||||
// or status 4 (wildcard blocked)
|
||||
@@ -934,15 +934,15 @@ void getRecentBlocked(char *client_message, int *sock, char type)
|
||||
if(queries[i].status == 1 || queries[i].status == 4)
|
||||
{
|
||||
found++;
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
ssend(*sock,"%s\n", domains[queries[i].domainID].domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock, ",");
|
||||
first = false;
|
||||
ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain);
|
||||
// if(!first) ssend(*sock, ",");
|
||||
// first = false;
|
||||
// ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,11 +950,11 @@ void getRecentBlocked(char *client_message, int *sock, char type)
|
||||
break;
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
ssend(*sock, "]");
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock, "]");
|
||||
}
|
||||
|
||||
// only available via SOCKET
|
||||
// only available via TELNET
|
||||
void getMemoryUsage(int *sock, char type)
|
||||
{
|
||||
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);
|
||||
@@ -994,11 +994,11 @@ void getForwardDestinationsOverTime(int *sock, char type)
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
{
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock,"\"over_time\":{");
|
||||
}
|
||||
// if(type != TELNET)
|
||||
// {
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock,"\"over_time\":{");
|
||||
// }
|
||||
|
||||
if(sendit > -1)
|
||||
{
|
||||
@@ -1008,15 +1008,15 @@ void getForwardDestinationsOverTime(int *sock, char type)
|
||||
double percentage;
|
||||
|
||||
validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__);
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
{
|
||||
ssend(*sock, "%i", overTime[i].timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!first) ssend(*sock, ",");
|
||||
first = false;
|
||||
ssend(*sock, "\"%i\":[", overTime[i].timestamp);
|
||||
// if(!first) ssend(*sock, ",");
|
||||
// first = false;
|
||||
// ssend(*sock, "\"%i\":[", overTime[i].timestamp);
|
||||
}
|
||||
|
||||
int j, forwardedsum = 0;
|
||||
@@ -1067,7 +1067,7 @@ void getForwardDestinationsOverTime(int *sock, char type)
|
||||
else
|
||||
percentage = 0.0;
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
ssend(*sock, " %.2f", percentage);
|
||||
else
|
||||
ssend(*sock, "%.2f,", percentage);
|
||||
@@ -1080,19 +1080,19 @@ void getForwardDestinationsOverTime(int *sock, char type)
|
||||
else
|
||||
percentage = 0.0;
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
ssend(*sock, " %.2f\n", percentage);
|
||||
else
|
||||
ssend(*sock, "%.2f]", percentage);
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
{
|
||||
ssend(*sock,"},");
|
||||
// Manually set API -> Don't send header a second time
|
||||
getForwardDestinations(">forward-dest unsorted", sock, API);
|
||||
}
|
||||
// if(type != TELNET)
|
||||
// {
|
||||
// ssend(*sock,"},");
|
||||
// // Manually set API -> Don't send header a second time
|
||||
// getForwardDestinations(">forward-dest unsorted", sock, SOCKET);
|
||||
// }
|
||||
|
||||
if(debugclients)
|
||||
logg("Sent overTime forwarded data to client, ID: %i", *sock);
|
||||
@@ -1120,11 +1120,11 @@ void getQueryTypesOverTime(int *sock, char type)
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
{
|
||||
sendAPIResponse(*sock, type, OK);
|
||||
ssend(*sock,"\"query_types\":{");
|
||||
}
|
||||
// if(type != TELNET)
|
||||
// {
|
||||
// sendAPIResponse(*sock, type, OK);
|
||||
// ssend(*sock,"\"query_types\":{");
|
||||
// }
|
||||
|
||||
if(sendit > -1)
|
||||
{
|
||||
@@ -1141,18 +1141,18 @@ void getQueryTypesOverTime(int *sock, char type)
|
||||
percentageIPv6 = 1e2*overTime[i].querytypedata[1] / sum;
|
||||
}
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6);
|
||||
else {
|
||||
if(!first) ssend(*sock, ",");
|
||||
first = false;
|
||||
ssend(*sock, "\"%i\":[%.2f,%.2f]", overTime[i].timestamp, percentageIPv4, percentageIPv6);
|
||||
// if(!first) ssend(*sock, ",");
|
||||
// first = false;
|
||||
// ssend(*sock, "\"%i\":[%.2f,%.2f]", overTime[i].timestamp, percentageIPv4, percentageIPv6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(type != SOCKET)
|
||||
ssend(*sock,"}");
|
||||
// if(type != TELNET)
|
||||
// ssend(*sock,"}");
|
||||
|
||||
if(debugclients)
|
||||
logg("Sent overTime query types data to client, ID: %i", *sock);
|
||||
|
||||
@@ -89,14 +89,6 @@ int main (int argc, char* argv[]) {
|
||||
}
|
||||
sleepms(100);
|
||||
|
||||
// Start API thread
|
||||
pthread_t api_listenthread;
|
||||
if(pthread_create( &api_listenthread, &attr, api_listening_thread, NULL ) != 0)
|
||||
{
|
||||
logg("Unable to open API listening thread. Exiting...");
|
||||
killed = 1;
|
||||
}
|
||||
|
||||
while(!killed)
|
||||
{
|
||||
sleepms(100);
|
||||
@@ -176,7 +168,6 @@ int main (int argc, char* argv[]) {
|
||||
pthread_cancel(socket_listenthread);
|
||||
close_telnet_socket();
|
||||
close_unix_socket();
|
||||
close_api_socket();
|
||||
removepid();
|
||||
logg("########## FTL terminated! ##########");
|
||||
return 1;
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
#include "FTL.h"
|
||||
#include "api.h"
|
||||
|
||||
void process_socket_request(char *client_message, int *sock)
|
||||
void process_request(char *client_message, int *sock, char type)
|
||||
{
|
||||
char EOT[2];
|
||||
EOT[0] = 0x04;
|
||||
EOT[1] = 0x00;
|
||||
bool processed = false;
|
||||
char type = SOCKET;
|
||||
|
||||
if(command(client_message, ">stats"))
|
||||
{
|
||||
@@ -149,159 +148,6 @@ void process_socket_request(char *client_message, int *sock)
|
||||
}
|
||||
}
|
||||
|
||||
void process_api_request(char *client_message, char *full_message, int *sock, bool header)
|
||||
{
|
||||
char type;
|
||||
if(header)
|
||||
type = APIH;
|
||||
else
|
||||
type = API;
|
||||
|
||||
char *data = getPayload(full_message);
|
||||
long session;
|
||||
|
||||
char authResult = authenticate(full_message, data, &session, *sock);
|
||||
if(authResult == AUTH_UNAUTHORIZED && !matchesEndpoint(client_message, "GET /stats/summary")
|
||||
&& !matchesEndpoint(client_message, "GET /stats/overTime/graph")
|
||||
&& !matchesEndpoint(client_message, "GET /dns/status")) {
|
||||
sendAPIResponse(*sock, type, UNAUTHORIZED);
|
||||
ssend(*sock, "\"status\":\"unauthorized\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
if(authResult == AUTH_NEW) {
|
||||
sendAPIResponseWithCookie(*sock, type, OK, &session);
|
||||
ssend(*sock, "\"status\":\"authorized\",\"session\":%ld}", session);
|
||||
return;
|
||||
}
|
||||
|
||||
if(matchesEndpoint(client_message, "GET /stats/summary"))
|
||||
{
|
||||
getStats(sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/overTime/graph"))
|
||||
{
|
||||
getOverTime(sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/top_domains") || matchesEndpoint(client_message, "GET /stats/top_ads"))
|
||||
{
|
||||
getTopDomains(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/top_clients"))
|
||||
{
|
||||
getTopClients(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/forward_dest") || matchesEndpoint(client_message, "GET /stats/forward_destinations"))
|
||||
{
|
||||
getForwardDestinations(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/dashboard"))
|
||||
{
|
||||
getStats(sock, type);
|
||||
type = API;
|
||||
ssend(*sock, ",");
|
||||
getOverTime(sock, type);
|
||||
ssend(*sock, ",");
|
||||
getTopDomains(client_message, sock, type);
|
||||
ssend(*sock, ",");
|
||||
getTopClients(client_message, sock, type);
|
||||
ssend(*sock, ",");
|
||||
getForwardDestinations(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/query_types"))
|
||||
{
|
||||
getQueryTypes(sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/history"))
|
||||
{
|
||||
getAllQueries(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/recent_blocked"))
|
||||
{
|
||||
getRecentBlocked(client_message, sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/overTime/forward_dest"))
|
||||
{
|
||||
getForwardDestinationsOverTime(sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /stats/overTime/query_types"))
|
||||
{
|
||||
getQueryTypesOverTime(sock, type);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /dns/whitelist"))
|
||||
{
|
||||
getList(sock, type, WHITELIST);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "POST /dns/whitelist"))
|
||||
{
|
||||
addList(sock, type, WHITELIST, data);
|
||||
}
|
||||
else if(matchesRegex("DELETE \\/dns\\/whitelist\\/[^\\/]*$", client_message))
|
||||
{
|
||||
removeList(sock, type, WHITELIST, client_message);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /dns/blacklist"))
|
||||
{
|
||||
getList(sock, type, BLACKLIST);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "POST /dns/blacklist"))
|
||||
{
|
||||
addList(sock, type, BLACKLIST, data);
|
||||
}
|
||||
else if(matchesRegex("DELETE \\/dns\\/blacklist\\/[^\\/]*$", client_message))
|
||||
{
|
||||
removeList(sock, type, BLACKLIST, client_message);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /dns/wildlist"))
|
||||
{
|
||||
getList(sock, type, WILDLIST);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "POST /dns/wildlist"))
|
||||
{
|
||||
addList(sock, type, WILDLIST, data);
|
||||
}
|
||||
else if(matchesRegex("DELETE \\/dns\\/wildlist\\/[^\\/]*$", client_message))
|
||||
{
|
||||
removeList(sock, type, WILDLIST, client_message);
|
||||
}
|
||||
else if(matchesEndpoint(client_message, "GET /dns/status"))
|
||||
{
|
||||
getPiholeStatus(sock, type);
|
||||
}
|
||||
else if(header)
|
||||
{
|
||||
sendAPIResponse(*sock, type, NOT_FOUND);
|
||||
ssend(*sock, "\"status\":\"not_found\"");
|
||||
}
|
||||
|
||||
ssend(*sock, "}");
|
||||
}
|
||||
|
||||
bool command(char *client_message, const char* cmd) {
|
||||
return strstr(client_message, cmd) != NULL;
|
||||
}
|
||||
|
||||
bool matchesEndpoint(char *client_message, const char *cmd) {
|
||||
char *get_params_start = strstr(client_message, "?");
|
||||
bool result;
|
||||
|
||||
// Check if there are GET parameters to ignore
|
||||
if(get_params_start != NULL) {
|
||||
char without_get_params[256];
|
||||
|
||||
// Check to make sure we don't overflow the buffer
|
||||
if(strlen(cmd)+1 > sizeof(without_get_params) / sizeof(char))
|
||||
return false;
|
||||
|
||||
size_t msg_len = get_params_start - client_message;
|
||||
|
||||
strncpy(without_get_params, client_message, msg_len);
|
||||
without_get_params[msg_len] = 0;
|
||||
|
||||
result = strcmp(without_get_params, cmd) == 0;
|
||||
}
|
||||
else
|
||||
result = strcmp(client_message, cmd) == 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
-4
@@ -40,15 +40,12 @@ void memory_check(int which);
|
||||
|
||||
void close_telnet_socket(void);
|
||||
void close_unix_socket(void);
|
||||
void close_api_socket(void);
|
||||
void seom(int sock);
|
||||
void ssend(int sock, const char *format, ...);
|
||||
void *telnet_listening_thread(void *args);
|
||||
void *socket_listening_thread(void *args);
|
||||
void *api_listening_thread(void *args);
|
||||
|
||||
void process_socket_request(char *client_message, int *sock);
|
||||
void process_api_request(char *client_message, char *full_message, int *sock, bool header);
|
||||
void process_request(char *client_message, int *sock, char type);
|
||||
bool command(char *client_message, const char* cmd);
|
||||
bool matchesEndpoint(char *client_message, const char *cmd);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#define BACKLOG 5
|
||||
|
||||
// File descriptors
|
||||
int telnetfd, socketfd, apifd;
|
||||
int telnetfd, socketfd;
|
||||
|
||||
void saveport(int port)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ void bind_to_telnet_port(char type, int *socketdescriptor)
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
if(config.socket_listenlocal && type == SOCKET)
|
||||
if(config.socket_listenlocal)
|
||||
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
else
|
||||
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
@@ -77,12 +77,9 @@ void bind_to_telnet_port(char type, int *socketdescriptor)
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case SOCKET:
|
||||
case TELNET:
|
||||
port_init = 4711;
|
||||
break;
|
||||
case API:
|
||||
port_init = 4747;
|
||||
break;
|
||||
default:
|
||||
logg("Incompatible socket type %i", (int)type);
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -110,7 +107,7 @@ void bind_to_telnet_port(char type, int *socketdescriptor)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(type == SOCKET)
|
||||
if(type == TELNET)
|
||||
saveport(port);
|
||||
|
||||
// The listen system call allows the process to listen on the socket for connections
|
||||
@@ -119,18 +116,8 @@ void bind_to_telnet_port(char type, int *socketdescriptor)
|
||||
logg("Error on listening");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
switch(type)
|
||||
{
|
||||
case SOCKET:
|
||||
logg("Listening on port %i for incoming socket connections", port);
|
||||
break;
|
||||
case API:
|
||||
logg("Listening on port %i for incoming API connections", port);
|
||||
break;
|
||||
default:
|
||||
/* That cannot happen */
|
||||
break;
|
||||
}
|
||||
|
||||
logg("Listening on port %i for incoming telnet connections", port);
|
||||
}
|
||||
|
||||
|
||||
@@ -243,11 +230,6 @@ void close_unix_socket(void)
|
||||
close(socketfd);
|
||||
}
|
||||
|
||||
void close_api_socket(void)
|
||||
{
|
||||
close(apifd);
|
||||
}
|
||||
|
||||
void *telnet_connection_handler_thread(void *socket_desc)
|
||||
{
|
||||
//Get the socket descriptor
|
||||
@@ -276,7 +258,7 @@ void *telnet_connection_handler_thread(void *socket_desc)
|
||||
// Requests should not be processed/answered when data is about to change
|
||||
enable_thread_lock(threadname);
|
||||
|
||||
process_socket_request(message, &sock);
|
||||
process_request(message, &sock, TELNET);
|
||||
free(message);
|
||||
|
||||
// Release thread lock
|
||||
@@ -334,7 +316,7 @@ void *socket_connection_handler_thread(void *socket_desc)
|
||||
// Requests should not be processed/answered when data is about to change
|
||||
enable_thread_lock(threadname);
|
||||
|
||||
process_socket_request(message, &sock);
|
||||
process_request(message, &sock, SOCKET);
|
||||
free(message);
|
||||
|
||||
// Release thread lock
|
||||
@@ -381,7 +363,7 @@ void *telnet_listening_thread(void *args)
|
||||
prctl(PR_SET_NAME,"telnet listener",0,0,0);
|
||||
|
||||
// Initialize sockets only after initial log parsing in listenting_thread
|
||||
bind_to_telnet_port(SOCKET, &telnetfd);
|
||||
bind_to_telnet_port(TELNET, &telnetfd);
|
||||
|
||||
// Listen as long as FTL is not killed
|
||||
while(!killed)
|
||||
@@ -443,159 +425,3 @@ void *socket_listening_thread(void *args)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void *api_connection_handler_thread(void *socket_desc)
|
||||
{
|
||||
//Get the socket descriptor
|
||||
int sock = *(int*)socket_desc;
|
||||
// Store copy only for displaying the debug messages
|
||||
int sockID = sock;
|
||||
char client_message[SOCKETBUFFERLEN] = "";
|
||||
|
||||
// Set thread name
|
||||
char threadname[16];
|
||||
sprintf(threadname,"api-%i",sockID);
|
||||
prctl(PR_SET_NAME,threadname,0,0,0);
|
||||
|
||||
//Receive from client
|
||||
if(recv(sock, client_message, SOCKETBUFFERLEN-1, 0) > 0)
|
||||
{
|
||||
char *message = calloc(strlen(client_message)+1,sizeof(char));
|
||||
strcpy(message, client_message);
|
||||
|
||||
// Clear client message receive buffer
|
||||
memset(client_message, 0, sizeof client_message);
|
||||
|
||||
if(debug)
|
||||
logg("Received API request: \n%s", message);
|
||||
|
||||
if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0 || strncmp(message, "DELETE ", 7) == 0)
|
||||
{
|
||||
// HTTP requests can be simple or full.
|
||||
// A simple request contains one line only, and looks like this:
|
||||
// GET /index.html
|
||||
// A full request can contain more than one line and may look like this:
|
||||
// GET /index.html HTTP/1.1
|
||||
// User-Agent: Wget/1.16 (linux-gnueabihf)
|
||||
// Accept: */*
|
||||
// Host: 127.0.0.1:4747
|
||||
// Connection: Keep-Alive
|
||||
bool header = false;
|
||||
|
||||
// Extract requested URL including arguments
|
||||
const char *p2;
|
||||
if(strstr(message, "HTTP/") != NULL)
|
||||
{
|
||||
// Output HTTP response headers only if we have a full request
|
||||
header = true;
|
||||
// End of request = "HTTP/"
|
||||
p2 = strstr(message, " HTTP/");
|
||||
}
|
||||
else
|
||||
{
|
||||
// End of requst = end of first line
|
||||
p2 = strstr(message, "\n");
|
||||
}
|
||||
if(p2 != NULL)
|
||||
{
|
||||
size_t len = p2 - message;
|
||||
char *request = calloc(len+1, sizeof(char));
|
||||
strncpy(request, message, len);
|
||||
request[len] = '\0';
|
||||
|
||||
// Are we asked for a favicon?
|
||||
if(strstr(request, "/favicon.ico") != NULL)
|
||||
ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n");
|
||||
else
|
||||
{
|
||||
enable_thread_lock(threadname);
|
||||
process_api_request(request, message, &sock, header);
|
||||
disable_thread_lock(threadname);
|
||||
}
|
||||
|
||||
// Free allocated memory
|
||||
free(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
logg("API received malformated request: \"%s\"", message);
|
||||
}
|
||||
}
|
||||
else if(strncmp(message, "OPTIONS ", 8) == 0)
|
||||
{
|
||||
// OPTIONS request: CORS preflight
|
||||
ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nAccess-Control-Allow-Origin: *\n"
|
||||
"Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\n"
|
||||
"Access-Control-Allow-Headers: Content-Type\n\n");
|
||||
}
|
||||
else if(strncmp(message, "HEAD ", 5) == 0)
|
||||
{
|
||||
// HEAD request: We do not send any content at all
|
||||
ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(debug)
|
||||
logg("API received something strange");
|
||||
}
|
||||
|
||||
// Close connection to show that we reached the end of the transmission
|
||||
close(sock);
|
||||
sock = 0;
|
||||
|
||||
// Free allocated memory
|
||||
free(message);
|
||||
}
|
||||
|
||||
//Free the socket pointer
|
||||
if(sock != 0)
|
||||
close(sock);
|
||||
free(socket_desc);
|
||||
|
||||
if(clientip[sock] != NULL) {
|
||||
free(clientip[sock]);
|
||||
clientip[sock] = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *api_listening_thread(void *args)
|
||||
{
|
||||
int *newsock;
|
||||
// We will use the attributes object later to start all threads in detached mode
|
||||
pthread_attr_t attr;
|
||||
// Initialize thread attributes object with default attribute values
|
||||
pthread_attr_init(&attr);
|
||||
// When a detached thread terminates, its resources are automatically released back to
|
||||
// the system without the need for another thread to join with the terminated thread
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
|
||||
// Set thread name
|
||||
prctl(PR_SET_NAME,"API listener",0,0,0);
|
||||
|
||||
// Initialize sockets only after initial log parsing in listening_thread
|
||||
bind_to_telnet_port(API, &apifd);
|
||||
|
||||
// Listen as long as FTL is not killed
|
||||
while(!killed)
|
||||
{
|
||||
// Look for new clients that want to connect
|
||||
int csck = listener(apifd);
|
||||
if(csck < 0) continue;
|
||||
|
||||
// Allocate memory used to transport client socket ID to client listening thread
|
||||
newsock = calloc(1,sizeof(int));
|
||||
*newsock = csck;
|
||||
|
||||
pthread_t api_connection_thread;
|
||||
// Create a new thread
|
||||
if(pthread_create( &api_connection_thread, &attr, api_connection_handler_thread, (void*) newsock ) != 0)
|
||||
{
|
||||
// Log the error code description
|
||||
logg("WARNING: Unable to open client API thread, error: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
#include "FTL.h"
|
||||
#include "api.h"
|
||||
|
||||
FTLFileNamesStruct FTLfiles = {
|
||||
"/etc/pihole/pihole-FTL.conf",
|
||||
@@ -120,15 +119,6 @@ void memory_check(int which)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case AUTHDATA:
|
||||
// Always called when we need one more entry, like wildcard
|
||||
logg_struct_resize("authdata", authLength+1, 1);
|
||||
authData = realloc(authData, (authLength+1) * sizeof(AuthData));
|
||||
|
||||
if(authData == NULL) {
|
||||
logg("FATAL: Memory allocation failed! Exiting");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
default:
|
||||
/* That cannot happen */
|
||||
break;
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
|
||||
# Block until FTL is ready, retry once per second for 45 seconds
|
||||
n=0
|
||||
until [ $n -ge 45 ]; do
|
||||
(nc -vv -z -w 30 127.0.0.1 4711 && nc -vv -z -w 30 127.0.0.1 4747) && break
|
||||
nc -vv -z -w 30 127.0.0.1 4711 && break
|
||||
n=$[$n+1]
|
||||
echo "..."
|
||||
tail -n2 pihole-FTL.log
|
||||
|
||||
+11
-57
@@ -183,52 +183,6 @@ load 'libs/bats-support/load'
|
||||
[[ "${lines[@]}" == *"INSERT INTO \"ftl\" VALUES(0,1);"* ]]
|
||||
}
|
||||
|
||||
@test "HTTP server: FTL responding correctly to HEAD request" {
|
||||
run bash -c "curl --head -s 127.0.0.1:4747"
|
||||
echo "output: ${lines[@]}"
|
||||
echo "curl exit code: ${status}"
|
||||
[[ ${lines[0]} == "HTTP/1.0 200 OK" ]]
|
||||
[[ ${lines[1]} == "Server: FTL" ]]
|
||||
[[ ${lines[2]} == "" ]]
|
||||
[[ "${status}" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "HTTP server: FTL responding correctly to GET request" {
|
||||
run bash -c "curl -s 127.0.0.1:4747"
|
||||
echo "output: ${lines[@]}"
|
||||
echo "curl exit code: ${status}"
|
||||
[[ "${status}" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "API: Correct answer to summary request (including header check)" {
|
||||
run bash -c "curl -si 127.0.0.1:4747/stats/summary"
|
||||
echo "output: ${lines[@]}"
|
||||
echo "curl exit code: ${status}"
|
||||
[[ ${lines[0]} == "HTTP/1.0 200 OK" ]]
|
||||
[[ ${lines[1]} == "Server: FTL" ]]
|
||||
[[ ${lines[2]} == "Cache-Control: no-cache" ]]
|
||||
[[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]]
|
||||
[[ ${lines[4]} == "Content-Type: application/json" ]]
|
||||
[[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"clients_ever_seen\":3,\"unique_clients\":3,\"status\":\"unknown\"}" ]]
|
||||
[[ "${status}" -eq 0 ]]
|
||||
}
|
||||
|
||||
#@test "API: Correct answer to top_domains request" {
|
||||
# run bash -c "curl -s 127.0.0.1:4747/stats/top_domains"
|
||||
# echo "output: ${lines[@]}"
|
||||
# echo "curl exit code: ${status}"
|
||||
# [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"example.com\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]]
|
||||
# [[ "${status}" -eq 0 ]]
|
||||
#}
|
||||
|
||||
#@test "API: Correct answer to top_ads request" {
|
||||
# run bash -c "curl -s 127.0.0.1:4747/stats/top_ads"
|
||||
# echo "output: ${lines[@]}"
|
||||
# echo "curl exit code: ${status}"
|
||||
# [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"ads_blocked_today\":2}" ]]
|
||||
# [[ "${status}" -eq 0 ]]
|
||||
#}
|
||||
|
||||
@test "Arguments check: Invalid option" {
|
||||
run bash -c './pihole-FTL abc'
|
||||
echo "output: ${lines[@]}"
|
||||
@@ -247,17 +201,17 @@ 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]} == "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[2]} == "---EOM---" ]]
|
||||
}
|
||||
|
||||
@test "Final part of the tests: Killing pihole-FTL process" {
|
||||
|
||||
Reference in New Issue
Block a user