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 }