aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/stringhelp.c33
-rw-r--r--common/stringhelp.h7
-rw-r--r--common/t-stringhelp.c112
-rw-r--r--g10/getkey.c19
-rw-r--r--g10/import.c6
-rw-r--r--g10/keydb.h5
-rw-r--r--g10/keylist.c5
-rw-r--r--g10/options.h1
-rw-r--r--g10/pubkey-enc.c2
-rw-r--r--g10/skclist.c3
-rw-r--r--kbx/backend-sqlite.c129
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)