unrealircd

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

tkldb.c (21482B)

      1 /*
      2  * Stores active *-Lines (G-Lines etc) inside a .db file for persistency
      3  * (C) Copyright 2019 Gottem 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 
     22 ModuleHeader MOD_HEADER = {
     23 	"tkldb",
     24 	"1.10",
     25 	"Stores active TKL entries (*-Lines) persistently/across IRCd restarts",
     26 	"UnrealIRCd Team",
     27 	"unrealircd-6",
     28 };
     29 
     30 #define TKLDB_MAGIC 0x10101010
     31 /* Database version */
     32 #define TKLDB_VERSION 4999
     33 /* Save tkls to file every <this> seconds */
     34 #define TKLDB_SAVE_EVERY 300
     35 /* The very first save after boot, apply this delta, this
     36  * so we don't coincide with other (potentially) expensive
     37  * I/O events like saving channeldb.
     38  */
     39 #define TKLDB_SAVE_EVERY_DELTA +15
     40 
     41 // #undef BENCHMARK
     42 /* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
     43  * 100,000 zlines:
     44  * - load db: 510 ms
     45  * - save db:  72 ms
     46  * Thus, saving does not take much time and can be done by a timer
     47  * which executes every 5 minutes.
     48  * Of course, exact figures will depend on the machine.
     49  */
     50 
     51 #define FreeTKLRead() \
     52  	do { \
     53 		/* Some of these might be NULL */ \
     54 		if (tkl) \
     55 			free_tkl(tkl); \
     56 	} while(0)
     57 
     58 #define WARN_WRITE_ERROR(fname) \
     59 	do { \
     60 		unreal_log(ULOG_ERROR, "tkldb", "TKLDB_FILE_WRITE_ERROR", NULL, \
     61 			   "[tkldb] Error writing to temporary database file $filename: $system_error", \
     62 			   log_data_string("filename", fname), \
     63 			   log_data_string("system_error", unrealdb_get_error_string())); \
     64 	} while(0)
     65 
     66 #define R_SAFE(x) \
     67 	do { \
     68 		if (!(x)) { \
     69 			config_warn("[tkldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
     70 			unrealdb_close(db); \
     71 			FreeTKLRead(); \
     72 			return 0; \
     73 		} \
     74 	} while(0)
     75 
     76 #define W_SAFE(x) \
     77 	do { \
     78 		if (!(x)) { \
     79 			WARN_WRITE_ERROR(tmpfname); \
     80 			unrealdb_close(db); \
     81 			return 0; \
     82 		} \
     83 	} while(0)
     84 
     85 #define IsMDErr(x, y, z) \
     86 	do { \
     87 		if (!(x)) { \
     88 			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
     89 			return MOD_FAILED; \
     90 		} \
     91 	} while(0)
     92 
     93 /* Structs */
     94 struct cfgstruct {
     95 	char *database;
     96 	char *db_secret;
     97 };
     98 
     99 /* Forward declarations */
    100 void tkldb_moddata_free(ModData *md);
    101 void setcfg(struct cfgstruct *cfg);
    102 void freecfg(struct cfgstruct *cfg);
    103 int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
    104 int tkldb_config_posttest(int *errs);
    105 int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
    106 EVENT(write_tkldb_evt);
    107 int write_tkldb(void);
    108 int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl);
    109 int read_tkldb(void);
    110 
    111 /* Globals variables */
    112 const uint32_t tkldb_version = TKLDB_VERSION;
    113 static struct cfgstruct cfg;
    114 static struct cfgstruct test;
    115 
    116 static long tkldb_next_event = 0;
    117 
    118 MOD_TEST()
    119 {
    120 	memset(&cfg, 0, sizeof(cfg));
    121 	memset(&test, 0, sizeof(test));
    122 	setcfg(&test);
    123 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkldb_config_test);
    124 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, tkldb_config_posttest);
    125 	return MOD_SUCCESS;
    126 }
    127 
    128 MOD_INIT()
    129 {
    130 	MARK_AS_OFFICIAL_MODULE(modinfo);
    131 	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -9999);
    132 
    133 	LoadPersistentLong(modinfo, tkldb_next_event);
    134 
    135 	setcfg(&cfg);
    136 
    137 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkldb_config_run);
    138 	return MOD_SUCCESS;
    139 }
    140 
    141 MOD_LOAD()
    142 {
    143 	if (!tkldb_next_event)
    144 	{
    145 		/* If this is the first time that our module is loaded, then
    146 		 * read the TKL DB and add all *-Lines.
    147 		 */
    148 		if (!read_tkldb())
    149 		{
    150 			char fname[512];
    151 			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
    152 			if (rename(cfg.database, fname) == 0)
    153 				config_warn("[tkldb] Existing database renamed to %s and starting a new one...", fname);
    154 			else
    155 				config_warn("[tkldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
    156 		}
    157 		tkldb_next_event = TStime() + TKLDB_SAVE_EVERY + TKLDB_SAVE_EVERY_DELTA;
    158 	}
    159 	EventAdd(modinfo->handle, "tkldb_write_tkldb", write_tkldb_evt, NULL, 1000, 0);
    160 	return MOD_SUCCESS;
    161 }
    162 
    163 MOD_UNLOAD()
    164 {
    165 	if (loop.terminating)
    166 		write_tkldb();
    167 	freecfg(&test);
    168 	freecfg(&cfg);
    169 	SavePersistentLong(modinfo, tkldb_next_event);
    170 	return MOD_SUCCESS;
    171 }
    172 
    173 void tkldb_moddata_free(ModData *md)
    174 {
    175 	if (md->i)
    176 		md->i = 0;
    177 }
    178 
    179 void setcfg(struct cfgstruct *cfg)
    180 {
    181 	// Default: data/tkl.db
    182 	safe_strdup(cfg->database, "tkl.db");
    183 	convert_to_absolute_path(&cfg->database, PERMDATADIR);
    184 }
    185 
    186 void freecfg(struct cfgstruct *cfg)
    187 {
    188 	safe_free(cfg->database);
    189 	safe_free(cfg->db_secret);
    190 }
    191 
    192 int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    193 {
    194 	int errors = 0;
    195 	ConfigEntry *cep;
    196 
    197 	// We are only interested in set::tkldb::database
    198 	if (type != CONFIG_SET)
    199 		return 0;
    200 
    201 	if (!ce || strcmp(ce->name, "tkldb"))
    202 		return 0;
    203 
    204 	for (cep = ce->items; cep; cep = cep->next)
    205 	{
    206 		if (!cep->value)
    207 		{
    208 			config_error("%s:%i: blank set::tkldb::%s without value", cep->file->filename, cep->line_number, cep->name);
    209 			errors++;
    210 		} else
    211 		if (!strcmp(cep->name, "database"))
    212 		{
    213 			convert_to_absolute_path(&cep->value, PERMDATADIR);
    214 			safe_strdup(test.database, cep->value);
    215 		} else
    216 		if (!strcmp(cep->name, "db-secret"))
    217 		{
    218 			const char *err;
    219 			if ((err = unrealdb_test_secret(cep->value)))
    220 			{
    221 				config_error("%s:%i: set::tkldb::db-secret: %s", cep->file->filename, cep->line_number, err);
    222 				errors++;
    223 				continue;
    224 			}
    225 			safe_strdup(test.db_secret, cep->value);
    226 		} else
    227 		{
    228 			config_error("%s:%i: unknown directive set::tkldb::%s", cep->file->filename, cep->line_number, cep->name);
    229 			errors++;
    230 		}
    231 	}
    232 
    233 	*errs = errors;
    234 	return errors ? -1 : 1;
    235 }
    236 
    237 int tkldb_config_posttest(int *errs)
    238 {
    239 	int errors = 0;
    240 	char *errstr;
    241 
    242 	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
    243 	{
    244 		config_error("[tkldb] %s", errstr);
    245 		errors++;
    246 	}
    247 
    248 	*errs = errors;
    249 	return errors ? -1 : 1;
    250 }
    251 
    252 int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    253 {
    254 	ConfigEntry *cep;
    255 
    256 	// We are only interested in set::tkldb::database
    257 	if (type != CONFIG_SET)
    258 		return 0;
    259 
    260 	if (!ce || strcmp(ce->name, "tkldb"))
    261 		return 0;
    262 
    263 	for (cep = ce->items; cep; cep = cep->next)
    264 	{
    265 		if (!strcmp(cep->name, "database"))
    266 			safe_strdup(cfg.database, cep->value);
    267 		else if (!strcmp(cep->name, "db-secret"))
    268 			safe_strdup(cfg.db_secret, cep->value);
    269 	}
    270 	return 1;
    271 }
    272 
    273 EVENT(write_tkldb_evt)
    274 {
    275 	if (tkldb_next_event > TStime())
    276 		return;
    277 	tkldb_next_event = TStime() + TKLDB_SAVE_EVERY;
    278 	write_tkldb();
    279 }
    280 
    281 int write_tkldb(void)
    282 {
    283 	char tmpfname[512];
    284 	UnrealDB *db;
    285 	uint64_t tklcount;
    286 	int index, index2;
    287 	TKL *tkl;
    288 #ifdef BENCHMARK
    289 	struct timeval tv_alpha, tv_beta;
    290 
    291 	gettimeofday(&tv_alpha, NULL);
    292 #endif
    293 
    294 	// Write to a tempfile first, then rename it if everything succeeded
    295 	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
    296 	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
    297 	if (!db)
    298 	{
    299 		WARN_WRITE_ERROR(tmpfname);
    300 		return 0;
    301 	}
    302 
    303 	W_SAFE(unrealdb_write_int32(db, TKLDB_MAGIC));
    304 	W_SAFE(unrealdb_write_int32(db, tkldb_version));
    305 
    306 	// Count the *-Lines
    307 	tklcount = 0;
    308 
    309 	// First the ones in the hash table
    310 	for (index = 0; index < TKLIPHASHLEN1; index++)
    311 	{
    312 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
    313 		{
    314 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
    315 			{
    316 				if (tkl->flags & TKL_FLAG_CONFIG)
    317 					continue; /* config entry */
    318 				tklcount++;
    319 			}
    320 		}
    321 	}
    322 	// Then the regular *-Lines
    323 	for (index = 0; index < TKLISTLEN; index++)
    324 	{
    325 		for (tkl = tklines[index]; tkl; tkl = tkl->next)
    326 		{
    327 			if (tkl->flags & TKL_FLAG_CONFIG)
    328 				continue; /* config entry */
    329 			tklcount++;
    330 		}
    331 	}
    332 	W_SAFE(unrealdb_write_int64(db, tklcount));
    333 
    334 	// Now write the actual *-Lines, first the ones in the hash table
    335 	for (index = 0; index < TKLIPHASHLEN1; index++)
    336 	{
    337 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
    338 		{
    339 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
    340 			{
    341 				if (tkl->flags & TKL_FLAG_CONFIG)
    342 					continue; /* config entry */
    343 				if (!write_tkline(db, tmpfname, tkl)) // write_tkline() closes the db on errors itself
    344 					return 0;
    345 			}
    346 		}
    347 	}
    348 	// Then the regular *-Lines
    349 	for (index = 0; index < TKLISTLEN; index++)
    350 	{
    351 		for (tkl = tklines[index]; tkl; tkl = tkl->next)
    352 		{
    353 			if (tkl->flags & TKL_FLAG_CONFIG)
    354 				continue; /* config entry */
    355 			if (!write_tkline(db, tmpfname, tkl))
    356 				return 0;
    357 		}
    358 	}
    359 
    360 	// Everything seems to have gone well, attempt to close and rename the tempfile
    361 	if (!unrealdb_close(db))
    362 	{
    363 		WARN_WRITE_ERROR(tmpfname);
    364 		return 0;
    365 	}
    366 #ifdef _WIN32
    367 	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
    368 	unlink(cfg.database);
    369 #endif
    370 	if (rename(tmpfname, cfg.database) < 0)
    371 	{
    372 		config_error("[tkldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
    373 		return 0;
    374 	}
    375 #ifdef BENCHMARK
    376 	gettimeofday(&tv_beta, NULL);
    377 	config_status("[tkldb] Benchmark: SAVE DB: %lld microseconds",
    378 		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
    379 #endif
    380 	return 1;
    381 }
    382 
    383 /** Write a TKL entry */
    384 int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
    385 {
    386 	char tkltype;
    387 	char buf[256];
    388 
    389 	/* First, write the common attributes */
    390 	tkltype = tkl_typetochar(tkl->type);
    391 	W_SAFE(unrealdb_write_char(db, tkltype)); // TKL char
    392 
    393 	W_SAFE(unrealdb_write_str(db, tkl->set_by));
    394 	W_SAFE(unrealdb_write_int64(db, tkl->set_at));
    395 	W_SAFE(unrealdb_write_int64(db, tkl->expire_at));
    396 
    397 	if (TKLIsServerBan(tkl))
    398 	{
    399 		char *usermask = tkl->ptr.serverban->usermask;
    400 		if (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)
    401 		{
    402 			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.serverban->usermask);
    403 			usermask = buf;
    404 		}
    405 		W_SAFE(unrealdb_write_str(db, usermask));
    406 		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->hostmask));
    407 		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->reason));
    408 	} else
    409 	if (TKLIsBanException(tkl))
    410 	{
    411 		char *usermask = tkl->ptr.banexception->usermask;
    412 		if (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)
    413 		{
    414 			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.banexception->usermask);
    415 			usermask = buf;
    416 		}
    417 		W_SAFE(unrealdb_write_str(db, usermask));
    418 		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->hostmask));
    419 		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->bantypes));
    420 		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->reason));
    421 	} else
    422 	if (TKLIsNameBan(tkl))
    423 	{
    424 		char *hold = tkl->ptr.nameban->hold ? "H" : "*";
    425 		W_SAFE(unrealdb_write_str(db, hold));
    426 		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->name));
    427 		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->reason));
    428 	} else
    429 	if (TKLIsSpamfilter(tkl))
    430 	{
    431 		char *match_type = unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type);
    432 		char *target = spamfilter_target_inttostring(tkl->ptr.spamfilter->target);
    433 		char action = banact_valtochar(tkl->ptr.spamfilter->action);
    434 
    435 		W_SAFE(unrealdb_write_str(db, match_type));
    436 		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->match->str));
    437 		W_SAFE(unrealdb_write_str(db, target));
    438 		W_SAFE(unrealdb_write_char(db, action));
    439 		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->tkl_reason));
    440 		W_SAFE(unrealdb_write_int64(db, tkl->ptr.spamfilter->tkl_duration));
    441 	}
    442 
    443 	return 1;
    444 }
    445 
    446 /** Read all entries from the TKL db */
    447 int read_tkldb(void)
    448 {
    449 	UnrealDB *db;
    450 	TKL *tkl = NULL;
    451 	uint32_t magic = 0;
    452 	uint32_t version;
    453 	uint64_t cnt;
    454 	uint64_t tklcount = 0;
    455 	uint64_t v;
    456 	int added_cnt = 0;
    457 	char c;
    458 	char *str;
    459 
    460 #ifdef BENCHMARK
    461 	struct timeval tv_alpha, tv_beta;
    462 
    463 	gettimeofday(&tv_alpha, NULL);
    464 #endif
    465 
    466 	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
    467 	if (!db)
    468 	{
    469 		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
    470 		{
    471 			/* Database does not exist. Could be first boot */
    472 			config_warn("[tkldb] No database present at '%s', will start a new one", cfg.database);
    473 			return 1;
    474 		} else
    475 		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
    476 		{
    477 			/* Re-open as unencrypted */
    478 			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
    479 			if (!db)
    480 			{
    481 				/* This should actually never happen, unless some weird I/O error */
    482 				config_warn("[tkldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
    483 				return 0;
    484 			}
    485 		} else
    486 		{
    487 			config_warn("[tkldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
    488 			return 0;
    489 		}
    490 	}
    491 
    492 	/* The database starts with a "magic value" - unless it's some old version or corrupt */
    493 	R_SAFE(unrealdb_read_int32(db, &magic));
    494 	if (magic != TKLDB_MAGIC)
    495 	{
    496 		config_warn("[tkldb] Database '%s' uses an old and unsupported format OR is corrupt", cfg.database);
    497 		config_status("If you are upgrading from UnrealIRCd 4 (or 5.0.0-alpha1) then we suggest you to "
    498 		              "delete the existing database. Just keep at least 1 server linked during the upgrade "
    499 		              "process to preserve your global *LINES and Spamfilters.");
    500 		unrealdb_close(db);
    501 		return 0;
    502 	}
    503 
    504 	/* Now do a version check */
    505 	R_SAFE(unrealdb_read_int32(db, &version));
    506 	if (version < 4999)
    507 	{
    508 		config_warn("[tkldb] Database '%s' uses an unsupport - possibly old - format (%ld).", cfg.database, (long)version);
    509 		unrealdb_close(db);
    510 		return 0;
    511 	}
    512 	if (version > tkldb_version)
    513 	{
    514 		config_warn("[tkldb] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
    515 			cfg.database, (unsigned long)tkldb_version, (unsigned long)version);
    516 		unrealdb_close(db);
    517 		return 0;
    518 	}
    519 
    520 	R_SAFE(unrealdb_read_int64(db, &tklcount));
    521 
    522 	for (cnt = 0; cnt < tklcount; cnt++)
    523 	{
    524 		int do_not_add = 0;
    525 
    526 		tkl = safe_alloc(sizeof(TKL));
    527 
    528 		/* First, fetch the TKL type.. */
    529 		R_SAFE(unrealdb_read_char(db, &c));
    530 		tkl->type = tkl_chartotype(c);
    531 		if (!tkl->type)
    532 		{
    533 			/* We can't continue reading the DB if we don't know the TKL type,
    534 			 * since we don't know how long the entry will be, we can't skip it.
    535 			 * This is "impossible" anyway, unless we some day remove a TKL type
    536 			 * in core UnrealIRCd. In which case we should add some skipping code
    537 			 * here to gracefully handle that situation ;)
    538 			 */
    539 			config_warn("[tkldb] Invalid type '%c' encountered - STOPPED READING DATABASE!", tkl->type);
    540 			FreeTKLRead();
    541 			break; /* we MUST stop reading */
    542 		}
    543 
    544 		/* Read the common types (same for all TKLs) */
    545 		R_SAFE(unrealdb_read_str(db, &tkl->set_by));
    546 		R_SAFE(unrealdb_read_int64(db, &v));
    547 		tkl->set_at = v;
    548 		R_SAFE(unrealdb_read_int64(db, &v));
    549 		tkl->expire_at = v;
    550 
    551 		/* Save some CPU... if it's already expired then don't bother adding */
    552 		if (tkl->expire_at != 0 && tkl->expire_at <= TStime())
    553 			do_not_add = 1;
    554 
    555 		/* Now handle all the specific types */
    556 		if (TKLIsServerBan(tkl))
    557 		{
    558 			int softban = 0;
    559 
    560 			tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
    561 
    562 			/* Usermask - but taking into account that the
    563 			 * %-prefix means a soft ban.
    564 			 */
    565 			R_SAFE(unrealdb_read_str(db, &str));
    566 			if (*str == '%')
    567 			{
    568 				softban = 1;
    569 				safe_strdup(tkl->ptr.serverban->usermask, str+1);
    570 			} else {
    571 				safe_strdup(tkl->ptr.serverban->usermask, str);
    572 			}
    573 			safe_free(str);
    574 
    575 			/* And the other 2 fields.. */
    576 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->hostmask));
    577 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->reason));
    578 
    579 			if (find_tkl_serverban(tkl->type, tkl->ptr.serverban->usermask,
    580 			                       tkl->ptr.serverban->hostmask, softban))
    581 			{
    582 				do_not_add = 1;
    583 			}
    584 
    585 			if (!do_not_add)
    586 			{
    587 				tkl_add_serverban(tkl->type, tkl->ptr.serverban->usermask,
    588 				                  tkl->ptr.serverban->hostmask,
    589 				                  tkl->ptr.serverban->reason,
    590 				                  tkl->set_by, tkl->expire_at,
    591 				                  tkl->set_at, softban, 0);
    592 			}
    593 		} else
    594 		if (TKLIsBanException(tkl))
    595 		{
    596 			int softban = 0;
    597 
    598 			tkl->ptr.banexception = safe_alloc(sizeof(BanException));
    599 
    600 			/* Usermask - but taking into account that the
    601 			 * %-prefix means a soft ban.
    602 			 */
    603 			R_SAFE(unrealdb_read_str(db, &str));
    604 			if (*str == '%')
    605 			{
    606 				softban = 1;
    607 				safe_strdup(tkl->ptr.banexception->usermask, str+1);
    608 			} else {
    609 				safe_strdup(tkl->ptr.banexception->usermask, str);
    610 			}
    611 			safe_free(str);
    612 
    613 			/* And the other 3 fields.. */
    614 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->hostmask));
    615 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->bantypes));
    616 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->reason));
    617 
    618 			if (find_tkl_banexception(tkl->type, tkl->ptr.banexception->usermask,
    619 			                          tkl->ptr.banexception->hostmask, softban))
    620 			{
    621 				do_not_add = 1;
    622 			}
    623 
    624 			if (!do_not_add)
    625 			{
    626 				tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask,
    627 				                     tkl->ptr.banexception->hostmask,
    628 				                     NULL,
    629 				                     tkl->ptr.banexception->reason,
    630 				                     tkl->set_by, tkl->expire_at,
    631 				                     tkl->set_at, softban,
    632 				                     tkl->ptr.banexception->bantypes,
    633 				                     0);
    634 			}
    635 		} else
    636 		if (TKLIsNameBan(tkl))
    637 		{
    638 			tkl->ptr.nameban = safe_alloc(sizeof(NameBan));
    639 
    640 			R_SAFE(unrealdb_read_str(db, &str));
    641 			if (*str == 'H')
    642 				tkl->ptr.nameban->hold = 1;
    643 			safe_free(str);
    644 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->name));
    645 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->reason));
    646 
    647 			if (find_tkl_nameban(tkl->type, tkl->ptr.nameban->name,
    648 			                     tkl->ptr.nameban->hold))
    649 			{
    650 				do_not_add = 1;
    651 			}
    652 
    653 			if (!do_not_add)
    654 			{
    655 				tkl_add_nameban(tkl->type, tkl->ptr.nameban->name,
    656 				                tkl->ptr.nameban->hold,
    657 				                tkl->ptr.nameban->reason,
    658 				                tkl->set_by, tkl->expire_at,
    659 				                tkl->set_at, 0);
    660 			}
    661 		} else
    662 		if (TKLIsSpamfilter(tkl))
    663 		{
    664 			int match_method;
    665 			char *err = NULL;
    666 
    667 			tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
    668 
    669 			/* Match method */
    670 			R_SAFE(unrealdb_read_str(db, &str));
    671 			match_method = unreal_match_method_strtoval(str);
    672 			if (!match_method)
    673 			{
    674 				config_warn("[tkldb] Unhandled spamfilter match method '%s' -- spamfilter entry not added", str);
    675 				do_not_add = 1;
    676 			}
    677 			safe_free(str);
    678 
    679 			/* Match string (eg: regex) */
    680 			R_SAFE(unrealdb_read_str(db, &str));
    681 			tkl->ptr.spamfilter->match = unreal_create_match(match_method, str, &err);
    682 			if (!tkl->ptr.spamfilter->match)
    683 			{
    684 				config_warn("[tkldb] Spamfilter '%s' does not compile: %s -- spamfilter entry not added", str, err);
    685 				do_not_add = 1;
    686 			}
    687 			safe_free(str);
    688 
    689 			/* Target (eg: cpn) */
    690 			R_SAFE(unrealdb_read_str(db, &str));
    691 			tkl->ptr.spamfilter->target = spamfilter_gettargets(str, NULL);
    692 			if (!tkl->ptr.spamfilter->target)
    693 			{
    694 				config_warn("[tkldb] Spamfilter '%s' without any valid targets (%s) -- spamfilter entry not added",
    695 					tkl->ptr.spamfilter->match->str, str);
    696 				do_not_add = 1;
    697 			}
    698 			safe_free(str);
    699 
    700 			/* Action */
    701 			R_SAFE(unrealdb_read_char(db, &c));
    702 			tkl->ptr.spamfilter->action = banact_chartoval(c);
    703 			if (!tkl->ptr.spamfilter->action)
    704 			{
    705 				config_warn("[tkldb] Spamfilter '%s' without valid action (%c) -- spamfilter entry not added",
    706 					tkl->ptr.spamfilter->match->str, c);
    707 				do_not_add = 1;
    708 			}
    709 
    710 			R_SAFE(unrealdb_read_str(db, &tkl->ptr.spamfilter->tkl_reason));
    711 			R_SAFE(unrealdb_read_int64(db, &v));
    712 			tkl->ptr.spamfilter->tkl_duration = v;
    713 
    714 			if (find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
    715 			                        tkl->ptr.spamfilter->action,
    716 			                        tkl->ptr.spamfilter->target))
    717 			{
    718 				do_not_add = 1;
    719 			}
    720 
    721 			if (!do_not_add)
    722 			{
    723 				tkl_add_spamfilter(tkl->type, tkl->ptr.spamfilter->target,
    724 				                   tkl->ptr.spamfilter->action,
    725 				                   tkl->ptr.spamfilter->match,
    726 				                   tkl->set_by, tkl->expire_at, tkl->set_at,
    727 				                   tkl->ptr.spamfilter->tkl_duration,
    728 				                   tkl->ptr.spamfilter->tkl_reason,
    729 				                   0);
    730 				/* tkl_add_spamfilter() does not copy the match but assign it.
    731 				 * so set to NULL here to avoid a read-after-free later on.
    732 				 */
    733 				tkl->ptr.spamfilter->match = NULL;
    734 			}
    735 		} else
    736 		{
    737 			config_warn("[tkldb] Unhandled type!! TKLDB is missing support for type %ld -- STOPPED reading db entries!", (long)tkl->type);
    738 			FreeTKLRead();
    739 			break; /* we MUST stop reading */
    740 		}
    741 
    742 		if (!do_not_add)
    743 			added_cnt++;
    744 
    745 		FreeTKLRead();
    746 	}
    747 
    748 	unrealdb_close(db);
    749 
    750 	if (added_cnt)
    751 		config_status("[tkldb] Re-added %d *-Lines", added_cnt);
    752 
    753 #ifdef BENCHMARK
    754 	gettimeofday(&tv_beta, NULL);
    755 	unreal_log(ULOG_DEBUG, "tkldb", "TKLDB_BENCHMARK", NULL,
    756 	           "[tkldb] Benchmark: LOAD DB: $time_msec microseconds",
    757 	           log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
    758 #endif
    759 	return 1;
    760 }
    761