diff options
| -rw-r--r-- | common/stringhelp.c | 33 | ||||
| -rw-r--r-- | common/stringhelp.h | 7 | ||||
| -rw-r--r-- | common/t-stringhelp.c | 112 | ||||
| -rw-r--r-- | g10/getkey.c | 19 | ||||
| -rw-r--r-- | g10/import.c | 6 | ||||
| -rw-r--r-- | g10/keydb.h | 5 | ||||
| -rw-r--r-- | g10/keylist.c | 5 | ||||
| -rw-r--r-- | g10/options.h | 1 | ||||
| -rw-r--r-- | g10/pubkey-enc.c | 2 | ||||
| -rw-r--r-- | g10/skclist.c | 3 | ||||
| -rw-r--r-- | kbx/backend-sqlite.c | 129 |
11 files changed, 294 insertions, 28 deletions
diff --git a/common/stringhelp.c b/common/stringhelp.c index 9347c3551..8bbc68ab1 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -2,7 +2,7 @@ * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch - * Copyright (C) 2015, 2021 g10 Code GmbH + * Copyright (C) 2015, 2021, 2025 g10 Code GmbH * * This file is part of GnuPG. * @@ -1721,6 +1721,37 @@ format_text (const char *text_in, int target_cols, int max_cols) } +/* In STRING replace the first occurance of SUBSTR by the string + * REPLACE. Return a new malloced string or set ERRNO and set NULL on + * error. If SUBSTR is not found a verbatim copy of STRING is + * returned. */ +char * +replace_substr (const char *string, const char *substr, const char *replace) +{ + size_t stringlen, substrlen, replacelen, n; + const char *s; + char *buffer; + + stringlen = strlen (string); + substrlen = strlen (substr); + replacelen = strlen (replace); + + if (stringlen < substrlen || !(s = strstr (string, substr))) + return xtrystrdup (string); + + stringlen -= substrlen; /* Found thus sryinglen >= substrlen */ + buffer = xtrymalloc (stringlen + replacelen +1); + if (!buffer) + return NULL; + memcpy (buffer, string, n=(s-string)); + memcpy (buffer+n, replace, replacelen); + strcpy (buffer+n+replacelen, s+substrlen); + + + return buffer; +} + + /* Substitute variables in STRING and return a new string. GETVAL is * a function which maps NAME to its value; that value is a string * which may not change during the execution time of this function. diff --git a/common/stringhelp.h b/common/stringhelp.h index d93373ec5..037ee8139 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -170,6 +170,13 @@ int compare_version_strings (const char *my_version, const char *req_version); /* Format a string so that it fits within about TARGET_COLS columns. */ char *format_text (const char *text, int target_cols, int max_cols); + +/* Return a new malloced string with the first occurance of SUBSTR in + * STRING replaced by REPLACE. Returns NULL on memory error. */ +char *replace_substr (const char *string, + const char *substr, const char *replace); + + /* Substitute variables in STRING. */ char *substitute_vars (const char *string, const char *(*getval)(void *cookie, const char *name), diff --git a/common/t-stringhelp.c b/common/t-stringhelp.c index b43bb7932..3bd9ba928 100644 --- a/common/t-stringhelp.c +++ b/common/t-stringhelp.c @@ -1202,6 +1202,117 @@ test_compare_version_strings (void) static void +test_replace_substr (void) +{ + struct { + const char *string; + const char *substr; + const char *replace; + const char *result; + } t[] = { + { "Look afar and see the end from the beginning.", + "see ", + "view ", + "Look afar and view the end from the beginning." + }, + { "Look afar and see the end from the beginning.", + "see ", + "xxx ", + "Look afar and xxx the end from the beginning." + }, + { "Look afar and see the end from the beginning.", + "see ", + "xx ", + "Look afar and xx the end from the beginning." + }, + { "Look afar and see the end from the beginning.", + "see ", + "x ", + "Look afar and x the end from the beginning." + }, + { "Look afar and see the end from the beginning.", + "see ", + " ", + "Look afar and the end from the beginning." + }, + { "Look afar and see the end from the beginning.", + "see ", + "", + "Look afar and the end from the beginning." + }, + { "Be different: conform.", + "", + "xxx", + "xxxBe different: conform." + }, + { "Be different: conform.", + "foo", + "bar", + "Be different: conform." + }, + { "Be different: conform.", + "different", + "unlike", + "Be unlike: conform." + }, + { "Be different: conform.", + "B", + "Bee", + "Beee different: conform." + }, + { "Be different: conform.", + ".", + "", + "Be different: conform" + }, + { "Be different: conform.", + ".", + "!", + "Be different: conform!" + }, + { "Be different: conform.", + ".", + "...", + "Be different: conform..." + }, + { "Be different: conform.", + ":", + " - this is a very long replacement string - ", + "Be different - this is a very long replacement string - conform." + }, + { "", + "", + "", + "" + } + }; + int idx; + char *res; + + for (idx=0; idx < DIM(t); idx++) + { + res = replace_substr (t[idx].string, t[idx].substr, t[idx].replace); + if (!res) + { + fprintf (stderr,"error replacing in '%s' (test %d): %s\n", + t[idx].string, idx, strerror (errno)); + exit (2); + } + if (strcmp (res, t[idx].result)) + { + fprintf (stderr, "string is '%s'\n", t[idx].string); + fprintf (stderr, " substr '%s'\n", t[idx].substr); + fprintf (stderr, " replace '%s'\n", t[idx].replace); + fprintf (stderr, " expected '%s'\n", t[idx].result); + fprintf (stderr, " got '%s'\n", res); + fail (idx); + } + xfree (res); + } +} + + +static void test_substitute_envvars (void) { struct { @@ -1317,6 +1428,7 @@ main (int argc, char **argv) test_split_fields_colon (); test_compare_version_strings (); test_format_text (); + test_replace_substr (); test_substitute_envvars (); xfree (home_buffer); diff --git a/g10/getkey.c b/g10/getkey.c index d9f35a935..084bd654d 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -613,6 +613,7 @@ get_pubkey_fast (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) /* Return the key block for the key with key id KEYID or NULL, if an * error occurs. Use release_kbnode() to release the key block. + * The only supported FLAGS bit is GETKEY_ALLOW_ADSK. * * The self-signed data has already been merged into the public key * using merge_selfsigs. */ @@ -633,7 +634,7 @@ get_pubkeyblock_ext (ctrl_t ctrl, u32 * keyid, unsigned int flags) ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; ctx.items[0].u.kid[0] = keyid[0]; ctx.items[0].u.kid[1] = keyid[1]; - ctx.allow_adsk = !!(flags & GET_PUBKEYBLOCK_FLAG_ADSK); + ctx.allow_adsk = !!(flags & GETKEY_ALLOW_ADSK); rc = lookup (ctrl, &ctx, 0, &keyblock, NULL); getkey_end (ctrl, &ctx); @@ -796,6 +797,10 @@ leave: (see the documentation for skip_unusable for an exact definition) are skipped unless they are looked up by key id or by fingerprint. + If the GETKEY_ALLOW_ADSK bit is set in FLAGS, ADSK keys are always + returned. Without that they are only returned if they have been + requested by PK->REQ_USAGE. + If RET_KB is not NULL, the keyblock is returned in *RET_KB. This should be freed using release_kbnode(). @@ -884,6 +889,7 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, } ctx->want_secret = !!(flags & GETKEY_WANT_SECRET); + ctx->allow_adsk = !!(flags & GETKEY_ALLOW_ADSK); ctx->kr_handle = keydb_new (ctrl); if (!ctx->kr_handle) { @@ -898,6 +904,7 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, if (ret_kdbhd) keydb_lock (ctx->kr_handle); + if (pk) { /* It is a bit tricky to allow returning an ADSK key: lookup @@ -2302,8 +2309,9 @@ get_seckey_default (ctrl_t ctrl, PKT_public_key *pk) * database does an OR of the terms, not an AND.) If NAMES is * NULL, then all results are returned. * - * If WANT_SECRET is set, then only keys with an available secret key - * (either locally or via key registered on a smartcard) are returned. + * If GETKEY_WANT_SECRET is set in FLAGS, only keys with an available + * secret key (either locally or via key registered on a smartcard) + * are returned. * * This function does not skip unusable keys (see the documentation * for skip_unusable for an exact definition). @@ -2316,11 +2324,10 @@ get_seckey_default (ctrl_t ctrl, PKT_public_key *pk) * (if want_secret is set) is returned if the key is not found. */ gpg_error_t getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, - strlist_t names, int want_secret, kbnode_t *ret_keyblock) + strlist_t names, unsigned int flags, kbnode_t *ret_keyblock) { return key_byname (ctrl, retctx, names, pk, - ((want_secret ? GETKEY_WANT_SECRET : 0) - | GETKEY_WITH_UNUSABLE), + (flags | GETKEY_WITH_UNUSABLE), ret_keyblock, NULL); } diff --git a/g10/import.c b/g10/import.c index 9affe057c..1f1a045d4 100644 --- a/g10/import.c +++ b/g10/import.c @@ -209,6 +209,9 @@ parse_import_options(char *str,unsigned int *options,int noisy) {"repair-keys", IMPORT_REPAIR_KEYS, NULL, N_("repair keys on import")}, + {"force-update", IMPORT_FORCE_UPDATE, NULL, + N_("update even unchanged keys")}, + /* New options. Right now, without description string. */ {"ignore-attributes", IMPORT_IGNORE_ATTRIBUTES, NULL, NULL}, @@ -2364,7 +2367,8 @@ import_one_real (ctrl_t ctrl, NULL, NULL); } - if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned) + if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned + || (options & IMPORT_FORCE_UPDATE)) { /* Unless we are in restore mode apply meta data to the * keyblock. Note that this will never change the first packet diff --git a/g10/keydb.h b/g10/keydb.h index 526620ce4..364e1287c 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -357,7 +357,6 @@ int get_pubkey_fast (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); kbnode_t get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig); /* Return the key block for the key with KEYID. */ -#define GET_PUBKEYBLOCK_FLAG_ADSK 1 /* Allow returning ADSK key. */ kbnode_t get_pubkeyblock_ext (ctrl_t ctrl, u32 *keyid, unsigned int flags); kbnode_t get_pubkeyblock (ctrl_t ctrl, u32 *keyid); @@ -387,6 +386,8 @@ enum get_pubkey_modes /* Other flags for functions in getkey.c */ #define GETKEY_WANT_SECRET 1 /* Only return keys having a secret key. */ #define GETKEY_WITH_UNUSABLE 2 /* Include unusable keys. */ +#define GETKEY_ALLOW_ADSK 4 /* Always return ADSK keys. */ + /* Find a public key identified by NAME. */ int get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, @@ -453,7 +454,7 @@ gpg_error_t get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk, /* Search for keys matching some criteria. */ gpg_error_t getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, - strlist_t names, int want_secret, + strlist_t names, unsigned int flags, kbnode_t *ret_keyblock); /* Search for one key matching some criteria. */ diff --git a/g10/keylist.c b/g10/keylist.c index e45471e87..aabffe9bb 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -934,7 +934,10 @@ list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret) * functions) or to have the search function return indicators for * found names. Yet another way is to use the keydb search * facilities directly. */ - rc = getkey_bynames (ctrl, &ctx, NULL, names, secret, &keyblock); + rc = getkey_bynames (ctrl, &ctx, NULL, names, + (GETKEY_ALLOW_ADSK + | (secret ? GETKEY_WANT_SECRET : 0)), + &keyblock); if (rc) { log_error ("error reading key: %s\n", gpg_strerror (rc)); diff --git a/g10/options.h b/g10/options.h index 962f45f16..8757b4b9a 100644 --- a/g10/options.h +++ b/g10/options.h @@ -440,6 +440,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define IMPORT_COLLAPSE_SUBKEYS (1<<16) #define IMPORT_BULK (1<<17) #define IMPORT_IGNORE_ATTRIBUTES (1<<18) +#define IMPORT_FORCE_UPDATE (1<<19) #define EXPORT_LOCAL_SIGS (1<<0) #define EXPORT_ATTRIBUTES (1<<1) diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c index d9a68d587..396e125d9 100644 --- a/g10/pubkey-enc.c +++ b/g10/pubkey-enc.c @@ -461,7 +461,7 @@ get_it (ctrl_t ctrl, { PKT_public_key *pk = NULL; PKT_public_key *mainpk = NULL; - KBNODE pkb = get_pubkeyblock_ext (ctrl, keyid, GET_PUBKEYBLOCK_FLAG_ADSK); + kbnode_t pkb = get_pubkeyblock_ext (ctrl, keyid, GETKEY_ALLOW_ADSK); if (!pkb) { diff --git a/g10/skclist.c b/g10/skclist.c index fe77aaede..6b16879ce 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -444,7 +444,8 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) break; case 5: /* Init search context to enum all secret keys. */ - err = getkey_bynames (ctrl, &c->ctx, NULL, NULL, 1, + err = getkey_bynames (ctrl, &c->ctx, NULL, NULL, + GETKEY_WANT_SECRET, &keyblock); if (err) { diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c index 97906235c..1ac757330 100644 --- a/kbx/backend-sqlite.c +++ b/kbx/backend-sqlite.c @@ -89,14 +89,16 @@ static sqlite3 *database_hd; /* A lockfile used make sure only we are accessing the database. */ static dotlock_t database_lock; -/* The version of our current database schema. */ -#define DATABASE_VERSION 1 +/* The version of our current database schema and the maximum version + * supported without migration. */ +#define DATABASE_VERSION 2 +#define DATABASE_VERSION_MAX 2 /* Table definitions for the database. */ static struct { const char *sql; - int special; + const char *name; } table_definitions[] = { { "PRAGMA foreign_keys = ON" }, @@ -109,7 +111,7 @@ static struct { "CREATE TABLE IF NOT EXISTS config (" "name TEXT NOT NULL UNIQUE," "value TEXT NOT NULL " - ")", 1 }, + ")", "config" }, /* The actual data; either X.509 certificates or OpenPGP * keyblocks. */ @@ -132,7 +134,7 @@ static struct { "CREATE TABLE IF NOT EXISTS fingerprint (" /* The fingerprint, for OpenPGP either 20 octets or 32 octets; * for X.509 it is the same as the UBID. */ - "fpr BLOB NOT NULL PRIMARY KEY," + "fpr BLOB NOT NULL," /* The long keyid as a 64 bit blob. */ "kid BLOB NOT NULL," /* The keygrip for this key. */ @@ -142,12 +144,15 @@ static struct "subkey INTEGER NOT NULL," /* The Unique Blob ID (possibly truncated fingerprint). */ "ubid BLOB NOT NULL REFERENCES pubkey" - ")" }, + ")", "fpr" }, /* Indices for the fingerprint table. */ - { "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)" }, - { "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" }, - { "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" }, + { "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)", + "fpr-index" }, + { "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)", + "fpr-index" }, + { "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)", + "fpr-index" }, /* Table to allow fast access via user ids or mail addresses. */ { "CREATE TABLE IF NOT EXISTS userid (" @@ -555,6 +560,82 @@ dblock_info_cb (dotlock_t h, void *opaque, enum dotlock_reasons reason, return rc; } + +/* Migrate from database version 1 to 2. We need apply this change: + * CREATE TABLE IF NOT EXISTS fingerprint ( + * - fpr BLOB NOT NULL PRIMARY KEY, + * + fpr BLOB NOT NULL, + * That is dropping the wrong PRIMARY KEY constraint. Unfortunately + * this is not a straightforward ALTER TABLE. The function is only + * called from create_or_open_database but it is guaranteed that the + * database is open. + */ +static gpg_error_t +migrate_from_v1_to_v2 (void) +{ + gpg_error_t err; + int idx; + const char *origsql = NULL; + char *sql = NULL; + int intransaction = 0; + + log_info ("migrating database from version 1 to version 2\n"); + for (idx=0; idx < DIM(table_definitions); idx++) + if (table_definitions[idx].name + && !strcmp (table_definitions[idx].name, "fpr")) + { + origsql = table_definitions[idx].sql; + break; + } + log_assert (origsql); + sql = replace_substr (origsql, " fingerprint ", " fingerprint_new "); + if (!sql) + return gpg_error_from_syserror (); + + err = run_sql_statement ("begin transaction"); + if (err) + goto leave; + intransaction = 1; + err = run_sql_statement (sql); + if (err) + goto leave; + err = run_sql_statement ("INSERT INTO fingerprint_new" + " SELECT * FROM fingerprint"); + if (err) + goto leave; + err = run_sql_statement ("DROP TABLE fingerprint"); + if (err) + goto leave; + err = run_sql_statement ("ALTER TABLE fingerprint_new RENAME TO fingerprint"); + if (err) + goto leave; + for (idx=0; idx < DIM(table_definitions); idx++) + if (table_definitions[idx].name + && !strcmp (table_definitions[idx].name, "fpr-index")) + { + err = run_sql_statement (table_definitions[idx].sql); + if (err) + goto leave; + } + err = set_config_value ("dbversion", STR2(DATABASE_VERSION)); + if (err) + goto leave; + err = run_sql_statement ("commit"); + if (err) + goto leave; + intransaction = 0; + log_info ("database migration succeeded\n"); + + + leave: + if (intransaction && run_sql_statement ("rollback")) + log_error ("Warning: database rollback failed - should not happen!\n"); + xfree (sql); + return err; +} + + + /* Create and initialize a new SQL database file if it does not * exists; else open it and check that all required objects are * available. */ @@ -567,6 +648,7 @@ create_or_open_database (ctrl_t ctrl, const char *filename) char *value; int dbversion; int setdbversion = 0; + int baddbversion = 0; acquire_mutex (); @@ -631,12 +713,14 @@ create_or_open_database (ctrl_t ctrl, const char *filename) sqlite3_extended_result_codes (database_hd, 1); /* Create the tables if needed. */ + dbversion = 0; /* unknown. */ for (idx=0; idx < DIM(table_definitions); idx++) { err = run_sql_statement (table_definitions[idx].sql); if (err) goto leave; - if (table_definitions[idx].special == 1) + if (table_definitions[idx].name + && !strcmp (table_definitions[idx].name, "config")) { /* Check and create dbversion etc entries. */ err = get_config_value ("dbversion", &value); @@ -652,10 +736,11 @@ create_or_open_database (ctrl_t ctrl, const char *filename) err = 0; dbversion = 0; } - else if ((dbversion = atoi (value)) < 1) + else if ((dbversion = atoi (value)) < DATABASE_VERSION + || dbversion > DATABASE_VERSION_MAX) { log_error ("database version %d is not valid\n", dbversion); - dbversion = 0; + baddbversion = 1; } log_info ("database version: %d\n", dbversion); @@ -674,8 +759,12 @@ create_or_open_database (ctrl_t ctrl, const char *filename) } } - if (!opt.quiet) - log_info (_("database '%s' created\n"), filename); + if (opt.quiet) + ; + else if (setdbversion) + log_info ("database '%s' created\n", filename); + else + log_info ("database '%s' opened\n", filename); if (setdbversion) { @@ -683,9 +772,19 @@ create_or_open_database (ctrl_t ctrl, const char *filename) if (!err) err = set_config_value ("created", isotimestamp (gnupg_get_time ())); } + else if (dbversion == 1 && DATABASE_VERSION == 2) + err = migrate_from_v1_to_v2 (); + else if (baddbversion) + { + log_info ("no migration procedure for this database version available\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + err = 0; + if (err) + goto leave; - err = 0; leave: if (err) |
