/* 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
*  DNS Client 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 "shmem.h"

// Resolve new client and upstream server host names
// once every minute
#define RESOLVE_INTERVAL 60

// Re-resolve client names
// once every hour
#define RERESOLVE_INTERVAL 3600

char *resolveHostname(const char *addr)
{
	// Get host name
	struct hostent *he = NULL;
	char *hostname = NULL;;
	bool IPv6 = false;

	// Check if this is a hidden client
	// if so, return "hidden" as hostname
	if(strcmp(addr, "0.0.0.0") == 0)
	{
		hostname = strdup("hidden");
		//if(hostname == NULL) return NULL;
		return hostname;
	}

	// Test if we want to resolve an IPv6 address
	if(strstr(addr,":") != NULL)
	{
		IPv6 = true;
	}

	if(IPv6 && config.resolveIPv6) // Resolve IPv6 address only if requested
	{
		struct in6_addr ipaddr;
		inet_pton(AF_INET6, addr, &ipaddr);
		he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET6);
	}
	else if(!IPv6 && config.resolveIPv4) // Resolve IPv4 address only if requested
	{
		struct in_addr ipaddr;
		inet_pton(AF_INET, addr, &ipaddr);
		he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET);
	}

	if(he == NULL)
	{
		// No hostname found
		hostname = strdup("");
		//if(hostname == NULL) return NULL;
	}
	else
	{
		// Return hostname copied to new memory location
		hostname = strdup(he->h_name);
		if(hostname == NULL) return NULL;
		// Convert hostname to lower case
		strtolower(hostname);
	}
	return hostname;
}

// Resolve client host names
void resolveClients(bool onlynew)
{
	int clientID;
	for(clientID = 0; clientID < counters->clients; clientID++)
	{
		// Memory validation
		validate_access("clients", clientID, true, __LINE__, __FUNCTION__, __FILE__);

		// If onlynew flag is set, we will only resolve new clients
		// If not, we will try to re-resolve all known clients
		if(onlynew && !clients[clientID].new)
			continue;

		// Lock data when obtaining IP of this client
		lock_shm();
		const char* ipaddr = getstr(clients[clientID].ippos);
		unlock_shm();

		// Important: Don't hold a lock while resolving as the main thread
		// (dnsmasq) needs to be operable during the call to resolveHostname()
		const char* hostname = resolveHostname(ipaddr);

		// Finally, lock data when storing obtained hostname
		lock_shm();
		clients[clientID].namepos = addstr(hostname);
		clients[clientID].new = false;
		unlock_shm();
	}
}

// Resolve upstream destination host names
void resolveForwardDestinations(bool onlynew)
{
	int forwardID;
	for(forwardID = 0; forwardID < counters->forwarded; forwardID++)
	{
		// Memory validation
		validate_access("forwarded", forwardID, true, __LINE__, __FUNCTION__, __FILE__);

		// If onlynew flag is set, we will only resolve new upstream destinations
		// If not, we will try to re-resolve all known upstream destinations
		if(onlynew && !forwarded[forwardID].new)
			continue;

		// Lock data when obtaining IP of this forward destination
		lock_shm();
		const char* ipaddr = getstr(forwarded[forwardID].ippos);
		unlock_shm();


		// Important: Don't hold a lock while resolving as the main thread
		// (dnsmasq) needs to be operable during the call to resolveHostname()
		const char* hostname = resolveHostname(ipaddr);

		// Finally, lock data when storing obtained hostname
		lock_shm();
		forwarded[forwardID].namepos = addstr(hostname);
		forwarded[forwardID].new = false;
		unlock_shm();
	}
}

void *DNSclient_thread(void *val)
{
	// Set thread name
	prctl(PR_SET_NAME, "DNS client", 0, 0, 0);

	while(!killed)
	{
		// Run every minute to resolve only new clients and upstream servers
		if(time(NULL) % RESOLVE_INTERVAL == 0)
		{
			// Try to resolve new client host names (onlynew=true)
			resolveClients(true);
			// Try to resolve new upstream destination host names (onlynew=true)
			resolveForwardDestinations(true);
			// Prevent immediate re-run of this routine
			sleepms(500);
		}

		// Run every hour to update possibly changed client host names
		if(time(NULL) % RERESOLVE_INTERVAL == 0)
		{
			// Try to resolve all client host names (onlynew=false)
			resolveClients(false);
			// Try to resolve all upstream destination host names (onlynew=false)
			resolveForwardDestinations(false);
			// Prevent immediate re-run of this routine
			sleepms(500);
		}

		// Idle for 0.5 sec before checking again the time criteria
		sleepms(500);
	}

	return NULL;
}
