unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

blacklist.c (22679B)

      1 /*
      2  * Blacklist support (currently only DNS Blacklists)
      3  * (C) Copyright 2015-.. Bram Matthys (Syzop) and the UnrealIRCd team
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 1, or (at your option)
      8  * any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program; if not, write to the Free Software
     17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  */
     19 
     20 #include "unrealircd.h"
     21 #include "dns.h"
     22 
     23 ModuleHeader MOD_HEADER
     24 = {
     25 	"blacklist",
     26 	"5.0",
     27 	"Check connecting users against DNS Blacklists",
     28 	"UnrealIRCd Team",
     29 	"unrealircd-6",
     30 };
     31 
     32 /* In this module and the config syntax I tried to 'abstract' things
     33  * a little, so things could later be extended if we ever want
     34  * to introduce another blacklist type (other than DNSBL).
     35  * Once that happens, best to re-check/audit the source.
     36  */
     37 
     38 /* Types */
     39 
     40 typedef enum {
     41 	DNSBL_RECORD=1, DNSBL_BITMASK=2
     42 } DNSBLType;
     43 
     44 typedef struct DNSBL DNSBL;
     45 struct DNSBL {
     46 	char *name;
     47 	DNSBLType type;
     48 	int *reply;
     49 };
     50 
     51 typedef union BlacklistBackend BlacklistBackend;
     52 union BlacklistBackend
     53 {
     54 	DNSBL *dns;
     55 };
     56 
     57 typedef enum {
     58 	BLACKLIST_BACKEND_DNS = 1
     59 } BlacklistBackendType;
     60 
     61 typedef struct Blacklist Blacklist;
     62 struct Blacklist {
     63 	Blacklist *prev, *next;
     64 	char *name;
     65 	BlacklistBackendType backend_type;
     66 	BlacklistBackend *backend;
     67 	int action;
     68 	long ban_time;
     69 	char *reason;
     70 	SecurityGroup *except;
     71 };
     72 
     73 /* Blacklist user struct. In the c-ares DNS reply callback we need to pass
     74  * some metadata. We can't use client directly there as the client may
     75  * be gone already by the time we receive the DNS reply.
     76  */
     77 typedef struct BLUser BLUser;
     78 struct BLUser {
     79 	Client *client;
     80 	int is_ipv6;
     81 	int refcnt;
     82 	/* The following save_* fields are used by softbans: */
     83 	int save_action;
     84 	long save_tkltime;
     85 	char *save_opernotice;
     86 	char *save_reason;
     87 	char *save_blacklist;
     88 	char *save_blacklist_dns_name;
     89 	int save_blacklist_dns_reply;
     90 };
     91 
     92 /* Global variables */
     93 ModDataInfo *blacklist_md = NULL;
     94 Blacklist *conf_blacklist = NULL;
     95 
     96 /* Forward declarations */
     97 int blacklist_config_test(ConfigFile *, ConfigEntry *, int, int *);
     98 int blacklist_config_run(ConfigFile *, ConfigEntry *, int);
     99 void blacklist_free_conf(void);
    100 void delete_blacklist_block(Blacklist *e);
    101 void blacklist_md_free(ModData *md);
    102 int blacklist_handshake(Client *client);
    103 int blacklist_ip_change(Client *client, const char *oldip);
    104 int blacklist_quit(Client *client, MessageTag *mtags, const char *comment);
    105 int blacklist_preconnect(Client *client);
    106 void blacklist_resolver_callback(void *arg, int status, int timeouts, struct hostent *he);
    107 int blacklist_start_check(Client *client);
    108 int blacklist_dns_request(Client *client, Blacklist *bl);
    109 int blacklist_rehash(void);
    110 int blacklist_rehash_complete(void);
    111 void blacklist_set_handshake_delay(void);
    112 void blacklist_free_bluser_if_able(BLUser *bl);
    113 
    114 #define SetBLUser(x, y)	do { moddata_client(x, blacklist_md).ptr = y; } while(0)
    115 #define BLUSER(x)	((BLUser *)moddata_client(x, blacklist_md).ptr)
    116 
    117 MOD_TEST()
    118 {
    119 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, blacklist_config_test);
    120 
    121 	CallbackAdd(modinfo->handle, CALLBACKTYPE_BLACKLIST_CHECK, blacklist_start_check);
    122 	return MOD_SUCCESS;
    123 }
    124 
    125 /** Called upon module init */
    126 MOD_INIT()
    127 {
    128 	ModDataInfo mreq;
    129 
    130 	MARK_AS_OFFICIAL_MODULE(modinfo);
    131 	/* This module needs to be permanent.
    132 	 * Not because of UnrealIRCd restrictions,
    133 	 * but because we use c-ares callbacks and the address
    134 	 * of those functions will change if we REHASH.
    135 	 */
    136 	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
    137 	
    138 	memset(&mreq, 0, sizeof(mreq));
    139 	mreq.name = "blacklist";
    140 	mreq.type = MODDATATYPE_CLIENT;
    141 	mreq.free = blacklist_md_free;
    142 	blacklist_md = ModDataAdd(modinfo->handle, mreq);
    143 	if (!blacklist_md)
    144 	{
    145 		config_error("could not register blacklist moddata");
    146 		return MOD_FAILED;
    147 	}
    148 
    149 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, blacklist_config_run);
    150 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, blacklist_handshake);
    151 	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, blacklist_ip_change);
    152 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, blacklist_preconnect);
    153 	HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, blacklist_rehash);
    154 	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, blacklist_rehash_complete);
    155 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, blacklist_quit);
    156 
    157 	return MOD_SUCCESS;
    158 }
    159 
    160 /** Called upon module load */
    161 MOD_LOAD()
    162 {
    163 	blacklist_set_handshake_delay();
    164 	return MOD_SUCCESS;
    165 }
    166 
    167 /** Called upon unload */
    168 MOD_UNLOAD()
    169 {
    170 	blacklist_free_conf();
    171 	return MOD_SUCCESS;
    172 }
    173 
    174 int blacklist_rehash(void)
    175 {
    176 	blacklist_free_conf();
    177 	return 0;
    178 }
    179 
    180 int blacklist_rehash_complete(void)
    181 {
    182 	blacklist_set_handshake_delay();
    183 	return 0;
    184 }
    185 
    186 void blacklist_set_handshake_delay(void)
    187 {
    188 	if ((iConf.handshake_delay == -1) && conf_blacklist)
    189 	{
    190 		/*
    191 		Too noisy?
    192 		config_status("[blacklist] I'm setting set::handshake-delay to 2 seconds. "
    193 		              "You may wish to set an explicit setting in the configuration file.");
    194 		config_status("See https://www.unrealircd.org/docs/Set_block#set::handshake-delay");
    195 		*/
    196 		iConf.handshake_delay = 2;
    197 	}
    198 }
    199 
    200 /** Find blacklist { } block */
    201 Blacklist *blacklist_find_block_by_dns(char *name)
    202 {
    203 	Blacklist *d;
    204 	
    205 	for (d = conf_blacklist; d; d = d->next)
    206 		if ((d->backend_type == BLACKLIST_BACKEND_DNS) && !strcmp(name, d->backend->dns->name))
    207 			return d;
    208 
    209 	return NULL;
    210 }
    211 
    212 void blacklist_free_conf(void)
    213 {
    214 	Blacklist *d, *d_next;
    215 
    216 	for (d = conf_blacklist; d; d = d_next)
    217 	{
    218 		d_next = d->next;
    219 		delete_blacklist_block(d);
    220 	}
    221 	conf_blacklist = NULL;
    222 }
    223 
    224 void delete_blacklist_block(Blacklist *e)
    225 {
    226 	if (e->backend_type == BLACKLIST_BACKEND_DNS)
    227 	{
    228 		if (e->backend->dns)
    229 		{
    230 			safe_free(e->backend->dns->name);
    231 			safe_free(e->backend->dns->reply);
    232 			safe_free(e->backend->dns);
    233 		}
    234 	}
    235 	
    236 	safe_free(e->backend);
    237 
    238 	safe_free(e->name);
    239 	safe_free(e->reason);
    240 	free_security_group(e->except);
    241 
    242 	safe_free(e);
    243 }
    244 
    245 int blacklist_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    246 {
    247 	ConfigEntry	*cep, *cepp, *ceppp;
    248 	int errors = 0;
    249 	char has_reason = 0, has_ban_time = 0, has_action = 0;
    250 	char has_dns_type = 0, has_dns_reply = 0, has_dns_name = 0;
    251 
    252 	if (type != CONFIG_MAIN)
    253 		return 0;
    254 	
    255 	if (!ce)
    256 		return 0;
    257 	
    258 	if (strcmp(ce->name, "blacklist"))
    259 		return 0; /* not interested in non-blacklist stuff.. */
    260 	
    261 	if (!ce->value)
    262 	{
    263 		config_error("%s:%i: blacklist block without name (use: blacklist somename { })",
    264 			ce->file->filename, ce->line_number);
    265 		*errs = 1;
    266 		return -1;
    267 	}
    268 
    269 	/* Now actually go parse the blacklist { } block */
    270 	for (cep = ce->items; cep; cep = cep->next)
    271 	{
    272 		if (!strcmp(cep->name, "dns"))
    273 		{
    274 			for (cepp = cep->items; cepp; cepp = cepp->next)
    275 			{
    276 				if (!strcmp(cepp->name, "reply"))
    277 				{
    278 					if (has_dns_reply)
    279 					{
    280 						/* this is an error (not a warning) */
    281 						config_error("%s:%i: blacklist block may contain only one blacklist::dns::reply item. "
    282 									 "You can specify multiple replies by using: reply { 1; 2; 4; };",
    283 									 cepp->file->filename, cepp->line_number);
    284 						errors++;
    285 						continue;
    286 					}
    287 					if (!cepp->value && !cepp->items)
    288 					{
    289 						config_error_blank(cepp->file->filename, cepp->line_number, "blacklist::dns::reply");
    290 						errors++;
    291 						continue;
    292 					}
    293 					has_dns_reply = 1; /* we have a reply. now whether it's actually valid is another story.. */
    294 					if (cepp->value && cepp->items)
    295 					{
    296 						config_error("%s:%i: blacklist::dns::reply must be either using format 'reply 1;' or "
    297 									 "'reply { 1; 2; 4; }; but not both formats at the same time.",
    298 									 cepp->file->filename, cepp->line_number);
    299 						errors++;
    300 						continue;
    301 					}
    302 					if (cepp->value)
    303 					{
    304 						if (atoi(cepp->value) <= 0)
    305 						{
    306 							config_error("%s:%i: blacklist::dns::reply must be >0",
    307 								cepp->file->filename, cepp->line_number);
    308 							errors++;
    309 							continue;
    310 						}
    311 					}
    312 					if (cepp->items)
    313 					{
    314 						for (ceppp = cepp->items; ceppp; ceppp=ceppp->next)
    315 						{
    316 							if (atoi(ceppp->name) <= 0)
    317 							{
    318 								config_error("%s:%i: all items in blacklist::dns::reply must be >0",
    319 									cepp->file->filename, cepp->line_number);
    320 								errors++;
    321 							}
    322 						}
    323 					}
    324 				} else
    325 				if (!cepp->value)
    326 				{
    327 					config_error_empty(cepp->file->filename, cepp->line_number,
    328 						"blacklist::dns", cepp->name);
    329 					errors++;
    330 					continue;
    331 				} else
    332 				if (!strcmp(cepp->name, "name"))
    333 				{
    334 					if (has_dns_name)
    335 					{
    336 						config_warn_duplicate(cepp->file->filename,
    337 							cepp->line_number, "blacklist::dns::name");
    338 					}
    339 					has_dns_name = 1;
    340 				} else
    341 				if (!strcmp(cepp->name, "type"))
    342 				{
    343 					if (has_dns_type)
    344 					{
    345 						config_warn_duplicate(cepp->file->filename,
    346 							cepp->line_number, "blacklist::dns::type");
    347 					}
    348 					has_dns_type = 1;
    349 					if (!strcmp(cepp->value, "record"))
    350 						;
    351 					else if (!strcmp(cepp->value, "bitmask"))
    352 						;
    353 					else
    354 					{
    355 						config_error("%s:%i: unknown blacklist::dns::type '%s', must be either 'record' or 'bitmask'",
    356 							cepp->file->filename, cepp->line_number, cepp->value);
    357 						errors++;
    358 					}
    359 				}
    360 			}
    361 		} else
    362 		if (!strcmp(cep->name, "except"))
    363 		{
    364 			test_match_block(cf, cep, &errors);
    365 		} else
    366 		if (!cep->value)
    367 		{
    368 			config_error_empty(cep->file->filename, cep->line_number,
    369 				"blacklist", cep->name);
    370 			errors++;
    371 			continue;
    372 		}
    373 		else if (!strcmp(cep->name, "action"))
    374 		{
    375 			if (has_action)
    376 			{
    377 				config_warn_duplicate(cep->file->filename,
    378 					cep->line_number, "blacklist::action");
    379 				continue;
    380 			}
    381 			has_action = 1;
    382 			if (!banact_stringtoval(cep->value))
    383 			{
    384 				config_error("%s:%i: blacklist::action has unknown action type '%s'",
    385 					cep->file->filename, cep->line_number, cep->value);
    386 				errors++;
    387 			}
    388 		}
    389 		else if (!strcmp(cep->name, "ban-time"))
    390 		{
    391 			if (has_ban_time)
    392 			{
    393 				config_warn_duplicate(cep->file->filename,
    394 					cep->line_number, "blacklist::ban-time");
    395 				continue;
    396 			}
    397 			has_ban_time = 1;
    398 		}
    399 		else if (!strcmp(cep->name, "reason"))
    400 		{
    401 			if (has_reason)
    402 			{
    403 				config_warn_duplicate(cep->file->filename,
    404 					cep->line_number, "blacklist::reason");
    405 				continue;
    406 			}
    407 			has_reason = 1;
    408 		}
    409 		else
    410 		{
    411 			config_error_unknown(cep->file->filename, cep->line_number,
    412 				"blacklist", cep->name);
    413 			errors++;
    414 		}
    415 	}
    416 
    417 	if (!has_action)
    418 	{
    419 		config_error_missing(ce->file->filename, ce->line_number,
    420 			"blacklist::action");
    421 		errors++;
    422 	}
    423 
    424 	if (!has_reason)
    425 	{
    426 		config_error_missing(ce->file->filename, ce->line_number,
    427 			"blacklist::reason");
    428 		errors++;
    429 	}
    430 
    431 	if (!has_dns_name)
    432 	{
    433 		config_error_missing(ce->file->filename, ce->line_number,
    434 			"blacklist::dns::name");
    435 		errors++;
    436 	}
    437 
    438 	if (!has_dns_type)
    439 	{
    440 		config_error_missing(ce->file->filename, ce->line_number,
    441 			"blacklist::dns::type");
    442 		errors++;
    443 	}
    444 
    445 	if (!has_dns_reply)
    446 	{
    447 		config_error_missing(ce->file->filename, ce->line_number,
    448 			"blacklist::dns::reply");
    449 		errors++;
    450 	}
    451 
    452 	*errs = errors;
    453 	return errors ? -1 : 1;
    454 }
    455 
    456 int blacklist_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    457 {
    458 	ConfigEntry *cep, *cepp, *ceppp;
    459 	Blacklist *d = NULL;
    460 	
    461 	if (type != CONFIG_MAIN)
    462 		return 0;
    463 	
    464 	if (!ce || !ce->name || strcmp(ce->name, "blacklist"))
    465 		return 0; /* not interested */
    466 
    467 	d = safe_alloc(sizeof(Blacklist));
    468 	safe_strdup(d->name, ce->value);
    469 	/* set some defaults */
    470 	d->action = BAN_ACT_KILL;
    471 	safe_strdup(d->reason, "Your IP is on a DNS Blacklist");
    472 	d->ban_time = 3600;
    473 	
    474 	/* assume dns for now ;) */
    475 	d->backend_type = BLACKLIST_BACKEND_DNS;
    476 	d->backend = safe_alloc(sizeof(BlacklistBackend));
    477 	d->backend->dns = safe_alloc(sizeof(DNSBL));
    478 
    479 	for (cep = ce->items; cep; cep = cep->next)
    480 	{
    481 		if (!strcmp(cep->name, "dns"))
    482 		{
    483 			for (cepp = cep->items; cepp; cepp = cepp->next)
    484 			{
    485 				if (!strcmp(cepp->name, "reply"))
    486 				{
    487 					if (cepp->value)
    488 					{
    489 						/* single reply */
    490 						d->backend->dns->reply = safe_alloc(sizeof(int)*2);
    491 						d->backend->dns->reply[0] = atoi(cepp->value);
    492 						d->backend->dns->reply[1] = 0;
    493 					} else
    494 					if (cepp->items)
    495 					{
    496 						/* (potentially) multiple reply values */
    497 						int cnt = 0;
    498 						for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
    499 						{
    500 							if (ceppp->name)
    501 								cnt++;
    502 						}
    503 						
    504 						if (cnt == 0)
    505 							abort(); /* impossible */
    506 						
    507 						d->backend->dns->reply = safe_alloc(sizeof(int)*(cnt+1));
    508 						
    509 						cnt = 0;
    510 						for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
    511 						{
    512 							d->backend->dns->reply[cnt++] = atoi(ceppp->name);
    513 						}
    514 						d->backend->dns->reply[cnt] = 0;
    515 					}
    516 				} else
    517 				if (!strcmp(cepp->name, "type"))
    518 				{
    519 					if (!strcmp(cepp->value, "record"))
    520 						d->backend->dns->type = DNSBL_RECORD;
    521 					else if (!strcmp(cepp->value, "bitmask"))
    522 						d->backend->dns->type = DNSBL_BITMASK;
    523 				} else
    524 				if (!strcmp(cepp->name, "name"))
    525 				{
    526 					safe_strdup(d->backend->dns->name, cepp->value);
    527 				}
    528 			}
    529 		}
    530 		else if (!strcmp(cep->name, "action"))
    531 		{
    532 			d->action = banact_stringtoval(cep->value);
    533 		}
    534 		else if (!strcmp(cep->name, "ban-time"))
    535 		{
    536 			d->ban_time = config_checkval(cep->value, CFG_TIME);
    537 		}
    538 		else if (!strcmp(cep->name, "reason"))
    539 		{
    540 			safe_strdup(d->reason, cep->value);
    541 		}
    542 		else if (!strcmp(cep->name, "except"))
    543 		{
    544 			conf_match_block(cf, cep, &d->except);
    545 		}
    546 	}
    547 
    548 	AddListItem(d, conf_blacklist);
    549 	
    550 	return 0;
    551 }
    552 
    553 void blacklist_md_free(ModData *md)
    554 {
    555 	BLUser *bl = md->ptr;
    556 
    557 	/* Mark bl->client as dead. Free the struct, if able. */
    558 	blacklist_free_bluser_if_able(bl);
    559 
    560 	md->ptr = NULL;
    561 }
    562 
    563 int blacklist_handshake(Client *client)
    564 {
    565 	blacklist_start_check(client);
    566 	return 0;
    567 }
    568 
    569 int blacklist_ip_change(Client *client, const char *oldip)
    570 {
    571 	blacklist_start_check(client);
    572 	return 0;
    573 }
    574 
    575 int blacklist_start_check(Client *client)
    576 {
    577 	Blacklist *bl;
    578 
    579 	if (find_tkl_exception(TKL_BLACKLIST, client))
    580 	{
    581 		/* If the user is exempt from DNSBL checking then:
    582 		 * 1) Don't bother checking DNSBL's
    583 		 * 2) Disable handshake delay for this user, since it serves no purpose.
    584 		 */
    585 		SetNoHandshakeDelay(client);
    586 		return 0;
    587 	}
    588 
    589 	if (!BLUSER(client))
    590 	{
    591 		SetBLUser(client, safe_alloc(sizeof(BLUser)));
    592 		BLUSER(client)->client = client;
    593 	}
    594 
    595 	for (bl = conf_blacklist; bl; bl = bl->next)
    596 	{
    597 		/* Stop processing if client is (being) killed already */
    598 		if (!BLUSER(client))
    599 			break;
    600 
    601 		/* Check if user is exempt (then don't bother checking) */
    602 		if (user_allowed_by_security_group(client, bl->except))
    603 			continue;
    604 
    605 		/* Initiate blacklist requests */
    606 		if (bl->backend_type == BLACKLIST_BACKEND_DNS)
    607 			blacklist_dns_request(client, bl);
    608 	}
    609 	
    610 	return 0;
    611 }
    612 
    613 int blacklist_dns_request(Client *client, Blacklist *d)
    614 {
    615 	char buf[256], wbuf[128];
    616 	unsigned int e[8];
    617 	char *ip = GetIP(client);
    618 	
    619 	if (!ip)
    620 		return 0;
    621 
    622 	memset(&e, 0, sizeof(e));
    623 
    624 	if (strchr(ip, '.'))
    625 	{
    626 		/* IPv4 */
    627 		if (sscanf(ip, "%u.%u.%u.%u", &e[0], &e[1], &e[2], &e[3]) != 4)
    628 			return 0;
    629 	
    630 		snprintf(buf, sizeof(buf), "%u.%u.%u.%u.%s", e[3], e[2], e[1], e[0], d->backend->dns->name);
    631 	} else
    632 	if (strchr(ip, ':'))
    633 	{
    634 		/* IPv6 */
    635 		int i;
    636 		BLUSER(client)->is_ipv6 = 1;
    637 		if (sscanf(ip, "%x:%x:%x:%x:%x:%x:%x:%x",
    638 		    &e[0], &e[1], &e[2], &e[3], &e[4], &e[5], &e[6], &e[7]) != 8)
    639 		{
    640 			return 0;
    641 		}
    642 		*buf = '\0';
    643 		for (i = 7; i >= 0; i--)
    644 		{
    645 			snprintf(wbuf, sizeof(wbuf), "%x.%x.%x.%x.",
    646 				(unsigned int)(e[i] & 0xf),
    647 				(unsigned int)((e[i] >> 4) & 0xf),
    648 				(unsigned int)((e[i] >> 8) & 0xf),
    649 				(unsigned int)((e[i] >> 12) & 0xf));
    650 			strlcat(buf, wbuf, sizeof(buf));
    651 		}
    652 		strlcat(buf, d->backend->dns->name, sizeof(buf));
    653 	}
    654 	else
    655 		return 0; /* unknown IP format */
    656 
    657 	BLUSER(client)->refcnt++; /* one (more) blacklist result remaining */
    658 	
    659 	unreal_gethostbyname(buf, AF_INET, blacklist_resolver_callback, BLUSER(client));
    660 	
    661 	return 0;
    662 }
    663 
    664 void blacklist_cancel(BLUser *bl)
    665 {
    666 	bl->client = NULL;
    667 }
    668 
    669 int blacklist_quit(Client *client, MessageTag *mtags, const char *comment)
    670 {
    671 	if (BLUSER(client))
    672 		blacklist_cancel(BLUSER(client));
    673 
    674 	return 0;
    675 }
    676 
    677 /** Free the BLUSER() struct, if we are able to do so.
    678  * This should only be called if the underlying client is dead or dyeing
    679  * and not earlier.
    680  * Reasons why we 'are not able' are: refcnt is non-zero, that is:
    681  * there is still an outstanding resolver request (eg: slow blacklist).
    682  * In that case, no worries, we will be called again after that request
    683  * is finished.
    684  */
    685 void blacklist_free_bluser_if_able(BLUser *bl)
    686 {
    687 	if (bl->client)
    688 		bl->client = NULL;
    689 
    690 	if (bl->refcnt > 0)
    691 		return; /* unable, still have DNS requests/replies in-flight */
    692 
    693 	safe_free(bl->save_opernotice);
    694 	safe_free(bl->save_reason);
    695 	safe_free(bl);
    696 }
    697 
    698 char *getdnsblname(char *p, Client *client)
    699 {
    700 	int dots = 0;
    701 	int dots_count;
    702 
    703 	if (!client)
    704 		return NULL;
    705 
    706 	if (BLUSER(client)->is_ipv6)
    707 		dots_count = 32;
    708 	else
    709 		dots_count = 4;
    710 
    711 	for (; *p; p++)
    712 	{
    713 		if (*p == '.')
    714 		{
    715 			dots++;
    716 			if (dots == dots_count)
    717 				return p+1;
    718 		}
    719 	}
    720 	return NULL;
    721 }
    722 
    723 /* Parse DNS reply.
    724  * A reply will be an A record in the format x.x.x.<reply>
    725  */
    726 int blacklist_parse_reply(struct hostent *he, int entry)
    727 {
    728 	char ipbuf[64];
    729 	char *p;
    730 
    731 	if ((he->h_addrtype != AF_INET) || (he->h_length != 4))
    732 		return 0;
    733 
    734 	*ipbuf = '\0';
    735 	if (!inet_ntop(AF_INET, he->h_addr_list[entry], ipbuf, sizeof(ipbuf)))
    736 		return 0;
    737 	
    738 	p = strrchr(ipbuf, '.');
    739 	if (!p)
    740 		return 0;
    741 	
    742 	return atoi(p+1);
    743 }
    744 
    745 /** Take the actual ban action.
    746  * Called from blacklist_hit() and for immediate bans and
    747  * from blacklist_preconnect() for softbans that need to be delayed
    748  * as to give the user the opportunity to do SASL Authentication.
    749  */
    750 int blacklist_action(Client *client, char *opernotice, BanAction ban_action, char *ban_reason, long ban_time,
    751                      char *blacklist, char *blacklist_dns_name, int blacklist_dns_reply)
    752 {
    753 	unreal_log_raw(ULOG_INFO, "blacklist", "BLACKLIST_HIT", client,
    754 	               opernotice,
    755 	               log_data_string("blacklist_name", blacklist),
    756 	               log_data_string("blacklist_dns_name", blacklist_dns_name),
    757 	               log_data_integer("blacklist_dns_reply", blacklist_dns_reply),
    758 	               log_data_string("ban_action", banact_valtostring(ban_action)),
    759 	               log_data_string("ban_reason", ban_reason),
    760 	               log_data_integer("ban_time", ban_time));
    761 	if (ban_action == BAN_ACT_WARN)
    762 		return 0;
    763 	return place_host_ban(client, ban_action, ban_reason, ban_time);
    764 }
    765 
    766 void blacklist_hit(Client *client, Blacklist *bl, int reply)
    767 {
    768 	char opernotice[512], banbuf[512], reply_num[5];
    769 	const char *name[6], *value[6];
    770 	BLUser *blu = BLUSER(client);
    771 
    772 	if (find_tkline_match(client, 1))
    773 		return; /* already klined/glined. Don't send the warning from below. */
    774 
    775 	if (IsUser(client))
    776 		snprintf(opernotice, sizeof(opernotice), "[Blacklist] IP %s (%s) matches blacklist %s (%s/reply=%d)",
    777 			GetIP(client), client->name, bl->name, bl->backend->dns->name, reply);
    778 	else
    779 		snprintf(opernotice, sizeof(opernotice), "[Blacklist] IP %s matches blacklist %s (%s/reply=%d)",
    780 			GetIP(client), bl->name, bl->backend->dns->name, reply);
    781 
    782 	snprintf(reply_num, sizeof(reply_num), "%d", reply);
    783 
    784 	name[0] = "ip";
    785 	value[0] = GetIP(client);
    786 	name[1] = "server";
    787 	value[1] = me.name;
    788 	name[2] = "blacklist";
    789 	value[2] = bl->name;
    790 	name[3] = "dnsname";
    791 	value[3] = bl->backend->dns->name;
    792 	name[4] = "dnsreply";
    793 	value[4] = reply_num;
    794 	name[5] = NULL;
    795 	value[5] = NULL;
    796 	/* when adding more, be sure to update the array elements number in the definition of const char *name[] and value[] */
    797 
    798 	buildvarstring(bl->reason, banbuf, sizeof(banbuf), name, value);
    799 
    800 	if (IsSoftBanAction(bl->action) && blu)
    801 	{
    802 		/* For soft bans, delay the action until later (so user can do SASL auth) */
    803 		blu->save_action = bl->action;
    804 		blu->save_tkltime = bl->ban_time;
    805 		safe_strdup(blu->save_opernotice, opernotice);
    806 		safe_strdup(blu->save_reason, banbuf);
    807 		safe_strdup(blu->save_blacklist, bl->name);
    808 		safe_strdup(blu->save_blacklist_dns_name, bl->backend->dns->name);
    809 		blu->save_blacklist_dns_reply = reply;
    810 	} else {
    811 		/* Otherwise, execute the action immediately */
    812 		blacklist_action(client, opernotice, bl->action, banbuf, bl->ban_time, bl->name, bl->backend->dns->name, reply);
    813 	}
    814 }
    815 
    816 void blacklist_process_result(Client *client, int status, struct hostent *he)
    817 {
    818 	Blacklist *bl;
    819 	char *domain;
    820 	int reply;
    821 	int i;
    822 	int replycnt;
    823 	
    824 	if ((status != 0) || (he->h_length != 4) || !he->h_name)
    825 		return; /* invalid reply */
    826 	
    827 	domain = getdnsblname(he->h_name, client);
    828 	if (!domain)
    829 		return; /* odd */
    830 	bl = blacklist_find_block_by_dns(domain);
    831 	if (!bl)
    832 		return; /* possibly just rehashed and the blacklist block is gone now */
    833 	
    834 	/* walk through all replies for this record... until we have a hit */
    835 	for (replycnt=0; he->h_addr_list[replycnt]; replycnt++)
    836 	{
    837 		reply = blacklist_parse_reply(he, replycnt);
    838 
    839 		for (i = 0; bl->backend->dns->reply[i]; i++)
    840 		{
    841 			if ((bl->backend->dns->reply[i] == -1) ||
    842 				( (bl->backend->dns->type == DNSBL_BITMASK) && (reply & bl->backend->dns->reply[i]) ) ||
    843 				( (bl->backend->dns->type == DNSBL_RECORD) && (bl->backend->dns->reply[i] == reply) ) )
    844 			{
    845 				blacklist_hit(client, bl, reply);
    846 				return;
    847 			}
    848 		}
    849 	}
    850 }
    851 
    852 void blacklist_resolver_callback(void *arg, int status, int timeouts, struct hostent *he)
    853 {
    854 	BLUser *blu = (BLUser *)arg;
    855 	Client *client = blu->client;
    856 
    857 	blu->refcnt--; /* one less outstanding DNS request remaining */
    858 
    859 	/* If we are the last to resolve something and the client is gone
    860 	 * already then free the struct.
    861 	 */
    862 	if ((blu->refcnt == 0) && !client)
    863 		blacklist_free_bluser_if_able(blu);
    864 
    865 	blu = NULL;
    866 
    867 	if (!client)
    868 		return; /* Client left already */
    869 	/* ^^ note: do not merge this with the other 'if' a few lines up (refcnt!) */
    870 
    871 	blacklist_process_result(client, status, he);
    872 }
    873 
    874 int blacklist_preconnect(Client *client)
    875 {
    876 	BLUser *blu = BLUSER(client);
    877 
    878 	if (!blu || !blu->save_action)
    879 		return HOOK_CONTINUE;
    880 
    881 	/* There was a pending softban... has the user authenticated via SASL by now? */
    882 	if (IsLoggedIn(client))
    883 		return HOOK_CONTINUE; /* yup, so the softban does not apply. */
    884 
    885 	if (blacklist_action(client, blu->save_opernotice, blu->save_action, blu->save_reason, blu->save_tkltime,
    886 	                     blu->save_blacklist, blu->save_blacklist_dns_name, blu->save_blacklist_dns_reply))
    887 	{
    888 		return HOOK_DENY;
    889 	}
    890 	return HOOK_CONTINUE; /* exempt */
    891 }