X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/cf9f324212bddcc09f405b94b9a01a5c99ffe19b..627391cbcaf3376e201be3ab2815b5abc560ce0c:/src/src/hintsdb.h diff --git a/src/src/hintsdb.h b/src/src/hintsdb.h index ed1b5566d..3898b0221 100644 --- a/src/src/hintsdb.h +++ b/src/src/hintsdb.h @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -16,13 +16,312 @@ from Pierre A. Humblet, so Exim could be made to work with Cygwin. For convenience, the definitions of the structures used in the various hints databases are also kept in this file, which is used by the maintenance -utilities as well as the main Exim binary. */ +utilities as well as the main Exim binary. + +A key/value store is supported (only). Keys are strings; values arbitrary +binary blobs. + +The API is: + Functions: + exim_dbopen O_RDONLY/O_RDWR, optionally OR'd with O_CREAT + exim_dbclose + exim_dbget + exim_dbput + exim_dbputb (non-overwriting put) + exim_dbdel + exim_dbcreate_cursor + exim_dbscan (get, and bump cursor) + exim_dbdelete_cursor + exim_datum_init + exim_datum_size_get + exim_datum_data_get + exim_datum_free + Defines: + EXIM_DB access handle + EXIM_CURSOR datatype for cursor + EXIM_DATUM datatype for "value" + EXIM_DBTYPE text for logging & debuug + +Selection of the shim layer implementation, and backend, is by #defines. + +The users of this API are: + hintsdb interface dbfn.c + hintsdb utilities exim_dbutil.c and exim_dbmvuild.c + dbmdb lookup lookups/dbmdb,c + autoreply transport transports/autoreply.c +*/ #ifndef HINTSDB_H #define HINTSDB_H -#if defined(USE_TDB) +#ifdef USE_SQLITE + +/* ********************* sqlite3 interface ************************ */ + +# include + +/* Basic DB type */ +# define EXIM_DB sqlite3 + +# define EXIM_CURSOR int + +# /* The datum type used for queries */ +# define EXIM_DATUM blob + +/* Some text for messages */ +# define EXIM_DBTYPE "sqlite3" + +# /* Access functions */ + +/* EXIM_DBOPEN - return pointer to an EXIM_DB, NULL if failed */ +static inline EXIM_DB * +exim_dbopen__(const uschar * name, const uschar * dirname, int flags, + unsigned mode) +{ +EXIM_DB * dbp; +int ret, sflags = flags & O_RDWR ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY; +if (flags & O_CREAT) sflags |= SQLITE_OPEN_CREATE; +if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK) + { + sqlite3_busy_timeout(dbp, 5000); + if (flags & O_CREAT) + ret == sqlite3_exec(dbp, + "CREATE TABLE IF NOT EXISTS tbl (ky TEXT PRIMARY KEY, dat BLOB);", + NULL, NULL, NULL); + } +//else +// fprintf(stderr, "sqlite3_open_v2: %s\n", sqlite3_errmsg(dbp)); +return ret == SQLITE_OK ? dbp : NULL; +} + +/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */ +/* note we alloc'n'copy - the caller need not do so */ +/* result has a NUL appended, but the length is as per the DB */ + +static inline BOOL +exim_dbget__(EXIM_DB * dbp, const uschar * s, EXIM_DATUM * res) +{ +sqlite3_stmt * statement; +int ret; + +res->len = (size_t) -1; +/* fprintf(stderr, "exim_dbget__(%s)\n", s); */ +if ((ret = sqlite3_prepare_v2(dbp, CCS s, -1, &statement, NULL)) != SQLITE_OK) + { +/* fprintf(stderr, "prepare fail: %s\n", sqlite3_errmsg(dbp)); */ + return FALSE; + } +if (sqlite3_step(statement) != SQLITE_ROW) + { +/* fprintf(stderr, "step fail: %s\n", sqlite3_errmsg(dbp)); */ + sqlite3_finalize(statement); + return FALSE; + } + +res->len = sqlite3_column_bytes(statement, 0); +res->data = store_get(res->len + 1, GET_TAINTED); +memcpy(res->data, sqlite3_column_blob(statement, 0), res->len); +res->data[res->len] = '\0'; +/* fprintf(stderr, "res %d bytes: '%.*s'\n", (int)res->len, (int)res->len, res->data); */ +sqlite3_finalize(statement); +return TRUE; +} + +static inline BOOL +exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res) +{ +# define FMT "SELECT dat FROM tbl WHERE ky = '%.*s';" +uschar * qry; +int i; +BOOL ret; + +# ifdef COMPILE_UTILITY +/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */ +qry = malloc(i = snprintf(NULL, 0, FMT, (int) key->len, key->data)); +snprintf(CS qry, i, FMT, (int) key->len, key->data); +ret = exim_dbget__(dbp, qry, res); +free(qry); +# else +/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */ +qry = string_sprintf(FMT, (int) key->len, key->data); +ret = exim_dbget__(dbp, qry, res); +# endif + +return ret; +# undef FMT +} + +/**/ +# define EXIM_DBPUTB_OK 0 +# define EXIM_DBPUTB_DUP (-1) + +static inline int +exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt) +{ +# define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%.*s', X'%.*s');" +uschar * hex = store_get(data->len * 2, data->data), * qry; +int res; + +for (const uschar * s = data->data, * t = s + data->len; s < t; s++) + sprintf(CS hex + 2 * (s - data->data), "%02X", *s); + +# ifdef COMPILE_UTILITY +res = snprintf(NULL, 0, FMT, + alt, (int) key->len, key->data, (int)data->len * 2, hex); +qry = malloc(res); +snprintf(CS qry, res, FMT, alt, (int) key->len, key->data, (int)data->len * 2, hex); +/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */ +res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); +free(qry); +# else +qry = string_sprintf(FMT, alt, (int) key->len, key->data, (int)data->len * 2, hex); +/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */ +res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); +/* fprintf(stderr, "exim_s_dbp res %d\n", res); */ +# endif + +if (res != SQLITE_OK) + fprintf(stderr, "sqlite3_exec: %s\n", sqlite3_errmsg(dbp)); + +return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP; +# undef FMT +} + +/* EXIM_DBPUT - returns nothing useful, assumes replace mode */ + +static inline int +exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) +{ +/* fprintf(stderr, "exim_dbput()\n"); */ +(void) exim_s_dbp(dbp, key, data, US"REPLACE"); +return 0; +} + +/* EXIM_DBPUTB - non-overwriting for use by dbmbuild */ + +/* Returns from EXIM_DBPUTB */ + +static inline int +exim_dbputb(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data) +{ +return exim_s_dbp(dbp, key, data, US"ABORT"); +} + +/* EXIM_DBDEL */ +static inline int +exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key) +{ +# define FMT "DELETE FROM tbl WHERE ky = '%.*s';" +uschar * qry; +int res; + +# ifdef COMPILE_UTILITY +res = snprintf(NULL, 0, FMT, (int) key->len, key->data); +qry = malloc(res); +snprintf(CS qry, res, FMT, (int) key->len, key->data); +res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); +free(qry); +# else +qry = string_sprintf(FMT, (int) key->len, key->data); +res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL); +# endif + +return res; +# undef FMT +} + + +/* EXIM_DBCREATE_CURSOR - initialize for scanning operation */ +/* Cursors are inefficiently emulated by repeating searches */ + +static inline EXIM_CURSOR * +exim_dbcreate_cursor(EXIM_DB * dbp) +{ +EXIM_CURSOR * c = store_malloc(sizeof(int)); +*c = 0; +return c; +} + +/* EXIM_DBSCAN */ +/* Note that we return the (next) key, not the record value */ +static inline BOOL +exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res, BOOL first, + EXIM_CURSOR * cursor) +{ +# define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;" +uschar * qry; +int i; +BOOL ret; + +# ifdef COMPILE_UTILITY +qry = malloc(i = snprintf(NULL, 0, FMT, *cursor)); +snprintf(CS qry, i, FMT, *cursor); +/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */ +ret = exim_dbget__(dbp, qry, key); +free(qry); +/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */ +# else +qry = string_sprintf(FMT, *cursor); +/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */ +ret = exim_dbget__(dbp, qry, key); +/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */ +# endif +if (ret) *cursor = *cursor + 1; +return ret; +# undef FMT +} + +/* EXIM_DBDELETE_CURSOR - terminate scanning operation. */ +static inline void +exim_dbdelete_cursor(EXIM_CURSOR * cursor) +{ store_free(cursor); } + + +/* EXIM_DBCLOSE */ +static void +exim_dbclose__(EXIM_DB * db) +{ sqlite3_close(db); } + + +/* Datum access */ + +static uschar * +exim_datum_data_get(EXIM_DATUM * dp) +{ return US dp->data; } +static void +exim_datum_data_set(EXIM_DATUM * dp, void * s) +{ dp->data = s; } + +static unsigned +exim_datum_size_get(EXIM_DATUM * dp) +{ return dp->len; } +static void +exim_datum_size_set(EXIM_DATUM * dp, unsigned n) +{ dp->len = n; } + + + +static inline void +exim_datum_init(EXIM_DATUM * dp) +{ dp->data = NULL; } /* compiler quietening */ + +/* No free needed for a datum */ + +static inline void +exim_datum_free(EXIM_DATUM * dp) +{ } + +/* size limit */ + +# define EXIM_DB_RLIMIT 150 + + + + + + +#elif defined(USE_TDB) # if defined(USE_DB) || defined(USE_GDBM) # error USE_TDB conflict with alternate definition @@ -128,7 +427,7 @@ exim_datum_data_set(EXIM_DATUM * dp, void * s) static inline unsigned exim_datum_size_get(EXIM_DATUM * dp) -{ return dp->dsize; } +{ return dp->len; } static inline void exim_datum_size_set(EXIM_DATUM * dp, unsigned n) { dp->dsize = n; } @@ -243,8 +542,10 @@ if (db_create(&b, dbp, 0) == 0) { dbp->app_private = b; if (b->open(b, NULL, CS name, NULL, - flags == O_RDONLY ? DB_UNKNOWN : DB_HASH, - flags == O_RDONLY ? DB_RDONLY : DB_CREATE, + flags & O_CREAT ? DB_HASH : DB_UNKNOWN, + flags & O_CREAT ? DB_CREATE + : flags & O_RDONLY ? DB_RDONLY + : 0, /*XXX is there a writeable if exists option? */ mode) == 0 ) return dbp; @@ -377,8 +678,10 @@ EXIM_DB * dbp; return db_create(&dbp, NULL, 0) == 0 && ( dbp->set_errcall(dbp, dbfn_bdb_error_callback), dbp->open(dbp, CS name, NULL, - flags == O_RDONLY ? DB_UNKNOWN : DB_HASH, - flags == O_RDONLY ? DB_RDONLY : DB_CREATE, + flags & O_CREAT ? DB_HASH : DB_UNKNOWN, + flags & O_CREAT ? DB_CREATE + : flags & O_RDONLY ? DB_RDONLY + : 0, /*XXX*/ mode) ) == 0 ? dbp : NULL; @@ -821,3 +1124,5 @@ exim_dbclose__(dbp); #endif /* whole file */ /* End of hintsdb.h */ +/* vi: aw ai sw=2 +*/