/*
 * ****** Information ******
 *
 * This module allows you to query multible DNSbl servers
 * for abusive clients. (If you don't know what a DNSbl is,
 * you probably don't want to use this module.) It then
 * z:lines that users for 1d (TODO: make this more flexible)
 * if at least one DNSbl comes up with a result.
 * See mask option for restricting the results' ranges.
 *
 * ****** BIG FAT WARNING ******
 *
 * This module is highly experimental. It does not do any
 * kind of DNS caching, so you NEED to run nscd or a own
 * caching DNS server on 127.0.0.1. If you ignore this
 * warning you may piss your hoster off by abusing their DNS
 * servers.
 *
 * THIS MODULE IS HIGHLY EXPERIMENTAL
 *
 *
 * Changes:
 * 0.1  : Initial release
 * 0.2  : Added support for duration and action
 * 0.3  : We need to check if the user is still on
 *        the server before we take action on them
 *        (Thanks to Syzop)
 * 0.4  : Using provided place_hosts_ban() and
 *        banact_stringtoval() now 
 *        (Thanks to Syzop again :))
 * 0.5  : Even more bugs, oops :/
 * 0.6	: More severe(!) bugs... instead of reusing the conf in a request
 *				I now copy all conf data explicitely into each request
 *
 * ****** Instructions *******
 *
 * You need one or more dnsbl blocks in your configuration.
 * Syntax as follows:
 * dnsbl {
 *   name = "Name of your dnsbl";
 *   hostname = "subdomain.pointing.to.the.dnsbl";
 *   reason = "Reason to use";
 *   mask = 255; // 255 means *ANY* return code. If you want
 *               // to ignore some results XOR it from mask
 *               // but the default is safe for most setups
 *	 action = [kline|zline|gline|gzline|shun|tempshun];
 *	 duration = "1d"; // Duration specification
 * };
 * 
 * // No example included!
 */

#include "config.h"
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include "res.h"
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include "h.h"
#ifdef STRIPBADWORDS
#include "badwords.h"
#endif
#ifdef _WIN32
#include "version.h"
#endif

/* Hooks */
DLLFUNC int h_dnsbl_prelocalconnect(aClient *cptr);
DLLFUNC int h_dnsbl_prelocalquit(aClient *cptr);
DLLFUNC int h_dnsbl_configtest();
DLLFUNC int h_dnsbl_configrehash();
DLLFUNC int h_dnsbl_configrun();

/* Helpers */
static void dnsbl_result_parser(void* arg, int status, struct hostent *he);
static void InitConf();
static void FreeConf();
static void removeDNSBLRequest(aClient *cptr);

/* Ares import */
extern ares_channel resolver_channel;

/* Hook pointers */
static Hook *HookPreLocalConnect = NULL;
static Hook *HookPreLocalQuit = NULL;
static Hook *HookConfTest = NULL;
static Hook *HookConfRun = NULL;
static Hook *HookConfRehash = NULL;

/* configuration keeping list */
typedef struct _dnsblconfentry DNSBLConfEntry;
struct _dnsblconfentry {
	DNSBLConfEntry *next;
	char *name;
	char *hostname;
	int mask;
	char *reason;
	int action;
	int duration;
};
static DNSBLConfEntry	*DNSBLConf;

/* ares callback data */
typedef struct _dnsblreq DNSBLReq;
struct _dnsblreq {
	char* name;
	char* hostname;
	int mask;
	char* reason;
	int action;
	int duration;
	aClient *cptr;
};

/* 
 * linked list for handling quits, thanks to Syzop for pointing that out 
 * We need to discard requests for users that quit before the lookup
 * completed.
 */
typedef struct __dnsblreqlist _DNSBLReqList;
struct __dnsblreqlist {
	_DNSBLReqList * next;
	DNSBLReq * data;
};

static _DNSBLReqList * DNSBLReqList = NULL;

/* Module code */
ModuleHeader MOD_HEADER(dnsbl) = {
	"dnsbl", /* Name of module */
	"$Id: m_dnsbl.c,v 0.6 2006/08/31 11:40 satmd Exp $", /* Version */
	"dnsbl", /* Short description of module */
	"3.2-b8-1",
	NULL
};

