mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
438 lines
12 KiB
C
438 lines
12 KiB
C
/* 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
|
|
* Log parsing routine
|
|
*
|
|
* This file is copyright under the latest version of the EUPL.
|
|
* Please see LICENSE file for your rights under this license. */
|
|
|
|
#include "FTL.h"
|
|
|
|
int dnsmasqlogpos = 0;
|
|
int checkLogForChanges(void)
|
|
{
|
|
// seek to the end of the file
|
|
fseek(dnsmasqlog, 0L, SEEK_END);
|
|
// ask for the position
|
|
int pos = ftell(dnsmasqlog);
|
|
if(pos > dnsmasqlogpos)
|
|
{
|
|
// Go back to to previous position
|
|
fseek(dnsmasqlog, dnsmasqlogpos, SEEK_SET);
|
|
}
|
|
return (pos-dnsmasqlogpos);
|
|
}
|
|
|
|
void open_pihole_log(void)
|
|
{
|
|
if((dnsmasqlog = fopen(files.log, "r")) == NULL) {
|
|
logg("FATAL: Opening of pihole.log failed!");
|
|
logg(" Make sure it exists and is readable");
|
|
// Return failure in exit status
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void pihole_log_flushed(void)
|
|
{
|
|
logg("NOTICE: pihole.log has been flushed");
|
|
logg(" Resetting internal data structure");
|
|
|
|
// Free memory on allocated data structure
|
|
free(queries);
|
|
queries = NULL;
|
|
free(forwarded);
|
|
forwarded = NULL;
|
|
free(clients);
|
|
clients = NULL;
|
|
free(domains);
|
|
domains = NULL;
|
|
|
|
// Reset all counters to zero
|
|
memset(&counters, 0, sizeof(countersStruct));
|
|
// Reset over Time data
|
|
memset(&overTime, 0, 600*sizeof(overTimeDataStruct));
|
|
|
|
// Update file pointer position
|
|
dnsmasqlogpos = ftell(dnsmasqlog);
|
|
|
|
// Recount entries in gravity files
|
|
read_gravity_files();
|
|
}
|
|
|
|
void process_pihole_log(void)
|
|
{
|
|
int i;
|
|
char readbuffer[1024] = "";
|
|
char readbuffer2[1024] = "";
|
|
|
|
// Read pihole log from current position until EOF line by line
|
|
while( fgets (readbuffer , sizeof(readbuffer) , dnsmasqlog) != NULL )
|
|
{
|
|
// Test if the read line is a query line
|
|
if(strstr(readbuffer,"]: query[A") != NULL)
|
|
{
|
|
// Ensure we have enough space in the queries struct
|
|
memory_check(QUERIES);
|
|
|
|
// Get timestamp
|
|
char timestamp[16] = "";
|
|
strncpy(timestamp,readbuffer,(size_t)15);
|
|
timestamp[15] = '\0';
|
|
// Get local time
|
|
time_t rawtime;
|
|
struct tm * timeinfo;
|
|
time(&rawtime);
|
|
timeinfo = localtime (&rawtime);
|
|
// Interpret dnsmasq timestamp
|
|
struct tm querytime;
|
|
// Expected format: Mmm dd hh:mm:ss
|
|
// %b = Abbreviated month name
|
|
// %e = Day of the month, space-padded ( 1-31)
|
|
// %H = Hour in 24h format (00-23)
|
|
// %M = Minute (00-59)
|
|
// %S = Second (00-59)
|
|
strptime(timestamp, "%b %e %H:%M:%S", &querytime);
|
|
// Year is missing in dnsmasq's output - add the current year
|
|
querytime.tm_year = (*timeinfo).tm_year;
|
|
|
|
// Prepare index for ater overTime counting
|
|
int timeidx = (querytime.tm_min - querytime.tm_min%10)/10 + 6*querytime.tm_hour;
|
|
if(timeidx > counters.overtime)
|
|
{
|
|
counters.overtime = timeidx;
|
|
}
|
|
|
|
// Get domain
|
|
// domainstart = pointer to | in "query[AAAA] |host.name from ww.xx.yy.zz\n"
|
|
const char *domainstart = strstr(readbuffer, "] ");
|
|
// Check if buffer pointer is valid
|
|
if(domainstart == NULL)
|
|
{
|
|
logg_str("Notice: Skipping malformated log line (domain start missing): ",strtok(readbuffer,"\n"));
|
|
// Skip this line
|
|
continue;
|
|
}
|
|
// domainend = pointer to | in "query[AAAA] host.name| from ww.xx.yy.zz\n"
|
|
const char *domainend = strstr(domainstart+2, " from");
|
|
// Check if buffer pointer is valid
|
|
if(domainend == NULL)
|
|
{
|
|
logg_str("Notice: Skipping malformated log line (domain end missing): ",strtok(readbuffer,"\n"));
|
|
// Skip this line
|
|
continue;
|
|
}
|
|
size_t domainlen = domainend-(domainstart+2);
|
|
char *domain = calloc(domainlen+1,sizeof(char));
|
|
strncpy(domain,domainstart+2,domainlen);
|
|
|
|
// Get client
|
|
// domainend+6 = pointer to | in "query[AAAA] host.name from |ww.xx.yy.zz\n"
|
|
const char *clientend = strstr(domainend+6, "\n");
|
|
// Check if buffer pointer is valid
|
|
if(clientend == NULL)
|
|
{
|
|
logg_str("Notice: Skipping malformated log line (client end missing): ",strtok(readbuffer,"\n"));
|
|
// Skip this line
|
|
continue;
|
|
}
|
|
size_t clientlen = (clientend-domainend)-6;
|
|
char *client = calloc(clientlen+1,sizeof(char));
|
|
strncpy(client,domainend+6,clientlen);
|
|
|
|
// Get type
|
|
unsigned char type = 0;
|
|
if(strstr(readbuffer,"query[A]") != NULL)
|
|
{
|
|
type = 1;
|
|
counters.IPv4++;
|
|
}
|
|
else if(strstr(readbuffer,"query[AAAA]") != NULL)
|
|
{
|
|
type = 2;
|
|
counters.IPv6++;
|
|
}
|
|
|
|
// Save current file pointer position
|
|
int fpos = ftell(dnsmasqlog);
|
|
unsigned char status = 0;
|
|
|
|
// Try to find either a matching
|
|
// - "gravity.list" + domain
|
|
// - "forwarded" + domain
|
|
// - "cached" + domain
|
|
// in the following up to 200 lines
|
|
bool firsttime = true;
|
|
for(i=0; i<200; i++)
|
|
{
|
|
if(fgets (readbuffer2 , sizeof(readbuffer2) , dnsmasqlog) != NULL)
|
|
{
|
|
// Process only matching lines
|
|
if(strstr(readbuffer2,domain) != NULL)
|
|
{
|
|
// Blocked by gravity.list ?
|
|
if(strstr(readbuffer2,"gravity.list ") != NULL)
|
|
{
|
|
status = 1;
|
|
break;
|
|
}
|
|
// Forwarded to upstream server?
|
|
else if(strstr(readbuffer2,"forwarded ") != NULL)
|
|
{
|
|
status = 2;
|
|
break;
|
|
}
|
|
// Answered by local cache?
|
|
else if((strstr(readbuffer2,"cached ") != NULL) ||
|
|
(strstr(readbuffer2,"local.list") != NULL) ||
|
|
(strstr(readbuffer2,"hostname.list") != NULL) ||
|
|
(strstr(readbuffer2,"/etc/hosts") != NULL))
|
|
{
|
|
status = 3;
|
|
break;
|
|
}
|
|
// wildcard blocking?
|
|
else if((strstr(readbuffer2,"config ") != NULL))
|
|
{
|
|
status = 4;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(firsttime)
|
|
{
|
|
// Reached EOF without finding the action
|
|
// wait 100msec and try again to read dnsmasq's response
|
|
i = 0;
|
|
fseek(dnsmasqlog, fpos, SEEK_SET);
|
|
firsttime = false;
|
|
nanosleep((const struct timespec[]){{0, 100000000L}}, NULL);
|
|
}
|
|
else
|
|
{
|
|
// Failed second time
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Return to previous file pointer position
|
|
fseek(dnsmasqlog, fpos, SEEK_SET);
|
|
|
|
// Go through already knows domains and see if it is one of them
|
|
bool processed = false;
|
|
int domainID;
|
|
for(i=0; i < counters.domains; i++)
|
|
{
|
|
if(strcmp(domains[i].domain,domain) == 0)
|
|
{
|
|
domains[i].count++;
|
|
processed = true;
|
|
domainID = i;
|
|
break;
|
|
}
|
|
}
|
|
if(!processed)
|
|
{
|
|
// This domain is not known
|
|
// Check struct size
|
|
memory_check(DOMAINS);
|
|
// Store ID
|
|
domainID = counters.domains;
|
|
// Set its counter to 1
|
|
domains[domainID].count = 1;
|
|
// Set blocked counter to zero
|
|
domains[domainID].blockedcount = 0;
|
|
// Store domain name
|
|
domains[domainID].domain = calloc(strlen(domain)+1,sizeof(char));
|
|
strcpy(domains[domainID].domain, domain);
|
|
// Increase counter by one
|
|
counters.domains++;
|
|
// logg_str("Added one new domain: ", domain);
|
|
}
|
|
|
|
// Go through already knows clients and see if it is one of them
|
|
processed = false;
|
|
int clientID;
|
|
for(i=0; i < counters.clients; i++)
|
|
{
|
|
if(strcmp(clients[i].ip,client) == 0)
|
|
{
|
|
clients[i].count++;
|
|
processed = true;
|
|
clientID = i;
|
|
break;
|
|
}
|
|
}
|
|
if(!processed)
|
|
{
|
|
// This client is not known
|
|
// Check struct size
|
|
memory_check(CLIENTS);
|
|
// Store ID
|
|
clientID = counters.clients;
|
|
// Set its counter to 1
|
|
clients[clientID].count = 1;
|
|
// Store client IP
|
|
clients[clientID].ip = calloc(strlen(client)+1,sizeof(char));
|
|
strcpy(clients[clientID].ip, client);
|
|
// Get and store client host name
|
|
struct hostent *he;
|
|
if(strstr(client,":") != NULL)
|
|
{
|
|
struct in6_addr ipaddr;
|
|
inet_pton(AF_INET6, client, &ipaddr);
|
|
he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET6);
|
|
}
|
|
else
|
|
{
|
|
struct in_addr ipaddr;
|
|
inet_pton(AF_INET, client, &ipaddr);
|
|
he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET);
|
|
}
|
|
|
|
if(he == NULL)
|
|
{
|
|
clients[clientID].name = calloc(1,sizeof(char));
|
|
strcpy(clients[clientID].name, "");
|
|
}
|
|
else
|
|
{
|
|
clients[clientID].name = calloc(strlen(he->h_name)+1,sizeof(char));
|
|
strcpy(clients[clientID].name, he->h_name);
|
|
}
|
|
// Increase counter by one
|
|
counters.clients++;
|
|
if(strlen(clients[clientID].name) > 0)
|
|
logg_str_str("Added new client: ", client, clients[clientID].name);
|
|
else
|
|
logg_str("Added new client: ", client);
|
|
}
|
|
|
|
// Save everything
|
|
queries[counters.queries].timestamp = (int)mktime(&querytime);
|
|
queries[counters.queries].type = type;
|
|
queries[counters.queries].status = status;
|
|
queries[counters.queries].domainID = domainID;
|
|
queries[counters.queries].clientID = clientID;
|
|
|
|
// Increase DNS queries counter
|
|
counters.queries++;
|
|
|
|
// Update overTime data
|
|
overTime[timeidx].total++;
|
|
|
|
// Decide what to increment depending on status
|
|
switch(status)
|
|
{
|
|
case 0: counters.unknown++; logg_str("Unknown: ",strtok(readbuffer, "\n")); break;
|
|
case 1: counters.blocked++; overTime[timeidx].blocked++; domains[domainID].blockedcount++; break;
|
|
case 2: break;
|
|
case 3: counters.cached++; break;
|
|
case 4: counters.wildcardblocked++; overTime[timeidx].blocked++; break;
|
|
default: /* That cannot happen */ break;
|
|
}
|
|
|
|
// Free allocated memory
|
|
free(client);
|
|
free(domain);
|
|
|
|
}
|
|
else if(strstr(readbuffer,": forwarded") != NULL)
|
|
{
|
|
// Get forward destination
|
|
// forwardstart = pointer to | in "forwarded domain.name| to www.xxx.yyy.zzz\n"
|
|
const char *forwardstart = strstr(readbuffer, " to ");
|
|
// Check if buffer pointer is valid
|
|
if(forwardstart == NULL)
|
|
{
|
|
logg_str("Notice: Skipping malformated log line (forward start missing): ",strtok(readbuffer,"\n"));
|
|
// Skip this line
|
|
continue;
|
|
}
|
|
// forwardend = pointer to | in "forwarded domain.name to www.xxx.yyy.zzz|\n"
|
|
const char *forwardend = strstr(forwardstart+4, "\n");
|
|
// Check if buffer pointer is valid
|
|
if(forwardend == NULL)
|
|
{
|
|
logg_str("Notice: Skipping malformated log line (forward end missing): ",strtok(readbuffer,"\n"));
|
|
// Skip this line
|
|
continue;
|
|
}
|
|
size_t forwardlen = forwardend-(forwardstart+4);
|
|
char *forward = calloc(forwardlen+1,sizeof(char));
|
|
strncpy(forward,forwardstart+4,forwardlen);
|
|
|
|
bool processed = false;
|
|
// Go through already knows forward servers and see if we used one of those
|
|
for(i=0; i < counters.forwarded; i++)
|
|
{
|
|
if(strcmp(forwarded[i].ip,forward) == 0)
|
|
{
|
|
forwarded[i].count++;
|
|
processed = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!processed)
|
|
{
|
|
// This forward server is not known
|
|
// Check struct size
|
|
memory_check(FORWARDED);
|
|
// Store ID
|
|
int forwardID = counters.forwarded;
|
|
// Set its counter to 1
|
|
forwarded[forwardID].count = 1;
|
|
// Save IP
|
|
forwarded[forwardID].ip = calloc(forwardlen+1,sizeof(char));
|
|
strcpy(forwarded[forwardID].ip,forward);
|
|
// Get and store client host name
|
|
struct hostent *he;
|
|
struct in_addr ipv4addr;
|
|
inet_pton(AF_INET, forwarded[forwardID].ip, &ipv4addr);
|
|
he = gethostbyaddr(&ipv4addr, sizeof ipv4addr, AF_INET);
|
|
if(he == NULL)
|
|
{
|
|
forwarded[forwardID].name = calloc(1,sizeof(char));
|
|
strcpy(forwarded[forwardID].name, "");
|
|
}
|
|
else
|
|
{
|
|
forwarded[forwardID].name = calloc(strlen(he->h_name)+1,sizeof(char));
|
|
strcpy(forwarded[forwardID].name, he->h_name);
|
|
}
|
|
// Increase counter by one
|
|
counters.forwarded++;
|
|
if(strlen(forwarded[forwardID].name) > 0)
|
|
logg_str_str("Added new forward server: ", forwarded[forwardID].ip, forwarded[forwardID].name);
|
|
else
|
|
logg_str("Added new forward server: ", forwarded[forwardID].ip);
|
|
}
|
|
}
|
|
else if((strstr(readbuffer,"IPv6") != NULL) &&
|
|
(strstr(readbuffer,"DBus") != NULL) &&
|
|
(strstr(readbuffer,"i18n") != NULL) &&
|
|
(strstr(readbuffer,"DHCP") != NULL) &&
|
|
!initialscan)
|
|
{
|
|
// dnsmasq restartet
|
|
logg("dnsmasq process restarted");
|
|
read_gravity_files();
|
|
}
|
|
else if(strstr(readbuffer,"query[PTR]"))
|
|
{
|
|
counters.PTR++;
|
|
}
|
|
else if(strstr(readbuffer,"query[SRV]"))
|
|
{
|
|
counters.SRV++;
|
|
}
|
|
|
|
// Update file pointer position
|
|
dnsmasqlogpos = ftell(dnsmasqlog);
|
|
}
|
|
}
|