unrealircd

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

unrealdb.c (36302B)

      1 /************************************************************************
      2  * src/unrealdb.c
      3  * Functions for dealing easily with (encrypted) database files.
      4  * (C) Copyright 2021 Bram Matthys (Syzop)
      5  *
      6  * This program is free software; you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation; either version 1, or (at your option)
      9  * any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program; if not, write to the Free Software
     18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     19  */
     20 
     21 #include "unrealircd.h"
     22 
     23 /** @file
     24  * @brief UnrealIRCd database API - see @ref UnrealDBFunctions
     25  */
     26 
     27 /**
     28  * Read and write to database files - encrypted and unencrypted.
     29  * This provides functions for dealing with (encrypted) database files.
     30  * - File format: https://www.unrealircd.org/docs/Dev:UnrealDB
     31  * - KDF: Argon2: https://en.wikipedia.org/wiki/Argon2
     32  * - Cipher: XChaCha20 from libsodium: https://libsodium.gitbook.io/doc/advanced/stream_ciphers/xchacha20
     33  * @defgroup UnrealDBFunctions Database functions
     34  */
     35 
     36 /* Benchmarking results:
     37  * On standard hardware as of 2021 speeds of 150-200 megabytes per second
     38  * are achieved realisticly for both reading and writing encrypted
     39  * database files. Of course, YMMV, depending on record sizes, CPU,
     40  * and I/O speeds of the underlying hardware.
     41  */
     42 
     43 /* In UnrealIRCd 5.2.x we didn't write the v1 header yet for unencrypted
     44  * database files, this so users using unencrypted could easily downgrade
     45  * to version 5.0.9 and older.
     46  * We DO support READING encypted, unencrypted v1, and unencrypted raw (v0)
     47  * in 5.2.0 onwards, though.
     48  * Starting with UnrealIRCd 6 we now write the header, so people can only
     49  * downgrade from UnrealIRCd 6 to 5.2.0 and later (not 5.0.9).
     50  */
     51 #define UNREALDB_WRITE_V1
     52 
     53 /* If a key is specified, it must be this size */
     54 #define UNREALDB_KEY_LEN	crypto_secretstream_xchacha20poly1305_KEYBYTES
     55 
     56 /** Default 'time cost' for Argon2id */
     57 #define UNREALDB_ARGON2_DEFAULT_TIME_COST             4
     58 /** Default 'memory cost' for Argon2id. Note that 15 means 1<<15=32M */
     59 #define UNREALDB_ARGON2_DEFAULT_MEMORY_COST           15
     60 /** Default 'parallelism cost' for Argon2id. */
     61 #define UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST      2
     62 
     63 #ifdef _WIN32
     64 /* Ignore this warning on Windows as it is a false positive */
     65 #pragma warning(disable : 6029)
     66 #endif
     67 
     68 /* Forward declarations - only used for internal (static) functions, of course */
     69 static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg);
     70 static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg);
     71 static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,3,4)));
     72 
     73 UnrealDBError unrealdb_last_error_code;
     74 static char *unrealdb_last_error_string = NULL;
     75 
     76 /** Set error condition on unrealdb 'c' (internal function).
     77  * @param c		The unrealdb file handle
     78  * @param pattern	The format string
     79  * @param ...		Any parameters to the format string
     80  * @note this will also set c->failed=1 to prevent any further reading/writing.
     81  */
     82 static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...)
     83 {
     84 	va_list vl;
     85 	char buf[512];
     86 	va_start(vl, pattern);
     87 	vsnprintf(buf, sizeof(buf), pattern, vl);
     88 	va_end(vl);
     89 	if (c)
     90 	{
     91 		c->error_code = errcode;
     92 		safe_strdup(c->error_string, buf);
     93 	}
     94 	unrealdb_last_error_code = errcode;
     95 	safe_strdup(unrealdb_last_error_string, buf);
     96 }
     97 
     98 /** Free a UnrealDB struct (internal function). */
     99 static void unrealdb_free(UnrealDB *c)
    100 {
    101 	unrealdb_free_config(c->config);
    102 	safe_free(c->error_string);
    103 	safe_free_sensitive(c);
    104 }
    105 
    106 static int unrealdb_kdf(UnrealDB *c, Secret *secr)
    107 {
    108 	if (c->config->kdf != UNREALDB_KDF_ARGON2ID)
    109 	{
    110 		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Unknown KDF 0x%x", (int)c->config->kdf);
    111 		return 0;
    112 	}
    113 	/* Need to run argon2 to generate key */
    114 	if (argon2id_hash_raw(c->config->t_cost,
    115 			      1 << c->config->m_cost,
    116 			      c->config->p_cost,
    117 			      secr->password, strlen(secr->password),
    118 			      c->config->salt, c->config->saltlen,
    119 			      c->config->key, c->config->keylen) != ARGON2_OK)
    120 	{
    121 		/* out of memory or some other very unusual error */
    122 		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Could not generate argon2 hash - out of memory or something weird?");
    123 		return 0;
    124 	}
    125 	return 1;
    126 }
    127 
    128 /**
    129  * @addtogroup UnrealDBFunctions
    130  * @{
    131  */
    132 
    133 /** Get the error string for last failed unrealdb operation.
    134  * @returns The error string
    135  * @note Use the return value only for displaying of errors
    136  *       to the end-user.
    137  *       For programmatically checking of error conditions
    138  *       use unrealdb_get_error_code() instead.
    139  */
    140 const char *unrealdb_get_error_string(void)
    141 {
    142 	return unrealdb_last_error_string;
    143 }
    144 
    145 /** Get the error code for last failed unrealdb operation
    146  * @returns An UNREAL_DB_ERROR_*
    147  */
    148 UnrealDBError unrealdb_get_error_code(void)
    149 {
    150 	return unrealdb_last_error_code;
    151 }
    152 
    153 /** Open an unrealdb file.
    154  * @param filename	The filename to open
    155  * @param mode		Either UNREALDB_MODE_READ or UNREALDB_MODE_WRITE
    156  * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
    157  * @returns A pointer to a UnrealDB structure that can be used in subsequent calls for db read/writes,
    158  *          and finally unrealdb_close(). Or NULL in case of failure.
    159  * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
    160  *       unrealdb_get_error_string() to see the actual error.
    161  */
    162 UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block)
    163 {
    164 	UnrealDB *c = safe_alloc_sensitive(sizeof(UnrealDB));
    165 	char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
    166 	char buf[32]; /* don't change this */
    167 	Secret *secr=NULL;
    168 	SecretCache *dbcache;
    169 	int cached = 0;
    170 	char *err;
    171 
    172 	errno = 0;
    173 
    174 	if ((mode != UNREALDB_MODE_READ) && (mode != UNREALDB_MODE_WRITE))
    175 	{
    176 		unrealdb_set_error(c, UNREALDB_ERROR_API, "unrealdb_open request for neither read nor write");
    177 		goto unrealdb_open_fail;
    178 	}
    179 
    180 	/* Do this check early, before we try to create any file */
    181 	if (secret_block != NULL)
    182 	{
    183 		secr = find_secret(secret_block);
    184 		if (!secr)
    185 		{
    186 			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Secret block '%s' not found or invalid", secret_block);
    187 			goto unrealdb_open_fail;
    188 		}
    189 
    190 		if (!valid_secret_password(secr->password, &err))
    191 		{
    192 			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Password in secret block '%s' does not meet complexity requirements", secr->name);
    193 			goto unrealdb_open_fail;
    194 		}
    195 	}
    196 
    197 	c->mode = mode;
    198 	c->fd = fopen(filename, (c->mode == UNREALDB_MODE_WRITE) ? "wb" : "rb");
    199 	if (!c->fd)
    200 	{
    201 		if (errno == ENOENT)
    202 			unrealdb_set_error(c, UNREALDB_ERROR_FILENOTFOUND, "File not found: %s", strerror(errno));
    203 		else
    204 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Could not open file: %s", strerror(errno));
    205 		goto unrealdb_open_fail;
    206 	}
    207 
    208 	if (secret_block == NULL)
    209 	{
    210 		if (mode == UNREALDB_MODE_READ)
    211 		{
    212 			/* READ: read header, if any, lots of fallback options here... */
    213 			if (fgets(buf, sizeof(buf), c->fd))
    214 			{
    215 				if (!strncmp(buf, "UnrealIRCd-DB-Crypted", 21))
    216 				{
    217 					unrealdb_set_error(c, UNREALDB_ERROR_CRYPTED, "file is encrypted but no password provided");
    218 					goto unrealdb_open_fail;
    219 				} else
    220 				if (!strcmp(buf, "UnrealIRCd-DB-v1"))
    221 				{
    222 					/* Skip over the 32 byte header, directly to the creationtime */
    223 					if (fseek(c->fd, 32L, SEEK_SET) < 0)
    224 					{
    225 						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "file header too short");
    226 						goto unrealdb_open_fail;
    227 					}
    228 					if (!unrealdb_read_int64(c, &c->creationtime))
    229 					{
    230 						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (A4)");
    231 						goto unrealdb_open_fail;
    232 					}
    233 					/* SUCCESS = fallthrough */
    234 				} else
    235 				if (!strncmp(buf, "UnrealIRCd-DB", 13)) /* any other version than v1 = not supported by us */
    236 				{
    237 					/* We don't support this format, so refuse clearly */
    238 					unrealdb_set_error(c, UNREALDB_ERROR_HEADER,
    239 							   "Unsupported version of database. Is this database perhaps created on "
    240 							   "a new version of UnrealIRCd and are you trying to use it on an older "
    241 							   "UnrealIRCd version? (Downgrading is not supported!)");
    242 					goto unrealdb_open_fail;
    243 				} else
    244 				{
    245 					/* Old db format, no header, seek back to beginning */
    246 					fseek(c->fd, 0L, SEEK_SET);
    247 					/* SUCCESS = fallthrough */
    248 				}
    249 			}
    250 		} else {
    251 #ifdef UNREALDB_WRITE_V1
    252 			/* WRITE */
    253 			memset(buf, 0, sizeof(buf));
    254 			snprintf(buf, sizeof(buf), "UnrealIRCd-DB-v1");
    255 			if ((fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf)) ||
    256 			    !unrealdb_write_int64(c, TStime()))
    257 			{
    258 				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (A1)");
    259 				goto unrealdb_open_fail;
    260 			}
    261 #endif
    262 		}
    263 		safe_free(unrealdb_last_error_string);
    264 		unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
    265 		return c;
    266 	}
    267 
    268 	c->crypted = 1;
    269 
    270 	if (c->mode == UNREALDB_MODE_WRITE)
    271 	{
    272 		/* Write the:
    273 		 * - generic header ("UnrealIRCd-DB" + some zeroes)
    274 		 * - the salt
    275 		 * - the crypto header
    276 		 */
    277 		memset(buf, 0, sizeof(buf));
    278 		snprintf(buf, sizeof(buf), "UnrealIRCd-DB-Crypted-v1");
    279 		if (fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
    280 		{
    281 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (1)");
    282 			goto unrealdb_open_fail; /* Unable to write header nr 1 */
    283 		}
    284 
    285 		if (secr->cache && secr->cache->config)
    286 		{
    287 			/* Use first found cached config for this secret */
    288 			c->config = unrealdb_copy_config(secr->cache->config);
    289 			cached = 1;
    290 		} else {
    291 			/* Create a new config */
    292 			c->config = safe_alloc(sizeof(UnrealDBConfig));
    293 			c->config->kdf = UNREALDB_KDF_ARGON2ID;
    294 			c->config->t_cost = UNREALDB_ARGON2_DEFAULT_TIME_COST;
    295 			c->config->m_cost = UNREALDB_ARGON2_DEFAULT_MEMORY_COST;
    296 			c->config->p_cost = UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST;
    297 			c->config->saltlen = UNREALDB_SALT_LEN;
    298 			c->config->salt = safe_alloc(c->config->saltlen);
    299 			randombytes_buf(c->config->salt, c->config->saltlen);
    300 			c->config->cipher = UNREALDB_CIPHER_XCHACHA20;
    301 			c->config->keylen = UNREALDB_KEY_LEN;
    302 			c->config->key = safe_alloc_sensitive(c->config->keylen);
    303 		}
    304 
    305 		if (c->config->kdf == 0)
    306 			abort();
    307 
    308 		/* Write KDF and cipher parameters */
    309 		if ((fwrite(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
    310 		    (fwrite(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
    311 		    (fwrite(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
    312 		    (fwrite(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
    313 		    (fwrite(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)) ||
    314 		    (fwrite(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen) ||
    315 		    (fwrite(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
    316 		    (fwrite(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
    317 		{
    318 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (2)");
    319 			goto unrealdb_open_fail;
    320 		}
    321 		
    322 		if (cached)
    323 		{
    324 #ifdef DEBUGMODE
    325 			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_HIT", NULL,
    326 			           "Cache hit for '$secret_block' while writing",
    327 			           log_data_string("secret_block", secr->name));
    328 #endif
    329 		} else
    330 		{
    331 #ifdef DEBUGMODE
    332 			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_MISS", NULL,
    333 			           "Cache miss for '$secret_block' while writing, need to run argon2",
    334 			           log_data_string("secret_block", secr->name));
    335 #endif
    336 			if (!unrealdb_kdf(c, secr))
    337 			{
    338 				/* Error already set by called function */
    339 				goto unrealdb_open_fail;
    340 			}
    341 		}
    342 
    343 		crypto_secretstream_xchacha20poly1305_init_push(&c->st, header, c->config->key);
    344 		if (fwrite(header, 1, sizeof(header), c->fd) != sizeof(header))
    345 		{
    346 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (3)");
    347 			goto unrealdb_open_fail; /* Unable to write crypto header */
    348 		}
    349 		if (!unrealdb_write_str(c, "UnrealIRCd-DB-Crypted-Now") ||
    350 		    !unrealdb_write_int64(c, TStime()))
    351 		{
    352 			/* error is already set by unrealdb_write_str() */
    353 			goto unrealdb_open_fail; /* Unable to write crypto header */
    354 		}
    355 		if (!cached)
    356 			unrealdb_add_to_secret_cache(secr, c->config);
    357 	} else
    358 	{
    359 		char *validate = NULL;
    360 		
    361 		/* Read file header */
    362 		if (fread(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
    363 		{
    364 			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file (file too small)");
    365 			goto unrealdb_open_fail; /* Header too short */
    366 		}
    367 		if (strncmp(buf, "UnrealIRCd-DB-Crypted-v1", 24))
    368 		{
    369 			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file");
    370 			goto unrealdb_open_fail; /* Invalid header */
    371 		}
    372 		c->config = safe_alloc(sizeof(UnrealDBConfig));
    373 		if ((fread(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
    374 		    (fread(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
    375 		    (fread(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
    376 		    (fread(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
    377 		    (fread(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)))
    378 		{
    379 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid");
    380 			goto unrealdb_open_fail;
    381 		}
    382 		if (c->config->kdf != UNREALDB_KDF_ARGON2ID) 
    383 		{
    384 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown KDF 0x%x", (int)c->config->kdf);
    385 			goto unrealdb_open_fail;
    386 		}
    387 		if (c->config->saltlen > 1024)
    388 		{
    389 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (saltlen=%d)", (int)c->config->saltlen);
    390 			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
    391 		}
    392 		c->config->salt = safe_alloc(c->config->saltlen);
    393 		if (fread(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen)
    394 		{
    395 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (2)");
    396 			goto unrealdb_open_fail; /* Header too short (read II) */
    397 		}
    398 		if ((fread(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
    399 		    (fread(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
    400 		{
    401 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid (3)");
    402 			goto unrealdb_open_fail;
    403 		}
    404 		if (c->config->cipher != UNREALDB_CIPHER_XCHACHA20)
    405 		{
    406 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown cipher 0x%x", (int)c->config->cipher);
    407 			goto unrealdb_open_fail;
    408 		}
    409 		if (c->config->keylen > 1024)
    410 		{
    411 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (keylen=%d)", (int)c->config->keylen);
    412 			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
    413 		}
    414 		c->config->key = safe_alloc_sensitive(c->config->keylen);
    415 
    416 		dbcache = find_secret_cache(secr, c->config);
    417 		if (dbcache)
    418 		{
    419 			/* Use cached key, no need to run expensive argon2.. */
    420 			memcpy(c->config->key, dbcache->config->key, c->config->keylen);
    421 #ifdef DEBUGMODE
    422 			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_HIT", NULL,
    423 			           "Cache hit for '$secret_block' while reading",
    424 			           log_data_string("secret_block", secr->name));
    425 #endif
    426 		} else {
    427 #ifdef DEBUGMODE
    428 			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_MISS", NULL,
    429 			           "Cache miss for '$secret_block' while reading, need to run argon2",
    430 			           log_data_string("secret_block", secr->name));
    431 #endif
    432 			if (!unrealdb_kdf(c, secr))
    433 			{
    434 				/* Error already set by called function */
    435 				goto unrealdb_open_fail;
    436 			}
    437 		}
    438 		/* key is now set */
    439 		if (fread(header, 1, sizeof(header), c->fd) != sizeof(header))
    440 		{
    441 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (3)");
    442 			goto unrealdb_open_fail; /* Header too short */
    443 		}
    444 		if (crypto_secretstream_xchacha20poly1305_init_pull(&c->st, header, c->config->key) != 0)
    445 		{
    446 			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Crypto error - invalid password or corrupt file");
    447 			goto unrealdb_open_fail; /* Unusual */
    448 		}
    449 		/* Now to validate the key we read a simple string */
    450 		if (!unrealdb_read_str(c, &validate))
    451 		{
    452 			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
    453 			goto unrealdb_open_fail; /* Incorrect key, probably */
    454 		}
    455 		if (strcmp(validate, "UnrealIRCd-DB-Crypted-Now"))
    456 		{
    457 			safe_free(validate);
    458 			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
    459 			goto unrealdb_open_fail; /* Incorrect key, probably */
    460 		}
    461 		safe_free(validate);
    462 		if (!unrealdb_read_int64(c, &c->creationtime))
    463 		{
    464 			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (4)");
    465 			goto unrealdb_open_fail;
    466 		}
    467 		unrealdb_add_to_secret_cache(secr, c->config);
    468 	}
    469 	sodium_stackzero(1024);
    470 	safe_free(unrealdb_last_error_string);
    471 	unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
    472 	return c;
    473 
    474 unrealdb_open_fail:
    475 	if (c->fd)
    476 		fclose(c->fd);
    477 	unrealdb_free(c);
    478 	sodium_stackzero(1024);
    479 	return NULL;
    480 }
    481 
    482 /** Close an unrealdb file.
    483  * @param c	The struct pointing to an unrealdb file
    484  * @returns 1 if the final close was graceful and 0 if not (eg: out of disk space on final flush).
    485  *          In all cases the file handle is closed and 'c' is freed.
    486  * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
    487  *       unrealdb_get_error_string() to see the actual error.
    488  */
    489 int unrealdb_close(UnrealDB *c)
    490 {
    491 	/* If this is file was opened for writing then flush the remaining data with a TAG_FINAL
    492 	 * (or push a block of 0 bytes with TAG_FINAL)
    493 	 */
    494 	if (c->crypted && (c->mode == UNREALDB_MODE_WRITE))
    495 	{
    496 		char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    497 		unsigned long long out_len = sizeof(buf_out);
    498 
    499 		crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, c->buflen, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL);
    500 		if (out_len > 0)
    501 		{
    502 			if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
    503 			{
    504 				/* Final write failed, error condition */
    505 				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
    506 				fclose(c->fd);
    507 				unrealdb_free(c);
    508 				return 0;
    509 			}
    510 		}
    511 	}
    512 
    513 	if (fclose(c->fd) != 0)
    514 	{
    515 		/* Final close failed, error condition */
    516 		unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
    517 		unrealdb_free(c);
    518 		return 0;
    519 	}
    520 
    521 	unrealdb_free(c);
    522 	return 1;
    523 }
    524 
    525 /** Test if there is something fatally wrong with the configuration of the DB file,
    526  * in which case we suggest to reject the /rehash or boot request.
    527  * This tests for "wrong password" and for "trying to open an encrypted file without providing a password"
    528  * which are clear configuration errors on the admin part.
    529  * It does NOT test for any other conditions such as missing file, corrupted file, etc.
    530  * since that usually needs different handling anyway, as they are I/O issues and don't
    531  * always have a clear solution (if any is needed at all).
    532  * @param filename	The filename to open
    533  * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
    534  * @returns 1 if the password was wrong, 0 for any other error or succes.
    535  */
    536 char *unrealdb_test_db(const char *filename, char *secret_block)
    537 {
    538 	static char buf[512];
    539 	UnrealDB *db = unrealdb_open(filename, UNREALDB_MODE_READ, secret_block);
    540 	if (!db)
    541 	{
    542 		if (unrealdb_get_error_code() == UNREALDB_ERROR_PASSWORD)
    543 		{
    544 			snprintf(buf, sizeof(buf), "Incorrect password specified in secret block '%s' for file %s",
    545 				secret_block, filename);
    546 			return buf;
    547 		}
    548 		if (unrealdb_get_error_code() == UNREALDB_ERROR_CRYPTED)
    549 		{
    550 			snprintf(buf, sizeof(buf), "File '%s' is encrypted but no secret block provided for it",
    551 				filename);
    552 			return buf;
    553 		}
    554 		return NULL;
    555 	} else
    556 	{
    557 		unrealdb_close(db);
    558 	}
    559 	return NULL;
    560 }
    561 
    562 /** @} */
    563 
    564 /** Write to an unrealdb file.
    565  * This code uses extra buffering to avoid writing small records
    566  * and wasting for example a 32 bytes encryption block for a 8 byte write request.
    567  * @param c		Database file open for writing
    568  * @param wbuf		The data to be written (plaintext)
    569  * @param len		The length of the data to be written
    570  * @note This is the internal function, api users must use one of the
    571  *       following functions instead:
    572  *       unrealdb_write_int64(), unrealdb_write_int32(), unrealdb_write_int16(),
    573  *       unrealdb_write_char(), unrealdb_write_str().
    574  */
    575 static int unrealdb_write(UnrealDB *c, const void *wbuf, int len)
    576 {
    577 	char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    578 	unsigned long long out_len;
    579 	const char *buf = wbuf;
    580 
    581 	if (c->error_code)
    582 		return 0;
    583 
    584 	if (c->mode != UNREALDB_MODE_WRITE)
    585 	{
    586 		unrealdb_set_error(c, UNREALDB_ERROR_API, "Write operation requested on a file opened for reading");
    587 		return 0;
    588 	}
    589 
    590 	if (!c->crypted)
    591 	{
    592 		if (fwrite(buf, 1, len, c->fd) != len)
    593 		{
    594 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
    595 			return 0;
    596 		}
    597 		return 1;
    598 	}
    599 
    600 	do {
    601 		if (c->buflen + len < UNREALDB_CRYPT_FILE_CHUNK_SIZE)
    602 		{
    603 			/* New data fits in new buffer. Then we are done with writing.
    604 			 * This can happen both for the first block (never write)
    605 			 * or the remainder (tail after X writes which is less than
    606 			 * UNREALDB_CRYPT_FILE_CHUNK_SIZE, a common case)
    607 			 */
    608 			memcpy(c->buf + c->buflen, buf, len);
    609 			c->buflen += len;
    610 			break; /* Done! */
    611 		} else
    612 		{
    613 			/* Fill up c->buf with UNREALDB_CRYPT_FILE_CHUNK_SIZE
    614 			 * Note that 'av_bytes' can be 0 here if c->buflen
    615 			 * happens to be exactly UNREALDB_CRYPT_FILE_CHUNK_SIZE,
    616 			 * that's okay.
    617 			 */
    618 			int av_bytes = UNREALDB_CRYPT_FILE_CHUNK_SIZE - c->buflen;
    619 			if (av_bytes > 0)
    620 				memcpy(c->buf + c->buflen, buf, av_bytes);
    621 			buf += av_bytes;
    622 			len -= av_bytes;
    623 		}
    624 		if (crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, UNREALDB_CRYPT_FILE_CHUNK_SIZE, NULL, 0, 0) != 0)
    625 		{
    626 			unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Failed to encrypt a block");
    627 			return 0;
    628 		}
    629 		if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
    630 		{
    631 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
    632 			return 0;
    633 		}
    634 		/* Buffer is now flushed for sure */
    635 		c->buflen = 0;
    636 	} while(len > 0);
    637 
    638 	return 1;
    639 }
    640 
    641 /**
    642  * @addtogroup UnrealDBFunctions
    643  * @{
    644  */
    645 
    646 /** Write a string to a database file.
    647  * @param c	UnrealDB file struct
    648  * @param x	String to be written
    649  * @note  This function can write a string up to 65534
    650  *        characters, which should be plenty for usage
    651  *        in UnrealIRCd.
    652  *        Note that 'x' can safely be NULL.
    653  * @returns 1 on success, 0 on failure.
    654  */
    655 int unrealdb_write_str(UnrealDB *c, const char *x)
    656 {
    657 	uint16_t len;
    658 
    659 	/* First, make sure the string is not too large (would be very unusual, though) */
    660 	if (x)
    661 	{
    662 		int stringlen = strlen(x);
    663 		if (stringlen >= 0xffff)
    664 		{
    665 			unrealdb_set_error(c, UNREALDB_ERROR_API,
    666 					   "unrealdb_write_str(): string has length %d, while maximum allowed is 65534",
    667 					   stringlen);
    668 			return 0;
    669 		}
    670 		len = stringlen;
    671 	} else {
    672 		len = 0xffff;
    673 	}
    674 
    675 	/* Write length to db as 16 bit integer */
    676 	if (!unrealdb_write_int16(c, len))
    677 		return 0;
    678 
    679 	/* Then, write the actual string (if any), without NUL terminator. */
    680 	if ((len > 0) && (len < 0xffff))
    681 	{
    682 		if (!unrealdb_write(c, x, len))
    683 			return 0;
    684 	}
    685 
    686 	return 1;
    687 }
    688 
    689 /** Write a 64 bit integer to a database file.
    690  * @param c	UnrealDB file struct
    691  * @param t	The value to write
    692  * @returns 1 on success, 0 on failure.
    693  */
    694 int unrealdb_write_int64(UnrealDB *c, uint64_t t)
    695 {
    696 #ifdef NATIVE_BIG_ENDIAN
    697 	t = bswap_64(t);
    698 #endif
    699 	return unrealdb_write(c, &t, sizeof(t));
    700 }
    701 
    702 /** Write a 32 bit integer to a database file.
    703  * @param c	UnrealDB file struct
    704  * @param t	The value to write
    705  * @returns 1 on success, 0 on failure.
    706  */
    707 int unrealdb_write_int32(UnrealDB *c, uint32_t t)
    708 {
    709 #ifdef NATIVE_BIG_ENDIAN
    710 	t = bswap_32(t);
    711 #endif
    712 	return unrealdb_write(c, &t, sizeof(t));
    713 }
    714 
    715 /** Write a 16 bit integer to a database file.
    716  * @param c	UnrealDB file struct
    717  * @param t	The value to write
    718  * @returns 1 on success, 0 on failure.
    719  */
    720 int unrealdb_write_int16(UnrealDB *c, uint16_t t)
    721 {
    722 #ifdef NATIVE_BIG_ENDIAN
    723 	t = bswap_16(t);
    724 #endif
    725 	return unrealdb_write(c, &t, sizeof(t));
    726 }
    727 
    728 /** Write a single 8 bit character to a database file.
    729  * @param c	UnrealDB file struct
    730  * @param t	The value to write
    731  * @returns 1 on success, 0 on failure.
    732  */
    733 int unrealdb_write_char(UnrealDB *c, char t)
    734 {
    735 	return unrealdb_write(c, &t, sizeof(t));
    736 }
    737 
    738 /** @} */
    739 
    740 /** Read from an UnrealDB file.
    741  * This code deals with buffering, block reading, etc. so the caller doesn't
    742  * have to worry about that.
    743  * @param c		Database file open for reading
    744  * @param rbuf		The data to be read (will be plaintext)
    745  * @param len		The length of the data to be read
    746  * @note This is the internal function, api users must use one of the
    747  *       following functions instead:
    748  *       unrealdb_read_int64(), unrealdb_read_int32(), unrealdb_read_int16(),
    749  *       unrealdb_read_char(), unrealdb_read_str().
    750  */
    751 static int unrealdb_read(UnrealDB *c, void *rbuf, int len)
    752 {
    753 	char buf_in[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    754 	unsigned long long out_len;
    755 	unsigned char tag;
    756 	size_t rlen;
    757 	char *buf = rbuf;
    758 
    759 	if (c->error_code)
    760 		return 0;
    761 
    762 	if (c->mode != UNREALDB_MODE_READ)
    763 	{
    764 		unrealdb_set_error(c, UNREALDB_ERROR_API, "Read operation requested on a file opened for writing");
    765 		return 0;
    766 	}
    767 
    768 	if (!c->crypted)
    769 	{
    770 		rlen = fread(buf, 1, len, c->fd);
    771 		if (rlen < len)
    772 		{
    773 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file (want:%d, got:%d bytes)",
    774 				len, (int)rlen);
    775 			return 0;
    776 		}
    777 		return 1;
    778 	}
    779 
    780 	/* First, fill 'buf' up with what we have */
    781 	if (c->buflen)
    782 	{
    783 		int av_bytes = MIN(c->buflen, len);
    784 		memcpy(buf, c->buf, av_bytes);
    785 		if (c->buflen - av_bytes > 0)
    786 			memmove(c->buf, c->buf + av_bytes, c->buflen - av_bytes);
    787 		c->buflen -= av_bytes;
    788 		len -= av_bytes;
    789 		if (len == 0)
    790 			return 1; /* Request completed entirely */
    791 		buf += av_bytes;
    792 	}
    793 
    794 	if (c->buflen != 0)
    795 		abort();
    796 
    797 	/* If we get here then we need to read some data */
    798 	do {
    799 		rlen = fread(buf_in, 1, sizeof(buf_in), c->fd);
    800 		if (rlen == 0)
    801 		{
    802 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file??");
    803 			return 0;
    804 		}
    805 		if (crypto_secretstream_xchacha20poly1305_pull(&c->st, c->buf, &out_len, &tag, buf_in, rlen, NULL, 0) != 0)
    806 		{
    807 			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Failed to decrypt a block - either corrupt or wrong key");
    808 			return 0;
    809 		}
    810 
    811 		/* This should be impossible as this is guaranteed not to happen by libsodium */
    812 		if (out_len > UNREALDB_CRYPT_FILE_CHUNK_SIZE)
    813 			abort();
    814 
    815 		if (len > out_len)
    816 		{
    817 			/* We eat a big block, but want more in next iteration of the loop */
    818 			memcpy(buf, c->buf, out_len);
    819 			buf += out_len;
    820 			len -= out_len;
    821 		} else {
    822 			/* This is the only (or last) block we need, we are satisfied */
    823 			memcpy(buf, c->buf, len);
    824 			c->buflen = out_len - len;
    825 			if (c->buflen > 0)
    826 				memmove(c->buf, c->buf+len, c->buflen);
    827 			return 1; /* Done */
    828 		}
    829 	} while(!feof(c->fd));
    830 
    831 	unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file?");
    832 	return 0;
    833 }
    834 
    835 /**
    836  * @addtogroup UnrealDBFunctions
    837  * @{
    838  */
    839 
    840 /** Read a 64 bit integer from a database file.
    841  * @param c	UnrealDB file struct
    842  * @param t	The value to read
    843  * @returns 1 on success, 0 on failure.
    844  */
    845 int unrealdb_read_int64(UnrealDB *c, uint64_t *t)
    846 {
    847 	if (!unrealdb_read(c, t, sizeof(uint64_t)))
    848 		return 0;
    849 #ifdef NATIVE_BIG_ENDIAN
    850 	*t = bswap_64(*t);
    851 #endif
    852 	return 1;
    853 }
    854 
    855 /** Read a 32 bit integer from a database file.
    856  * @param c	UnrealDB file struct
    857  * @param t	The value to read
    858  * @returns 1 on success, 0 on failure.
    859  */
    860 int unrealdb_read_int32(UnrealDB *c, uint32_t *t)
    861 {
    862 	if (!unrealdb_read(c, t, sizeof(uint32_t)))
    863 		return 0;
    864 #ifdef NATIVE_BIG_ENDIAN
    865 	*t = bswap_32(*t);
    866 #endif
    867 	return 1;
    868 }
    869 
    870 /** Read a 16 bit integer from a database file.
    871  * @param c	UnrealDB file struct
    872  * @param t	The value to read
    873  * @returns 1 on success, 0 on failure.
    874  */
    875 int unrealdb_read_int16(UnrealDB *c, uint16_t *t)
    876 {
    877 	if (!unrealdb_read(c, t, sizeof(uint16_t)))
    878 		return 0;
    879 #ifdef NATIVE_BIG_ENDIAN
    880 	*t = bswap_16(*t);
    881 #endif
    882 	return 1;
    883 }
    884 
    885 /** Read a string from a database file.
    886  * @param c    UnrealDB file struct
    887  * @param x    Pointer to string pointer
    888  * @note  This function will allocate memory for the data
    889  *        and set the string pointer to this value.
    890  *        If a NULL pointer was written via write_str()
    891  *        then read_str() may also return a NULL pointer.
    892  * @returns 1 on success, 0 on failure.
    893  */
    894 int unrealdb_read_str(UnrealDB *c, char **x)
    895 {
    896 	uint16_t len;
    897 	size_t size;
    898 
    899 	*x = NULL;
    900 
    901 	if (!unrealdb_read_int16(c, &len))
    902 		return 0;
    903 
    904 	if (len == 0xffff)
    905 	{
    906 		/* Magic value meaning NULL */
    907 		*x = NULL;
    908 		return 1;
    909 	}
    910 
    911 	if (len == 0)
    912 	{
    913 		/* 0 means empty string */
    914 		safe_strdup(*x, "");
    915 		return 1;
    916 	}
    917 
    918 	if (len > 10000)
    919 		return 0;
    920 
    921 	size = len;
    922 	*x = safe_alloc(size + 1);
    923 	if (!unrealdb_read(c, *x, size))
    924 	{
    925 		safe_free(*x);
    926 		return 0;
    927 	}
    928 	(*x)[len] = 0;
    929 	return 1;
    930 }
    931 
    932 /** Read a single 8 bit character from a database file.
    933  * @param c	UnrealDB file struct
    934  * @param t	The value to read
    935  * @returns 1 on success, 0 on failure.
    936  */
    937 int unrealdb_read_char(UnrealDB *c, char *t)
    938 {
    939 	if (!unrealdb_read(c, t, sizeof(char)))
    940 		return 0;
    941 	return 1;
    942 }
    943 
    944 /** @} */
    945 
    946 #if 0
    947 void fatal_error(FORMAT_STRING(const char *pattern), ...)
    948 {
    949 	va_list vl;
    950 	va_start(vl, pattern);
    951 	vfprintf(stderr, pattern, vl);
    952 	va_end(vl);
    953 	fprintf(stderr, "\n");
    954 	fprintf(stderr, "Exiting with failure\n");
    955 	exit(-1);
    956 }
    957 
    958 void unrealdb_test_simple(void)
    959 {
    960 	UnrealDB *c;
    961 	char *key = "test";
    962 	int i;
    963 	char *str;
    964 
    965 
    966 	fprintf(stderr, "*** WRITE TEST ***\n");
    967 	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
    968 	if (!c)
    969 		fatal_error("Could not open test db for writing: %s", strerror(errno));
    970 
    971 	if (!unrealdb_write_str(c, "Hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
    972 		fatal_error("Error on write 1");
    973 	if (!unrealdb_write_str(c, "This is a test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
    974 		fatal_error("Error on write 2");
    975 	if (!unrealdb_close(c))
    976 		fatal_error("Error on close");
    977 	c = NULL;
    978 	fprintf(stderr, "Done with writing.\n\n");
    979 
    980 	fprintf(stderr, "*** READ TEST ***\n");
    981 	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
    982 	if (!c)
    983 		fatal_error("Could not open test db for reading: %s", strerror(errno));
    984 	if (!unrealdb_read_str(c, &str))
    985 		fatal_error("Error on read 1: %s", c->error_string);
    986 	fprintf(stderr, "Got: '%s'\n", str);
    987 	safe_free(str);
    988 	if (!unrealdb_read_str(c, &str))
    989 		fatal_error("Error on read 2: %s", c->error_string);
    990 	fprintf(stderr, "Got: '%s'\n", str);
    991 	safe_free(str);
    992 	if (!unrealdb_close(c))
    993 		fatal_error("Error on close");
    994 	fprintf(stderr, "All good.\n");
    995 }
    996 
    997 #define UNREALDB_SPEED_TEST_BYTES 100000000
    998 void unrealdb_test_speed(char *key)
    999 {
   1000 	UnrealDB *c;
   1001 	int i, len;
   1002 	char *str;
   1003 	char buf[1024];
   1004 	int written = 0, read = 0;
   1005 	struct timeval tv_start, tv_end;
   1006 
   1007 	fprintf(stderr, "*** WRITE TEST ***\n");
   1008 	gettimeofday(&tv_start, NULL);
   1009 	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
   1010 	if (!c)
   1011 		fatal_error("Could not open test db for writing: %s", strerror(errno));
   1012 	do {
   1013 		
   1014 		len = getrandom32() % 500;
   1015 		//gen_random_alnum(buf, len);
   1016 		for (i=0; i < len; i++)
   1017 			buf[i] = 'a';
   1018 		buf[i] = '\0';
   1019 		if (!unrealdb_write_str(c, buf))
   1020 			fatal_error("Error on writing a string of %d size", len);
   1021 		written += len + 2; /* +2 for length */
   1022 	} while(written < UNREALDB_SPEED_TEST_BYTES);
   1023 	if (!unrealdb_close(c))
   1024 		fatal_error("Error on close");
   1025 	c = NULL;
   1026 	gettimeofday(&tv_end, NULL);
   1027 	fprintf(stderr, "Done with writing: %lld usecs\n\n",
   1028 		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
   1029 
   1030 	fprintf(stderr, "*** READ TEST ***\n");
   1031 	gettimeofday(&tv_start, NULL);
   1032 	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
   1033 	if (!c)
   1034 		fatal_error("Could not open test db for reading: %s", strerror(errno));
   1035 	do {
   1036 		if (!unrealdb_read_str(c, &str))
   1037 			fatal_error("Error on read at position %d/%d: %s", read, written, c->error_string);
   1038 		read += strlen(str) + 2; /* same calculation as earlier */
   1039 		safe_free(str);
   1040 	} while(read < written);
   1041 	if (!unrealdb_close(c))
   1042 		fatal_error("Error on close");
   1043 	gettimeofday(&tv_end, NULL);
   1044 	fprintf(stderr, "Done with reading: %lld usecs\n\n",
   1045 		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
   1046 
   1047 	fprintf(stderr, "All good.\n");
   1048 }
   1049 
   1050 void unrealdb_test(void)
   1051 {
   1052 	//unrealdb_test_simple();
   1053 	fprintf(stderr, "**** TESTING ENCRYPTED ****\n");
   1054 	unrealdb_test_speed("test");
   1055 	fprintf(stderr, "**** TESTING UNENCRYPTED ****\n");
   1056 	unrealdb_test_speed(NULL);
   1057 }
   1058 #endif
   1059 
   1060 /** TODO: document and implement
   1061  */
   1062 const char *unrealdb_test_secret(const char *name)
   1063 {
   1064 	// FIXME: check if exists, if not then return an error, with a nice FAQ reference etc.
   1065 	return NULL; /* no error */
   1066 }
   1067 
   1068 UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src)
   1069 {
   1070 	UnrealDBConfig *dst = safe_alloc(sizeof(UnrealDBConfig));
   1071 
   1072 	dst->kdf = src->kdf;
   1073 	dst->t_cost = src->t_cost;
   1074 	dst->m_cost = src->m_cost;
   1075 	dst->p_cost = src->p_cost;
   1076 	dst->saltlen = src->saltlen;
   1077 	dst->salt = safe_alloc(dst->saltlen);
   1078 	memcpy(dst->salt, src->salt, dst->saltlen);
   1079 
   1080 	dst->cipher = src->cipher;
   1081 	dst->keylen = src->keylen;
   1082 	if (dst->keylen)
   1083 	{
   1084 		dst->key = safe_alloc_sensitive(dst->keylen);
   1085 		memcpy(dst->key, src->key, dst->keylen);
   1086 	}
   1087 
   1088 	return dst;
   1089 }
   1090 
   1091 UnrealDBConfig *unrealdb_get_config(UnrealDB *db)
   1092 {
   1093 	return unrealdb_copy_config(db->config);
   1094 }
   1095 
   1096 void unrealdb_free_config(UnrealDBConfig *c)
   1097 {
   1098 	if (!c)
   1099 		return;
   1100 	safe_free(c->salt);
   1101 	safe_free_sensitive(c->key);
   1102 	safe_free(c);
   1103 }
   1104 
   1105 static int unrealdb_config_identical(UnrealDBConfig *one, UnrealDBConfig *two)
   1106 {
   1107 	/* NOTE: do not compare 'key' here or all cache lookups will fail */
   1108 	if ((one->kdf == two->kdf) &&
   1109 	    (one->t_cost == two->t_cost) &&
   1110 	    (one->m_cost == two->m_cost) &&
   1111 	    (one->p_cost == two->p_cost) &&
   1112 	    (one->saltlen == two->saltlen) &&
   1113 	    (memcmp(one->salt, two->salt, one->saltlen) == 0) &&
   1114 	    (one->cipher == two->cipher) &&
   1115 	    (one->keylen == two->keylen))
   1116 	{
   1117 		return 1;
   1118 	}
   1119 	return 0;
   1120 }
   1121 
   1122 static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg)
   1123 {
   1124 	SecretCache *c;
   1125 
   1126 	for (c = secr->cache; c; c = c->next)
   1127 	{
   1128 		if (unrealdb_config_identical(c->config, cfg))
   1129 		{
   1130 			c->cache_hit = TStime();
   1131 			return c;
   1132 		}
   1133 	}
   1134 	return NULL;
   1135 }
   1136 
   1137 static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg)
   1138 {
   1139 	SecretCache *c = find_secret_cache(secr, cfg);
   1140 
   1141 	if (c)
   1142 		return; /* Entry already exists in cache */
   1143 
   1144 	/* New entry, add! */
   1145 	c = safe_alloc(sizeof(SecretCache));
   1146 	c->config = unrealdb_copy_config(cfg);
   1147 	c->cache_hit = TStime();
   1148 	AddListItem(c, secr->cache);
   1149 }
   1150 
   1151 #ifdef DEBUGMODE
   1152 #define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	1200
   1153 #else
   1154 #define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	86400
   1155 #endif
   1156 
   1157 /** Expire cached secret entries (previous Argon2 runs) */
   1158 EVENT(unrealdb_expire_secret_cache)
   1159 {
   1160 	Secret *s;
   1161 	SecretCache *c, *c_next;
   1162 	for (s = secrets; s; s = s->next)
   1163 	{
   1164 		for (c = s->cache; c; c = c_next)
   1165 		{
   1166 			c_next = c->next;
   1167 			if (c->cache_hit < TStime() - UNREALDB_EXPIRE_SECRET_CACHE_AFTER)
   1168 			{
   1169 				DelListItem(c, s->cache);
   1170 				free_secret_cache(c);
   1171 			}
   1172 		}
   1173 	}
   1174 }