DLLFUNC int MOD_TEST(dnsbl)(ModuleInfo *modinfo) {
	HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, h_dnsbl_configtest);
	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(dnsbl)(ModuleInfo *modinfo)
{
	InitConf();
	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, h_dnsbl_configrun);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, h_dnsbl_configrehash);
	HookPreLocalConnect = HookAddEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, h_dnsbl_prelocalconnect);
	HookPreLocalQuit = HookAddEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, h_dnsbl_prelocalquit);
	if((!HookPreLocalConnect) || (!HookPreLocalQuit))
		return MOD_FAILED;
		
	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(dnsbl)(int module_load)
{
	return MOD_SUCCESS;
}


DLLFUNC int MOD_UNLOAD(dnsbl)(int module_unload)
{
	HookDel(HookPreLocalConnect);
	HookDel(HookPreLocalQuit);
	HookDel(HookConfRehash);
	HookDel(HookConfRun);
	HookDel(HookConfTest);
	return MOD_SUCCESS;
}

/* scan connecting clients */
DLLFUNC int h_dnsbl_prelocalconnect(aClient *cptr)
{
	char hostname[HOSTLEN + 1];
	char *infobackup;
	char *splitip[4];
	DNSBLReq *request;
	_DNSBLReqList *reqlist=DNSBLReqList;
	DNSBLConfEntry *conf=DNSBLConf;
	infobackup = strdup(Inet_ia2p(&cptr->ip));
	/*
	 * Split Client IP into 4 fields
	 * This code don't work with IPv6.
	 * -- someone with more knowledge
	 *    on IPv6 should fix this
	*/
	if (!(splitip[0] = strtok(infobackup, "."))) {
		free(infobackup);
		return 0;
	}		
	if (!(splitip[1] = strtok(NULL, "."))) {
		free(infobackup);
		return 0;
	}
	if (!(splitip[2] = strtok(NULL, "."))) {
		free(infobackup);
		return 0;
	}
	if (!(splitip[3] = strtok(NULL, "."))) {
		free(infobackup);
		return 0;
	}
	/*
	 * Make sure we got all parts
	 * This code may be unnecessary
	 */
	while(strtok(NULL,"."));
	/*
	 * Iterate over all Entries from the conf
	 * This is actually save since we use
	 * ares callbacks to clean up
	 */
	for(conf=DNSBLConf;conf!=NULL;conf=conf->next) {
		snprintf(hostname, sizeof(hostname), "%s.%s.%s.%s.%s\0",splitip[3],splitip[2],splitip[1],splitip[0],conf->hostname);
		sendto_snomask(SNO_JUNK, "*** made dnsbl lookup for %s (%s)", Inet_ia2p(&cptr->ip), hostname);
		request=MyMallocEx(sizeof(DNSBLReq));
		request->cptr=cptr;
		request->name=strdup(conf->name);
		request->reason=strdup(conf->reason);
		request->hostname=strdup(conf->hostname);
		request->duration=conf->duration;
		request->action=conf->action;
		request->mask=conf->mask;
		DNSBLReqList=MyMallocEx(sizeof(_DNSBLReqList));
		DNSBLReqList->data=request;
		DNSBLReqList->next=reqlist;
		ares_gethostbyname(resolver_channel, hostname, AF_INET, dnsbl_result_parser,request);
	}
	free(infobackup);
	return 0;
}
/* Callback for ares */
static void dnsbl_result_parser(void* arg, int status, struct hostent *he) {
	/* DNSBLs usually start at a offset at 127.0.0.0 */
	const unsigned long RESULTOFFSET=2130706432;
	DNSBLReq *r=(DNSBLReq *)arg;
	unsigned long result=0;
	if (status == ARES_SUCCESS) {
		result=htonl(*((unsigned long *) he->h_addr_list[0])) - RESULTOFFSET;
		result&=r->mask;
		if((result>0) && (r->cptr !=NULL)) {
			sendto_snomask(SNO_EYES, "*** DNSBL lookup on %s for %s returned %d", r->name, Inet_ia2p(&r->cptr->ip), result);
			place_host_ban(r->cptr,r->action, r->reason, r->duration);
		}
	}
	free(r->hostname);
	free(r->reason);
	free(r->name);
	return;
}

static void InitConf() {
	DNSBLConf = NULL;
}

static void FreeConf() {
	DNSBLConfEntry *it=DNSBLConf;
	DNSBLConfEntry *tmp=DNSBLConf;
	while(it!=NULL) {
		tmp=it->next;
		free(it);
		it=tmp;
	}
}

/* check config */
DLLFUNC int h_dnsbl_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) {
	ConfigEntry *cep;
	int errors = 0;
	
	/* local structure */
	char dnsbl_name = 0;
	char dnsbl_hostname = 0;
	char dnsbl_reason = 0;
	char dnsbl_mask = 0;
	
	if(type != CONFIG_MAIN)
		return 0;
		
	if(!strcmp(ce->ce_varname, "dnsbl")) {
		for(cep=ce->ce_entries; cep; cep=cep->ce_next) {
			if(!strcmp(cep->ce_varname, "name")) {
				dnsbl_name=1;
			} else if(!strcmp(cep->ce_varname, "hostname")) {
				dnsbl_hostname=1;
			} else if(!strcmp(cep->ce_varname, "reason")) {
				dnsbl_reason=1;
			} else if(!strcmp(cep->ce_varname, "mask")) {
				dnsbl_mask=1;
			}
		}
		if(dnsbl_name == 0 || dnsbl_hostname == 0 || dnsbl_reason == 0 || dnsbl_mask ==0 ) {
			config_error("%s:%i: Incomplete DNSbl definition [name,hostname,reason:char][mask:int]",
				cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
			errors=1;
			*errs=errors;
			return -1;
		} else {
			return 1;
		}
	}
	return 0;
}

/* run config */
DLLFUNC int h_dnsbl_configrun(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) {
	ConfigEntry *cep;
	int errors = 0;
	DNSBLConfEntry *tmp=DNSBLConf;
	
	/* local structure */
	char *dnsbl_name = NULL;
	char *dnsbl_hostname = NULL;
	char *dnsbl_reason = NULL;
	int dnsbl_mask = 0;
	
	if(type != CONFIG_MAIN)
		return 0;
	
	if(!strcmp(ce->ce_varname, "dnsbl")) {
		DNSBLConf=MyMallocEx(sizeof(DNSBLConfEntry));
		DNSBLConf->next=tmp;
		DNSBLConf->action=0;
		DNSBLConf->duration=86400; // 1 day default
		for(cep=ce->ce_entries; cep; cep=cep->ce_next) {
			if(!strcmp(cep->ce_varname, "name")) {
				DNSBLConf->name=strdup(cep->ce_vardata);
			} else if(!strcmp(cep->ce_varname, "hostname")) {
				DNSBLConf->hostname=strdup(cep->ce_vardata);
			} else if(!strcmp(cep->ce_varname, "reason")) {
				DNSBLConf->reason=strdup(cep->ce_vardata);
			} else if(!strcmp(cep->ce_varname, "mask")) {
				DNSBLConf->mask=config_checkval(cep->ce_vardata,CFG_SIZE);
			} else if(!strcmp(cep->ce_varname, "action")) {
				DNSBLConf->action=banact_stringtoval(cep->ce_vardata);
			} else if(!strcmp(cep->ce_varname, "duration")) {
				DNSBLConf->duration=config_checkval(cep->ce_vardata, CFG_TIME);
			}
		}
		return 1;
	}
	return 0;
}
DLLFUNC int h_dnsbl_configrehash() {
	FreeConf();
	InitConf();
}

int str2DNSBLAction(char * string) {
	if(!stricmp(string, "KLINE"))
		return BAN_ACT_KLINE;
	else if(!stricmp(string, "ZLINE"))
		return BAN_ACT_ZLINE;
	else if(!stricmp(string, "GLINE"))
		return BAN_ACT_GLINE;
	else if(!stricmp(string, "GZLINE"))
		return BAN_ACT_GZLINE;
	else if(!stricmp(string, "SHUN"))
		return BAN_ACT_SHUN;
	else if(!stricmp(string, "TEMPSHUN"))
		return BAN_ACT_TEMPSHUN;
	else
		return BAN_ACT_KILL;
}

static void removeDNSBLRequest(aClient *cptr) {
	_DNSBLReqList *it, *tmp;
	if(DNSBLReqList->data->cptr==cptr) {
		tmp=DNSBLReqList->next;
		free(DNSBLReqList);
		DNSBLReqList=tmp;
	} else {
		for(it=DNSBLReqList->next,tmp=DNSBLReqList;it!=NULL;it=it->next) {
			if(it->data->cptr==cptr) {
				tmp->next=it->next;
				free(it);
				it=tmp->next;
			}
			tmp=tmp->next;
		}
	}
}

DLLFUNC int h_dnsbl_prelocalquit(aClient *cptr) {
	_DNSBLReqList *it;
	for(it=DNSBLReqList;it!=NULL;it=it->next) {
		if(it->data->cptr==cptr)
			it->data->cptr=NULL;
	}
	return;
}
