diff --git a/kbx/Makefile.am b/kbx/Makefile.am
index 9f8f7953d..436733fc5 100644
--- a/kbx/Makefile.am
+++ b/kbx/Makefile.am
@@ -1,96 +1,99 @@
# Keybox Makefile
# Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see .
## Process this file with automake to produce Makefile.in
EXTRA_DIST = mkerrors keyboxd-w32info.rc
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
if HAVE_W32_SYSTEM
resource_objs += keyboxd-w32info.o
endif
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
noinst_LIBRARIES = libkeybox.a libkeybox509.a
bin_PROGRAMS = kbxutil
libexec_PROGRAMS = keyboxd
if HAVE_W32CE_SYSTEM
extra_libs = $(LIBASSUAN_LIBS)
else
extra_libs =
endif
common_libs = $(libcommon)
commonpth_libs = $(libcommonpth)
common_sources = \
keybox.h keybox-defs.h keybox-search-desc.h \
keybox-util.c \
keybox-init.c \
keybox-blob.c \
keybox-file.c \
keybox-search.c \
keybox-update.c \
keybox-openpgp.c \
keybox-dump.c
libkeybox_a_SOURCES = $(common_sources)
libkeybox509_a_SOURCES = $(common_sources)
libkeybox_a_CFLAGS = $(AM_CFLAGS)
libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
# We need W32SOCKLIBS because the init subsystem code in libcommon
# requires it - although we don't actually need it. It is easier
# to do it this way.
kbxutil_SOURCES = kbxutil.c $(common_sources)
kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
kbxutil_LDADD = $(common_libs) \
$(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \
$(NETLIBS)
keyboxd_SOURCES = \
keyboxd.c keyboxd.h \
kbxserver.c \
frontend.c frontend.h \
backend.h backend-support.c \
backend-cache.c \
backend-kbx.c \
+ backend-sqlite.c \
$(common_sources)
keyboxd_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 \
- $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(INCICONV)
+ $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(SQLITE3_CFLAGS) \
+ $(INCICONV)
keyboxd_LDADD = $(commonpth_libs) \
$(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
- $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
+ $(SQLITE3_LIBS) $(GPG_ERROR_LIBS) \
+ $(LIBINTL) $(NETLIBS) $(LIBICONV) \
$(resource_objs)
keyboxd_LDFLAGS = $(extra_bin_ldflags)
keyboxd_DEPENDENCIES = $(resource_objs)
# Make sure that all libs are build before we use them. This is
# important for things like make -j2.
$(PROGRAMS): $(common_libs) $(commonpth_libs)
diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c
index 25d4596af..1c4b04226 100644
--- a/kbx/backend-kbx.c
+++ b/kbx/backend-kbx.c
@@ -1,457 +1,455 @@
/* backend-kbx.c - Keybox format backend for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include "keyboxd.h"
#include "../common/i18n.h"
#include "backend.h"
#include "keybox.h"
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_KBX. */
unsigned int backend_id; /* Always the id of the backend. */
void *token; /* Used to create a new KEYBOX_HANDLE. */
char filename[1];
};
/* Check that the file FILENAME is a valid keybox file which can be
* used here. Common return codes:
*
* 0 := Valid keybox file
* GPG_ERR_ENOENT := No such file
* GPG_ERR_NO_OBJ := File exists with size zero.
* GPG_ERR_INV_OBJ:= File exists but is not a keybox file.
*/
static gpg_error_t
check_kbx_file_magic (const char *filename)
{
gpg_error_t err;
u32 magic;
unsigned char verbuf[4];
estream_t fp;
fp = es_fopen (filename, "rb");
if (!fp)
return gpg_error_from_syserror ();
err = gpg_error (GPG_ERR_INV_OBJ);
if (es_fread (&magic, 4, 1, fp) == 1 )
{
if (es_fread (&verbuf, 4, 1, fp) == 1
&& verbuf[0] == 1
&& es_fread (&magic, 4, 1, fp) == 1
&& !memcmp (&magic, "KBXf", 4))
{
err = 0;
}
}
else /* Maybe empty: Let's create it. */
err = gpg_error (GPG_ERR_NO_OBJ);
es_fclose (fp);
return err;
}
/* Create new keybox file. This can also be used if the keybox
* already exists but has a length of zero. Do not use it in any
* other cases. */
static gpg_error_t
create_keybox (const char *filename)
{
gpg_error_t err;
dotlock_t lockhd = NULL;
estream_t fp;
/* To avoid races with other temporary instances of keyboxd trying
* to create or update the keybox, we do the next stuff in a locked
* state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
err = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
* writable. However, this whole locking stuff does not make
* sense if this is the case. An empty non-writable directory
* with no keybox is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (err));
return err;
}
if ( dotlock_take (lockhd, -1) )
{
err = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
/* Make sure that at least one record is in a new keybox file, so
* that the detection magic will work the next time it is used.
* We always set the OpenPGP blobs maybe availabale flag. */
fp = es_fopen (filename, "w+b,mode=-rw-------");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (err));
goto leave;
}
err = _keybox_write_header_blob (NULL, fp, 1);
es_fclose (fp);
if (err)
{
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (err));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
err = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly)
{
gpg_error_t err;
backend_handle_t hd;
void *token;
(void)ctrl;
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd + strlen (filename));
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_KBX;
strcpy (hd->filename, filename);
err = check_kbx_file_magic (filename);
switch (gpg_err_code (err))
{
case 0:
break;
case GPG_ERR_ENOENT:
case GPG_ERR_NO_OBJ:
if (readonly)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
err = create_keybox (filename);
if (err)
goto leave;
break;
default:
goto leave;
}
err = keybox_register_file (filename, 0, &token);
if (err)
goto leave;
hd->backend_id = be_new_backend_id ();
hd->token = token;
*r_hd = hd;
hd = NULL;
leave:
xfree (hd);
return err;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
xfree (hd);
}
void
be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd)
{
keybox_release (kbx_hd);
}
/* Helper for be_find_request_part to initialize a kbx request part. */
gpg_error_t
be_kbx_init_request_part (backend_handle_t backend_hd, db_request_part_t part)
{
part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0);
if (!part->kbx_hd)
return gpg_error_from_syserror ();
return 0;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. */
gpg_error_t
be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t part;
size_t descindex;
unsigned long skipped_long_blobs;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
if (!desc)
err = keybox_search_reset (part->kbx_hd);
else
err = keybox_search (part->kbx_hd, desc, ndesc, KEYBOX_BLOBTYPE_PGP,
&descindex, &skipped_long_blobs);
if (err == -1)
err = gpg_error (GPG_ERR_EOF);
if (desc && !err)
{
/* Successful search operation. */
void *buffer;
size_t buflen;
enum pubkey_types pubkey_type;
unsigned char ubid[UBID_LEN];
err = keybox_get_data (part->kbx_hd, &buffer, &buflen,
&pubkey_type, ubid);
if (err)
goto leave;
err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, ubid);
if (!err)
be_cache_pubkey (ctrl, ubid, buffer, buflen, pubkey_type);
xfree (buffer);
}
leave:
return err;
}
/* Seek in the keybox to the given UBID (if UBID is not NULL) or to
* the primary fingerprint specified by (FPR,FPRLEN). BACKEND_HD is
* the handle for this backend and REQUEST is the current database
* request object. This does a dummy read so that the next search
* operation starts right after that UBID. */
gpg_error_t
be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid)
{
gpg_error_t err;
db_request_part_t part;
size_t descindex;
unsigned long skipped_long_blobs;
KEYDB_SEARCH_DESC desc;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_UBID;
memcpy (desc.u.ubid, ubid, UBID_LEN);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
err = keybox_search_reset (part->kbx_hd);
if (!err)
err = keybox_search (part->kbx_hd, &desc, 1, 0,
&descindex, &skipped_long_blobs);
if (err == -1)
err = gpg_error (GPG_ERR_EOF);
leave:
return err;
}
/* Insert (BLOB,BLOBLEN) into the keybox. BACKEND_HD is the handle
* for this backend and REQUEST is the current database request
* object. */
gpg_error_t
be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
ksba_cert_t cert = NULL;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
if (pktype == PUBKEY_TYPE_OPGP)
err = keybox_insert_keyblock (part->kbx_hd, blob, bloblen);
else if (pktype == PUBKEY_TYPE_X509)
{
unsigned char sha1[20];
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1, blob, bloblen);
err = keybox_insert_cert (part->kbx_hd, cert, sha1);
}
else
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
ksba_cert_release (cert);
return err;
}
/* Update (BLOB,BLOBLEN) in the keybox. BACKEND_HD is the handle for
* this backend and REQUEST is the current database request object. */
gpg_error_t
be_kbx_update (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
ksba_cert_t cert = NULL;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* FIXME: We make use of the fact that we know that the caller
* already did a keybox search. This needs to be made more
* explicit. */
if (pktype == PUBKEY_TYPE_OPGP)
{
err = keybox_update_keyblock (part->kbx_hd, blob, bloblen);
}
else if (pktype == PUBKEY_TYPE_X509)
{
unsigned char sha1[20];
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1, blob, bloblen);
err = keybox_update_cert (part->kbx_hd, cert, sha1);
}
else
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
ksba_cert_release (cert);
return err;
}
/* Delete the blob from the keybox. BACKEND_HD is the handle for
* this backend and REQUEST is the current database request object. */
gpg_error_t
be_kbx_delete (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request)
{
gpg_error_t err;
db_request_part_t part;
- ksba_cert_t cert = NULL;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* FIXME: We make use of the fact that we know that the caller
* already did a keybox search. This needs to be made more
* explicit. */
err = keybox_delete (part->kbx_hd);
leave:
- ksba_cert_release (cert);
return err;
}
diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c
new file mode 100644
index 000000000..e5fda9458
--- /dev/null
+++ b/kbx/backend-sqlite.c
@@ -0,0 +1,1288 @@
+/* backend-sqlite.c - SQLite based backend for keyboxd
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "keyboxd.h"
+#include "../common/i18n.h"
+#include "../common/mbox-util.h"
+#include "backend.h"
+#include "keybox-search-desc.h"
+#include "keybox-defs.h" /* (for the openpgp parser) */
+
+
+/* Add replacement error codes; GPGRT provides SQL error codes from
+ * version 1.37 on. */
+#if GPGRT_VERSION_NUMBER < 0x012500 /* 1.37 */
+
+static GPGRT_INLINE gpg_error_t
+gpg_err_code_from_sqlite (int sqlres)
+{
+ return sqlres? 1500 + (sqlres & 0xff) : 0;
+}
+
+#define GPG_ERR_SQL_OK 1500
+#define GPG_ERR_SQL_ROW 1600
+#define GPG_ERR_SQL_DONE 1601
+
+#endif /*GPGRT_VERSION_NUMBER*/
+
+
+/* Our definition of the backend handle. */
+struct backend_handle_s
+{
+ enum database_types db_type; /* Always DB_TYPE_SQLITE. */
+ unsigned int backend_id; /* Always the id of the backend. */
+
+ char filename[1];
+};
+
+
+/* Definition of local request data. */
+struct be_sqlite_local_s
+{
+ /* The statement object of the current select command. */
+ sqlite3_stmt *select_stmt;
+
+ /* The search mode represented by the current select command. */
+ KeydbSearchMode select_mode;
+
+ /* The select statement has been executed with success. */
+ int select_done;
+
+ /* The last row has already been reached. */
+ int select_eof;
+};
+
+
+/* The Mutex we use to protect all our SQLite calls. */
+static npth_mutex_t database_mutex = NPTH_MUTEX_INITIALIZER;
+/* The one and only database handle. */
+static sqlite3 *database_hd;
+/* A lockfile used make sure only we are accessing the database. */
+static dotlock_t database_lock;
+
+
+static struct
+{
+ const char *sql;
+ int special;
+} table_definitions[] =
+ {
+ { "PRAGMA foreign_keys = ON" },
+
+ /* Table to store config values:
+ * Standard name value pairs:
+ * dbversion = 1
+ * created =
+ */
+ { "CREATE TABLE IF NOT EXISTS config ("
+ "name TEXT NOT NULL,"
+ "value TEXT NOT NULL "
+ ")", 1 },
+
+ /* The actual data; either X.509 certificates or OpenPGP
+ * keyblocks. */
+ { "CREATE TABLE IF NOT EXISTS pubkey ("
+ /* The 20 octet truncted primary-fpr */
+ "ubid BLOB NOT NULL PRIMARY KEY,"
+ /* The type of the public key: 1 = openpgp, 2 = X.509. */
+ "type INTEGER NOT NULL,"
+ /* The OpenPGP keyblock or X.509 certificate. */
+ "keyblob BLOB NOT NULL"
+ ")" },
+
+ /* Table with fingerprints and keyids of OpenPGP and X.509 keys.
+ * It is also used for the primary key and the X.509 fingerprint
+ * because we want to be able to use the keyid and keygrip. */
+ { "CREATE TABLE IF NOT EXISTS fingerprint ("
+ "fpr BLOB NOT NULL PRIMARY KEY,"
+ /* The long keyid as 64 bit integer. */
+ "kid INTEGER NOT NULL,"
+ /* The keygrip for this key. */
+ "keygrip BLOB NOT NULL,"
+ /* 0 = primary, > 0 = subkey. */
+ "subkey INTEGER NOT NULL,"
+ /* The Unique Blob ID (possibly truncated fingerprint). */
+ "ubid BLOB NOT NULL REFERENCES pubkey"
+ ")" },
+
+ /* 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)" },
+
+
+ /* Table to allow fast access via user ids or mail addresses. */
+ { "CREATE TABLE IF NOT EXISTS userid ("
+ /* The full user id. */
+ "uid TEXT NOT NULL,"
+ /* The mail address if available or NULL. */
+ "addrspec TEXT,"
+ /* The type of the public key: 1 = openpgp, 2 = X.509. */
+ "type INTEGER NOT NULL,"
+ /* The Unique Blob ID (possibly truncated fingerprint). */
+ "ubid BLOB NOT NULL REFERENCES pubkey"
+ ")" },
+
+ /* Indices for the userid table. */
+ { "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" },
+ { "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" },
+ { "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" }
+
+ };
+
+
+
+
+/* Take a lock for accessing SQLite. */
+static void
+acquire_mutex (void)
+{
+ int res = npth_mutex_lock (&database_mutex);
+ if (res)
+ log_fatal ("failed to acquire database lock: %s\n",
+ gpg_strerror (gpg_error_from_errno (res)));
+}
+
+
+
+/* Release a lock. */
+static void
+release_mutex (void)
+{
+ int res = npth_mutex_unlock (&database_mutex);
+ if (res)
+ log_fatal ("failed to release database db lock: %s\n",
+ gpg_strerror (gpg_error_from_errno (res)));
+}
+
+
+static void
+show_sqlstr (const char *sqlstr)
+{
+ if (!opt.verbose)
+ return;
+
+ log_info ("(SQL: %s)\n", sqlstr);
+}
+
+
+static void
+show_sqlstmt (sqlite3_stmt *stmt)
+{
+ char *p;
+
+ if (!opt.verbose)
+ return;
+
+ p = sqlite3_expanded_sql (stmt);
+ if (p)
+ log_info ("(SQL: %s)\n", p);
+ sqlite3_free (p);
+}
+
+
+static gpg_error_t
+diag_prepare_err (int res, const char *sqlstr)
+{
+ gpg_error_t err;
+
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ show_sqlstr (sqlstr);
+ log_error ("error preparing SQL statement: %s\n", sqlite3_errstr (res));
+ return err;
+}
+
+static gpg_error_t
+diag_bind_err (int res, sqlite3_stmt *stmt)
+{
+ gpg_error_t err;
+
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ show_sqlstmt (stmt);
+ log_error ("error binding a value to an SQL statement: %s\n",
+ sqlite3_errstr (res));
+ return err;
+}
+
+
+static gpg_error_t
+diag_step_err (int res, sqlite3_stmt *stmt)
+{
+ gpg_error_t err;
+
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ show_sqlstmt (stmt);
+ log_error ("error executing SQL statement: %s\n", sqlite3_errstr (res));
+ return err;
+}
+
+
+/* We store the keyid in the database as an integer - this function
+ * converts it from a memory buffer. */
+static GPGRT_INLINE sqlite3_int64
+kid_from_mem (const unsigned char *keyid)
+{
+ return ( ((uint64_t)keyid[0] << 56)
+ | ((uint64_t)keyid[1] << 48)
+ | ((uint64_t)keyid[2] << 40)
+ | ((uint64_t)keyid[3] << 32)
+ | ((uint64_t)keyid[4] << 24)
+ | ((uint64_t)keyid[5] << 16)
+ | ((uint64_t)keyid[6] << 8)
+ | ((uint64_t)keyid[7])
+ );
+}
+
+
+/* We store the keyid in the database as an integer - this function
+ * converts it from the usual u32[2] array. */
+static GPGRT_INLINE sqlite3_int64
+kid_from_u32 (u32 *keyid)
+{
+ return (((uint64_t)keyid[0] << 32) | ((uint64_t)keyid[1]) );
+}
+
+
+/* Run an SQL reset on STMT. */
+static gpg_error_t
+run_sql_reset (sqlite3_stmt *stmt)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_reset (stmt);
+ if (res)
+ {
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ show_sqlstmt (stmt);
+ log_error ("error executing SQL reset: %s\n", sqlite3_errstr (res));
+ }
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Run an SQL prepare for SQLSTR and return a statement at R_STMT. */
+static gpg_error_t
+run_sql_prepare (const char *sqlstr, sqlite3_stmt **r_stmt)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL);
+ if (res)
+ err = diag_prepare_err (res, sqlstr);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Helper to bind a BLOB parameter to a statement. */
+static gpg_error_t
+run_sql_bind_blob (sqlite3_stmt *stmt, int no,
+ const void *blob, size_t bloblen)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_bind_blob (stmt, no, blob, bloblen, SQLITE_TRANSIENT);
+ if (res)
+ err = diag_bind_err (res, stmt);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Helper to bind an INTEGER parameter to a statement. */
+static gpg_error_t
+run_sql_bind_int (sqlite3_stmt *stmt, int no, int value)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_bind_int (stmt, no, value);
+ if (res)
+ err = diag_bind_err (res, stmt);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Helper to bind an INTEGER64 parameter to a statement. */
+static gpg_error_t
+run_sql_bind_int64 (sqlite3_stmt *stmt, int no, sqlite3_int64 value)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_bind_int64 (stmt, no, value);
+ if (res)
+ err = diag_bind_err (res, stmt);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Helper to bind a string parameter to a statement. VALUE is allowed
+ * to be NULL to bind NULL. */
+static gpg_error_t
+run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_bind_text (stmt, no, value, value? strlen (value):0,
+ SQLITE_TRANSIENT);
+ if (res)
+ err = diag_bind_err (res, stmt);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Helper to bind a string parameter to a statement. VALUE is allowed
+ * to be NULL to bind NULL. A non-NULL VALUE is clamped with percent
+ * signs. */
+static gpg_error_t
+run_sql_bind_text_like (sqlite3_stmt *stmt, int no, const char *value)
+{
+ gpg_error_t err;
+ int res;
+ char *buf;
+
+ if (!value)
+ {
+ res = sqlite3_bind_null (stmt, no);
+ buf = NULL;
+ }
+ else
+ {
+ buf = xtrymalloc (strlen (value) + 2 + 1);
+ if (!buf)
+ return gpg_error_from_syserror ();
+ *buf = '%';
+ strcpy (buf+1, value);
+ strcat (buf+1, "%");
+ res = sqlite3_bind_text (stmt, no, buf, strlen (buf), SQLITE_TRANSIENT);
+ }
+ if (res)
+ err = diag_bind_err (res, stmt);
+ else
+ err = 0;
+ xfree (buf);
+ return err;
+}
+
+
+/* Wrapper around sqlite3_step for use with simple functions. */
+static gpg_error_t
+run_sql_step (sqlite3_stmt *stmt)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_step (stmt);
+ if (res != SQLITE_DONE)
+ err = diag_step_err (res, stmt);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Wrapper around sqlite3_step for use with select. This version does
+ * not print diags for SQLITE_DONE or SQLITE_ROW but returns them as
+ * gpg error codes. */
+static gpg_error_t
+run_sql_step_for_select (sqlite3_stmt *stmt)
+{
+ gpg_error_t err;
+ int res;
+
+ res = sqlite3_step (stmt);
+ if (res == SQLITE_DONE || res == SQLITE_ROW)
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ else
+ {
+ /* SQL_OK is unexpected for a select so in this case we return
+ * the OK error code by bypassing the special mapping. */
+ if (!res)
+ err = gpg_error (GPG_ERR_SQL_OK);
+ else
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ show_sqlstmt (stmt);
+ log_error ("error running SQL step: %s\n", sqlite3_errstr (res));
+ }
+ return err;
+}
+
+
+/* Run the simple SQL statement in SQLSTR. If UBID is not NULL this
+ * will be bound to :1 in SQLSTR. This command may not be used for
+ * select or other command which return rows. */
+static gpg_error_t
+run_sql_statement_bind_ubid (const char *sqlstr, const unsigned char *ubid)
+{
+ gpg_error_t err;
+ sqlite3_stmt *stmt;
+
+ err = run_sql_prepare (sqlstr, &stmt);
+ if (err)
+ goto leave;
+ if (ubid)
+ {
+ err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
+ if (err)
+ goto leave;
+ }
+
+ err = run_sql_step (stmt);
+ sqlite3_finalize (stmt);
+ if (err)
+ goto leave;
+
+ leave:
+ return err;
+}
+
+
+/* Run the simple SQL statement in SQLSTR. This command may not be used
+ * for select or other command which return rows. */
+static gpg_error_t
+run_sql_statement (const char *sqlstr)
+{
+ return run_sql_statement_bind_ubid (sqlstr, NULL);
+}
+
+
+/* Create and intitialize a new SQL database file if it does not
+ * exists; else open it and check that all required objects are
+ * available. */
+static gpg_error_t
+create_or_open_database (const char *filename)
+{
+ gpg_error_t err;
+ int res;
+ int idx;
+
+ if (database_hd)
+ return 0; /* Already initialized. */
+
+ acquire_mutex ();
+
+ /* To avoid races with other temporary instances of keyboxd trying
+ * to create or update the database, we run the database with a lock
+ * file held. */
+ database_lock = dotlock_create (filename, 0);
+ if (!database_lock)
+ {
+ err = gpg_error_from_syserror ();
+ /* A reason for this to fail is that the directory is not
+ * writable. However, this whole locking stuff does not make
+ * sense if this is the case. An empty non-writable directory
+ * with no database is not really useful at all. */
+ if (opt.verbose)
+ log_info ("can't allocate lock for '%s': %s\n",
+ filename, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (dotlock_take (database_lock, -1))
+ {
+ err = gpg_error_from_syserror ();
+ /* This is something bad. Probably a stale lockfile. */
+ log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Database has not yet been opened. Open or create it, make sure
+ * the tables exist, and prepare the required statements. We use
+ * our own locking instead of the more complex serialization sqlite
+ * would have to do and it avoid that we call
+ * npth_unprotect/protect. */
+ res = sqlite3_open_v2 (filename,
+ &database_hd,
+ (SQLITE_OPEN_READWRITE
+ | SQLITE_OPEN_CREATE
+ | SQLITE_OPEN_NOMUTEX),
+ NULL);
+ if (res)
+ {
+ err = gpg_error (gpg_err_code_from_sqlite (res));
+ log_error ("error opening '%s': %s\n", filename, sqlite3_errstr (res));
+ goto leave;
+ }
+ /* Enable extended error codes. */
+ sqlite3_extended_result_codes (database_hd, 1);
+
+ /* Create the tables if needed. */
+ 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)
+ {
+ /* Check and create dbversion etc entries. */
+ // FIXME
+ }
+ }
+
+ if (!opt.quiet)
+ log_info (_("database '%s' created\n"), filename);
+ err = 0;
+
+ leave:
+ if (err)
+ {
+ log_error (_("error creating database '%s': %s\n"),
+ filename, gpg_strerror (err));
+ dotlock_release (database_lock);
+ dotlock_destroy (database_lock);
+ database_lock = NULL;
+ }
+ release_mutex ();
+ return err;
+}
+
+
+/* Install a new resource and return a handle for that backend. */
+gpg_error_t
+be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
+ const char *filename, int readonly)
+{
+ gpg_error_t err;
+ backend_handle_t hd;
+
+ (void)ctrl;
+ (void)readonly; /* FIXME: implement read-only mode. */
+
+ *r_hd = NULL;
+ hd = xtrycalloc (1, sizeof *hd + strlen (filename));
+ if (!hd)
+ return gpg_error_from_syserror ();
+ hd->db_type = DB_TYPE_SQLITE;
+ strcpy (hd->filename, filename);
+
+ err = create_or_open_database (filename);
+ if (err)
+ goto leave;
+
+ hd->backend_id = be_new_backend_id ();
+
+ *r_hd = hd;
+ hd = NULL;
+
+ leave:
+ xfree (hd);
+ return err;
+}
+
+
+/* Release the backend handle HD and all its resources. HD is not
+ * valid after a call to this function. */
+void
+be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd)
+{
+ (void)ctrl;
+
+ if (!hd)
+ return;
+ hd->db_type = DB_TYPE_NONE;
+
+ xfree (hd);
+}
+
+
+/* Helper for be_find_request_part to initialize a sqlite request part. */
+gpg_error_t
+be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part)
+{
+ (void)backend_hd;
+
+ part->besqlite = xtrycalloc (1, sizeof *part->besqlite);
+ if (!part->besqlite)
+ return gpg_error_from_syserror ();
+ return 0;
+}
+
+
+/* Release local data of a sqlite request part. */
+void
+be_sqlite_release_local (be_sqlite_local_t ctx)
+{
+ if (ctx->select_stmt)
+ sqlite3_finalize (ctx->select_stmt);
+ xfree (ctx);
+}
+
+
+/* Run a select for the search given by (DESC,NDESC). The data is not
+ * returned but stored in the request item. */
+static gpg_error_t
+run_select_statement (be_sqlite_local_t ctx,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
+{
+ gpg_error_t err = 0;
+ unsigned int descidx;
+
+ descidx = 0; /* Fixme: take from context. */
+ if (descidx >= ndesc)
+ {
+ err = gpg_error (GPG_ERR_EOF);
+ goto leave;
+ }
+
+ /* Check whether we can re-use the current select statement. */
+ if (!ctx->select_stmt)
+ ;
+ else if (ctx->select_mode != desc[descidx].mode)
+ {
+ sqlite3_finalize (ctx->select_stmt);
+ ctx->select_stmt = NULL;
+ }
+
+ ctx->select_mode = desc[descidx].mode;
+
+ /* Prepare the select and bind the parameters. */
+ if (ctx->select_stmt)
+ {
+ err = run_sql_reset (ctx->select_stmt);
+ if (err)
+ goto leave;
+ }
+ else
+ err = 0;
+
+ switch (desc[descidx].mode)
+ {
+ case KEYDB_SEARCH_MODE_NONE:
+ never_reached ();
+ err = gpg_error (GPG_ERR_INTERNAL);
+ break;
+
+ case KEYDB_SEARCH_MODE_EXACT:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, userid as u"
+ " WHERE u.uid = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAIL:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, userid as u"
+ " WHERE u.addrspec = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, userid as u"
+ " WHERE u.addrspec LIKE ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_text_like (ctx->select_stmt, 1,
+ desc[descidx].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, userid as u"
+ " WHERE u.uid LIKE ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_text_like (ctx->select_stmt, 1,
+ desc[descidx].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILEND:
+ case KEYDB_SEARCH_MODE_WORDS:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ /* if (has_issuer (blob, desc[n].u.name)) */
+ /* goto found; */
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER_SN:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ /* if (has_issuer_sn (blob, desc[n].u.name, */
+ /* sn_array? sn_array[n].sn : desc[n].sn, */
+ /* sn_array? sn_array[n].snlen : desc[n].snlen)) */
+ /* goto found; */
+ break;
+ case KEYDB_SEARCH_MODE_SN:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ /* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */
+ /* sn_array? sn_array[n].snlen : desc[n].snlen)) */
+ /* goto found; */
+ break;
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ /* if (has_subject (blob, desc[n].u.name)) */
+ /* goto found; */
+ break;
+
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ /* pk_no = has_short_kid (blob, desc[n].u.kid[1]); */
+ /* if (pk_no) */
+ /* goto found; */
+ break;
+
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, fingerprint as f"
+ " WHERE f.kid = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_int64 (ctx->select_stmt, 1,
+ kid_from_u32 (desc[descidx].u.kid));
+ break;
+
+ case KEYDB_SEARCH_MODE_FPR:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, fingerprint as f"
+ " WHERE f.fpr = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_blob (ctx->select_stmt, 1,
+ desc[descidx].u.fpr, desc[descidx].fprlen);
+ break;
+
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
+ " FROM pubkey as p, fingerprint as f"
+ " WHERE f.keygrip = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_blob (ctx->select_stmt, 1,
+ desc[descidx].u.grip, KEYGRIP_LEN);
+ break;
+
+ case KEYDB_SEARCH_MODE_UBID:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT ubid, type, keyblob"
+ " FROM pubkey"
+ " WHERE ubid = ?1",
+ &ctx->select_stmt);
+ if (!err)
+ err = run_sql_bind_blob (ctx->select_stmt, 1,
+ desc[descidx].u.ubid, UBID_LEN);
+ break;
+
+ case KEYDB_SEARCH_MODE_FIRST:
+ if (!ctx->select_stmt)
+ err = run_sql_prepare ("SELECT ubid, type, keyblob"
+ " FROM pubkey ORDER by ubid",
+ &ctx->select_stmt);
+ break;
+
+ case KEYDB_SEARCH_MODE_NEXT:
+ err = gpg_error (GPG_ERR_INTERNAL);
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ break;
+ }
+
+ leave:
+ return err;
+}
+
+
+/* Search for the keys described by (DESC,NDESC) and return them to
+ * the caller. BACKEND_HD is the handle for this backend and REQUEST
+ * is the current database request object. */
+gpg_error_t
+be_sqlite_search (ctrl_t ctrl,
+ backend_handle_t backend_hd, db_request_t request,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
+{
+ gpg_error_t err;
+ db_request_part_t part;
+ be_sqlite_local_t ctx;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
+ log_assert (request);
+
+ acquire_mutex ();
+
+ /* Find the specific request part or allocate it. */
+ err = be_find_request_part (backend_hd, request, &part);
+ if (err)
+ goto leave;
+ ctx = part->besqlite;
+
+ if (!desc)
+ {
+ /* Reset */
+ ctx->select_done = 0;
+ ctx->select_eof = 0;
+ err = 0;
+ goto leave;
+ }
+
+ if (ctx->select_eof)
+ {
+ /* Still in EOF state. */
+ err = gpg_error (GPG_ERR_EOF);
+ goto leave;
+ }
+
+ if (!ctx->select_done)
+ {
+ /* Initial search - run the select. */
+ err = run_select_statement (ctx, desc, ndesc);
+ if (err)
+ goto leave;
+ ctx->select_done = 1;
+ }
+
+ show_sqlstmt (ctx->select_stmt);
+
+ /* SQL select succeeded - get the first or next row. */
+ err = run_sql_step_for_select (ctx->select_stmt);
+ if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
+ {
+ int n;
+ const void *ubid, *keyblob;
+ size_t keybloblen;
+ enum pubkey_types pubkey_type;
+
+ ubid = sqlite3_column_blob (ctx->select_stmt, 0);
+ n = sqlite3_column_bytes (ctx->select_stmt, 0);
+ if (!ubid || n < 0)
+ {
+ if (!ubid && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
+ err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
+ else
+ err = gpg_error (GPG_ERR_DB_CORRUPTED);
+ show_sqlstmt (ctx->select_stmt);
+ log_error ("error in returned SQL column UBID: No column (n=%d)\n",n);
+ goto leave;
+ }
+ if (n != UBID_LEN)
+ {
+ show_sqlstmt (ctx->select_stmt);
+ log_error ("error in returned SQL column UBID: Bad value (n=%d)\n",n);
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ goto leave;
+ }
+
+ n = sqlite3_column_int (ctx->select_stmt, 1);
+ if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
+ {
+ err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
+ show_sqlstmt (ctx->select_stmt);
+ log_error ("error in returned SQL column TYPE: %s)\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+ pubkey_type = n;
+
+ keyblob = sqlite3_column_blob (ctx->select_stmt, 2);
+ n = sqlite3_column_bytes (ctx->select_stmt, 2);
+ if (!keyblob || n < 0)
+ {
+ if (!keyblob && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
+ err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
+ else
+ err = gpg_error (GPG_ERR_DB_CORRUPTED);
+ show_sqlstmt (ctx->select_stmt);
+ log_error ("error in returned SQL column KEYBLOB: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+ keybloblen = n;
+
+ err = be_return_pubkey (ctrl, keyblob, keybloblen, pubkey_type, ubid);
+ if (!err)
+ be_cache_pubkey (ctrl, ubid, keyblob, keybloblen, pubkey_type);
+ }
+ else if (gpg_err_code (err) == GPG_ERR_SQL_DONE)
+ {
+ /* FIXME: Move on to the next description index. */
+ err = gpg_error (GPG_ERR_EOF);
+ ctx->select_eof = 1;
+ }
+ else
+ {
+ log_assert (err);
+ }
+
+ leave:
+ release_mutex ();
+ return err;
+}
+
+
+
+/* Helper for be_sqlite_store to update or insert a row in the pubkey
+ * table. */
+static gpg_error_t
+store_into_pubkey (enum kbxd_store_modes mode,
+ enum pubkey_types pktype, const unsigned char *ubid,
+ const void *blob, size_t bloblen)
+{
+ gpg_error_t err;
+ const char *sqlstr;
+ sqlite3_stmt *stmt = NULL;
+
+ if (mode == KBXD_STORE_UPDATE)
+ sqlstr = ("UPDATE pubkey set keyblob = :3, type = :2 WHERE ubid = :1");
+ else if (mode == KBXD_STORE_INSERT)
+ sqlstr = ("INSERT INTO pubkey(ubid,type,keyblob) VALUES(:1,:2,:3)");
+ else /* Auto */
+ sqlstr = ("INSERT OR REPLACE INTO pubkey(ubid,type,keyblob)"
+ " VALUES(:1,:2,:3)");
+ err = run_sql_prepare (sqlstr, &stmt);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
+ if (err)
+ goto leave;
+ err = run_sql_bind_int (stmt, 2, (int)pktype);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 3, blob, bloblen);
+ if (err)
+ goto leave;
+
+ err = run_sql_step (stmt);
+
+ leave:
+ if (stmt)
+ sqlite3_finalize (stmt);
+ return err;
+}
+
+
+/* Helper for be_sqlite_store to update or insert a row in the
+ * fingerprint table. */
+static gpg_error_t
+store_into_fingerprint (const unsigned char *ubid, int subkey,
+ const unsigned char *keygrip, sqlite3_int64 kid,
+ const unsigned char *fpr, int fprlen)
+{
+ gpg_error_t err;
+ const char *sqlstr;
+ sqlite3_stmt *stmt = NULL;
+
+ sqlstr = ("INSERT OR REPLACE INTO fingerprint(fpr,kid,keygrip,subkey,ubid)"
+ " VALUES(:1,:2,:3,:4,:5)");
+ err = run_sql_prepare (sqlstr, &stmt);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 1, fpr, fprlen);
+ if (err)
+ goto leave;
+ err = run_sql_bind_int64 (stmt, 2, kid);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 3, keygrip, KEYGRIP_LEN);
+ if (err)
+ goto leave;
+ err = run_sql_bind_int (stmt, 4, subkey);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 5, ubid, UBID_LEN);
+ if (err)
+ goto leave;
+
+ err = run_sql_step (stmt);
+
+ leave:
+ if (stmt)
+ sqlite3_finalize (stmt);
+ return err;
+}
+
+
+/* Helper for be_sqlite_store to update or insert a row in the
+ * userid table. */
+static gpg_error_t
+store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
+ const char *uid)
+{
+ gpg_error_t err;
+ const char *sqlstr;
+ sqlite3_stmt *stmt = NULL;
+ char *addrspec = NULL;
+
+ sqlstr = ("INSERT OR REPLACE INTO userid(uid,addrspec,type,ubid)"
+ " VALUES(:1,:2,:3,:4)");
+ err = run_sql_prepare (sqlstr, &stmt);
+ if (err)
+ goto leave;
+
+ err = run_sql_bind_text (stmt, 1, uid);
+ if (err)
+ goto leave;
+ addrspec = mailbox_from_userid (uid, 0);
+ err = run_sql_bind_text (stmt, 2, addrspec);
+ if (err)
+ goto leave;
+ err = run_sql_bind_int (stmt, 3, pktype);
+ if (err)
+ goto leave;
+ err = run_sql_bind_blob (stmt, 4, ubid, UBID_LEN);
+ if (err)
+ goto leave;
+
+ err = run_sql_step (stmt);
+
+ leave:
+ if (stmt)
+ sqlite3_finalize (stmt);
+ xfree (addrspec);
+ return err;
+}
+
+
+/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID macthing
+ * that blob. BACKEND_HD is the handle for this backend and REQUEST
+ * is the current database request object. MODE is the store
+ * mode. */
+gpg_error_t
+be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, enum kbxd_store_modes mode,
+ enum pubkey_types pktype, const unsigned char *ubid,
+ const void *blob, size_t bloblen)
+{
+ gpg_error_t err;
+ db_request_part_t part;
+ /* be_sqlite_local_t ctx; */
+ int got_mutex = 0;
+ int in_transaction = 0;
+ int info_valid = 0;
+ struct _keybox_openpgp_info info;
+ struct _keybox_openpgp_key_info *kinfo;
+
+ (void)ctrl;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
+ log_assert (request);
+
+ /* Fixme: The code below is duplicated in be_ubid_from_blob - we
+ * should have only one function and pass the passed info around
+ * with the BLOB. */
+
+ if (be_is_x509_blob (blob, bloblen))
+ {
+ /* The UBID is also our fingerprint. */
+ /* FIXME: Extract keygrip and KID. */
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ goto leave;
+ }
+ else
+ {
+ err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
+ if (err)
+ {
+ log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
+ err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
+ goto leave;
+ }
+ info_valid = 1;
+ log_assert (pktype == PUBKEY_TYPE_OPGP);
+ log_assert (info.primary.fprlen >= 20);
+ log_assert (!memcmp (ubid, info.primary.fpr, UBID_LEN));
+ }
+
+
+ acquire_mutex ();
+ got_mutex = 1;
+
+ /* Find the specific request part or allocate it. */
+ err = be_find_request_part (backend_hd, request, &part);
+ if (err)
+ goto leave;
+ /* ctx = part->besqlite; */
+
+ err = run_sql_statement ("begin transaction");
+ if (err)
+ goto leave;
+ in_transaction = 1;
+
+ err = store_into_pubkey (mode, pktype, ubid, blob, bloblen);
+ if (err)
+ goto leave;
+
+ /* Delete all related rows so that we can freshly add possibly added
+ * or changed user ids and subkeys. */
+ err = run_sql_statement_bind_ubid
+ ("DELETE FROM fingerprint WHERE ubid = :1", ubid);
+ if (err)
+ goto leave;
+ err = run_sql_statement_bind_ubid
+ ("DELETE FROM userid WHERE ubid = :1", ubid);
+ if (err)
+ goto leave;
+
+ kinfo = &info.primary;
+ err = store_into_fingerprint (ubid, 0, kinfo->grip,
+ kid_from_mem (kinfo->keyid),
+ kinfo->fpr, kinfo->fprlen);
+ if (err)
+ goto leave;
+
+ if (info.nsubkeys)
+ {
+ int subkey = 1;
+ for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
+ {
+ err = store_into_fingerprint (ubid, subkey, kinfo->grip,
+ kid_from_mem (kinfo->keyid),
+ kinfo->fpr, kinfo->fprlen);
+ if (err)
+ goto leave;
+ }
+ }
+
+ if (info.nuids)
+ {
+ struct _keybox_openpgp_uid_info *u;
+
+ u = &info.uids;
+ do
+ {
+ log_assert (u->off <= bloblen);
+ log_assert (u->off + u->len <= bloblen);
+ {
+ char *uid = xtrymalloc (u->len + 1);
+ if (!uid)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ memcpy (uid, (const unsigned char *)blob + u->off, u->len);
+ uid[u->len] = 0;
+ /* Note that we ignore embedded zeros in the user id; this
+ * is what we do all over the place. */
+ err = store_into_userid (ubid, pktype, uid);
+ xfree (uid);
+ }
+ if (err)
+ goto leave;
+
+ u = u->next;
+ }
+ while (u);
+ }
+
+ leave:
+ if (in_transaction && !err)
+ err = run_sql_statement ("commit");
+ else if (in_transaction)
+ {
+ if (run_sql_statement ("rollback"))
+ log_error ("Warning: database rollback failed - should not happen!\n");
+ }
+ if (got_mutex)
+ release_mutex ();
+ if (info_valid)
+ _keybox_destroy_openpgp_info (&info);
+ return err;
+}
+
+
+/* Delete the blob specified by UBID from the database. BACKEND_HD is
+ * the handle for this backend and REQUEST is the current database
+ * request object. */
+gpg_error_t
+be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, const unsigned char *ubid)
+{
+ gpg_error_t err;
+ db_request_part_t part;
+ /* be_sqlite_local_t ctx; */
+ sqlite3_stmt *stmt = NULL;
+ int in_transaction = 0;
+
+ (void)ctrl;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
+ log_assert (request);
+
+ acquire_mutex ();
+
+ /* Find the specific request part or allocate it. */
+ err = be_find_request_part (backend_hd, request, &part);
+ if (err)
+ goto leave;
+ /* ctx = part->besqlite; */
+
+ err = run_sql_statement ("begin transaction");
+ if (err)
+ goto leave;
+ in_transaction = 1;
+
+ err = run_sql_statement_bind_ubid
+ ("DELETE from userid WHERE ubid = :1", ubid);
+ if (!err)
+ err = run_sql_statement_bind_ubid
+ ("DELETE from fingerprint WHERE ubid = :1", ubid);
+ if (!err)
+ err = run_sql_statement_bind_ubid
+ ("DELETE from pubkey WHERE ubid = :1", ubid);
+
+
+ leave:
+ if (stmt)
+ sqlite3_finalize (stmt);
+ if (in_transaction && !err)
+ err = run_sql_statement ("commit");
+ else if (in_transaction)
+ {
+ if (run_sql_statement ("rollback"))
+ log_error ("Warning: database rollback failed - should not happen!\n");
+ }
+ release_mutex ();
+ return err;
+}
diff --git a/kbx/backend-support.c b/kbx/backend-support.c
index f1e80b0c3..c8965da9a 100644
--- a/kbx/backend-support.c
+++ b/kbx/backend-support.c
@@ -1,270 +1,284 @@
/* backend-support.c - Supporting functions for the backend.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0+
*/
#include
#include
#include
#include
#include
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/tlv.h"
#include "backend.h"
#include "keybox-defs.h"
/* Common definition part of all backend handle. All definitions of
* this structure must start with these fields. */
struct backend_handle_s
{
enum database_types db_type;
unsigned int backend_id;
};
/* Return a string with the name of the database type T. */
const char *
strdbtype (enum database_types t)
{
switch (t)
{
case DB_TYPE_NONE: return "none";
case DB_TYPE_CACHE:return "cache";
case DB_TYPE_KBX: return "keybox";
+ case DB_TYPE_SQLITE: return "sqlite";
}
return "?";
}
/* Return a new backend ID. Backend IDs are used to identify backends
* without using the actual object. The number of backend resources
* is limited because they are specified in the config file. Thus an
* overflow check is not required. */
unsigned int
be_new_backend_id (void)
{
static unsigned int last;
return ++last;
}
/* Release the backend described by HD. This is a generic function
* which dispatches to the the actual backend. */
void
be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd)
{
if (!hd)
return;
switch (hd->db_type)
{
case DB_TYPE_NONE:
xfree (hd);
break;
case DB_TYPE_CACHE:
be_cache_release_resource (ctrl, hd);
break;
case DB_TYPE_KBX:
be_kbx_release_resource (ctrl, hd);
break;
+ case DB_TYPE_SQLITE:
+ be_sqlite_release_resource (ctrl, hd);
+ break;
default:
log_error ("%s: faulty backend handle of type %d given\n",
__func__, hd->db_type);
}
}
/* Release the request object REQ. */
void
be_release_request (db_request_t req)
{
db_request_part_t part, partn;
if (!req)
return;
for (part = req->part; part; part = partn)
{
partn = part->next;
be_kbx_release_kbx_hd (part->kbx_hd);
+ be_sqlite_release_local (part->besqlite);
xfree (part);
}
}
/* Given the backend handle BACKEND_HD and the REQUEST find or
* allocate a request part for that backend and store it at R_PART.
* On error R_PART is set to NULL and an error returned. */
gpg_error_t
be_find_request_part (backend_handle_t backend_hd, db_request_t request,
db_request_part_t *r_part)
{
gpg_error_t err;
db_request_part_t part;
for (part = request->part; part; part = part->next)
if (part->backend_id == backend_hd->backend_id)
break;
if (!part)
{
part = xtrycalloc (1, sizeof *part);
if (!part)
return gpg_error_from_syserror ();
part->backend_id = backend_hd->backend_id;
if (backend_hd->db_type == DB_TYPE_KBX)
{
err = be_kbx_init_request_part (backend_hd, part);
if (err)
{
xfree (part);
return err;
}
}
+ else if (backend_hd->db_type == DB_TYPE_SQLITE)
+ {
+ err = be_sqlite_init_local (backend_hd, part);
+ if (err)
+ {
+ xfree (part);
+ return err;
+ }
+ }
part->next = request->part;
request->part = part;
}
*r_part = part;
return 0;
}
/* Return the public key (BUFFER,BUFLEN) which has the type
* PUBKEY_TYPE to the caller. */
gpg_error_t
be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
enum pubkey_types pubkey_type, const unsigned char *ubid)
{
gpg_error_t err;
char hexubid[2*UBID_LEN+1];
bin2hex (ubid, UBID_LEN, hexubid);
err = status_printf (ctrl, "PUBKEY_INFO", "%d %s", pubkey_type, hexubid);
if (err)
goto leave;
if (ctrl->no_data_return)
err = 0;
else
err = kbxd_write_data_line(ctrl, buffer, buflen);
leave:
return err;
}
/* Return true if (BLOB/BLOBLEN) seems to be an X509 certificate. */
-static int
-is_x509_blob (const unsigned char *blob, size_t bloblen)
+int
+be_is_x509_blob (const unsigned char *blob, size_t bloblen)
{
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, cons, ndef;
/* An X.509 certificate can be identified by this DER encoding:
*
* 30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39
* ----------- +++++++++++ ----- ++++++++ --------------------------
* SEQUENCE SEQUENCE [0] INTEGER INTEGER
* (tbs) (version) (s/n)
*
*/
p = blob;
n = bloblen;
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
return 0; /* Does not start with a sequence. */
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
return 0; /* No TBS sequence. */
if (n < 7 || objlen < 7)
return 0; /* Too short: [0], version and min. s/n required. */
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_CONTEXT && tag == 0 && cons))
return 0; /* No context tag. */
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER
&& !cons && objlen == 1 && n && (*p == 1 || *p == 2)))
return 0; /* Unknown X.509 version. */
p++; /* Skip version number. */
n--;
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons))
return 0; /* No s/n. */
return 1; /* Looks like an X.509 certificate. */
}
/* Return the public key type and the (primary) fingerprint for
* (BLOB,BLOBLEN). r_UBID must point to a buffer of at least UBID_LEN
* bytes, on success it receives the UBID (primary fingerprint
* truncated 20 octets). R_PKTYPE receives the public key type. */
gpg_error_t
be_ubid_from_blob (const void *blob, size_t bloblen,
enum pubkey_types *r_pktype, char *r_ubid)
{
gpg_error_t err;
- if (is_x509_blob (blob, bloblen))
+ if (be_is_x509_blob (blob, bloblen))
{
/* Although libksba has a dedicated function to compute the
* fingerprint we compute it here directly because we know that
* we have the entire certificate here (we checked the start of
* the blob and assume that the length is also okay). */
*r_pktype = PUBKEY_TYPE_X509;
gcry_md_hash_buffer (GCRY_MD_SHA1, r_ubid, blob, bloblen);
err = 0;
}
else
{
struct _keybox_openpgp_info info;
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
}
else
{
*r_pktype = PUBKEY_TYPE_OPGP;
log_assert (info.primary.fprlen >= 20);
memcpy (r_ubid, info.primary.fpr, UBID_LEN);
_keybox_destroy_openpgp_info (&info);
}
}
return err;
}
diff --git a/kbx/backend.h b/kbx/backend.h
index 3aa848714..092f490bc 100644
--- a/kbx/backend.h
+++ b/kbx/backend.h
@@ -1,151 +1,181 @@
/* backend.h - Definitions for keyboxd backends
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#ifndef KBX_BACKEND_H
#define KBX_BACKEND_H
#include "keybox-search-desc.h"
/* Forward declaration of the keybox handle type. */
struct keybox_handle;
typedef struct keybox_handle *KEYBOX_HANDLE;
/* The types of the backends. */
enum database_types
{
DB_TYPE_NONE, /* No database at all (unitialized etc.). */
DB_TYPE_CACHE, /* The cache backend (backend-cache.c). */
- DB_TYPE_KBX /* Keybox type database (backend-kbx.c). */
+ DB_TYPE_KBX, /* Keybox type database (backend-kbx.c). */
+ DB_TYPE_SQLITE /* SQLite type database (backend-sqlite.c).*/
};
/* Declaration of the backend handle. Each backend uses its own
* hidden handle structure with the only common thing being that the
* first field is the database_type to help with debugging. */
struct backend_handle_s;
typedef struct backend_handle_s *backend_handle_t;
+/* Private data for sqlite requests. */
+struct be_sqlite_local_s;
+typedef struct be_sqlite_local_s *be_sqlite_local_t;
+
+
/* Object to store backend specific database information per database
* handle. */
struct db_request_part_s
{
struct db_request_part_s *next;
/* Id of the backend instance this object pertains to. */
unsigned int backend_id;
- /* The handle used for a KBX backend or NULL. */
+ /* Local data for a KBX backend or NULL. */
KEYBOX_HANDLE kbx_hd;
+ /* Local data for a sqlite backend. */
+ be_sqlite_local_t besqlite;
+
/* For the CACHE backend the indices into the bloblist for each
* index type. */
struct {
unsigned int fpr;
unsigned int kid;
unsigned int grip;
unsigned int ubid;
} cache_seqno;
};
typedef struct db_request_part_s *db_request_part_t;
/* A database request handle. This keeps per session search
* information as well as a list of per-backend infos. */
struct db_request_s
{
unsigned int any_search:1; /* Any search has been done. */
unsigned int any_found:1; /* Any object has been found. */
unsigned int last_cached_valid:1; /* see below */
unsigned int last_cached_final:1; /* see below */
unsigned int last_cached_fprlen:8;/* see below */
db_request_part_t part;
/* Counter to track the next to be searched database index. */
unsigned int next_dbidx;
/* The last UBID found in the cache and the corresponding keyid and,
* if found via fpr, the fingerprint. For the LAST_CACHED_FPRLEN see
* above. The entry here is only valid if LAST_CACHED_VALID is set;
* if LAST_CACHED_FINAL is also set, this indicates that no further
* database searches are required. */
unsigned char last_cached_ubid[UBID_LEN];
u32 last_cached_kid_h;
u32 last_cached_kid_l;
unsigned char last_cached_fpr[32];
};
/*-- backend-support.c --*/
const char *strdbtype (enum database_types t);
unsigned int be_new_backend_id (void);
void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd);
void be_release_request (db_request_t req);
gpg_error_t be_find_request_part (backend_handle_t backend_hd,
db_request_t request,
db_request_part_t *r_part);
gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
enum pubkey_types pubkey_type,
const unsigned char *ubid);
+int be_is_x509_blob (const unsigned char *blob, size_t bloblen);
gpg_error_t be_ubid_from_blob (const void *blob, size_t bloblen,
enum pubkey_types *r_pktype, char *r_ubid);
/*-- backend-cache.c --*/
gpg_error_t be_cache_initialize (void);
gpg_error_t be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd);
void be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd);
gpg_error_t be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
void be_cache_mark_final (ctrl_t ctrl, db_request_t request);
void be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
const void *blob, unsigned int bloblen,
enum pubkey_types pubkey_type);
void be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
/*-- backend-kbx.c --*/
gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly);
void be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd);
void be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd);
gpg_error_t be_kbx_init_request_part (backend_handle_t backend_hd,
db_request_part_t part);
gpg_error_t be_kbx_search (ctrl_t ctrl, backend_handle_t hd,
db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
gpg_error_t be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid);
gpg_error_t be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen);
gpg_error_t be_kbx_update (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen);
gpg_error_t be_kbx_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request);
+/*-- backend-sqlite.c --*/
+gpg_error_t be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
+ const char *filename, int readonly);
+void be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd);
+
+gpg_error_t be_sqlite_init_local (backend_handle_t backend_hd,
+ db_request_part_t part);
+void be_sqlite_release_local (be_sqlite_local_t ctx);
+gpg_error_t be_sqlite_search (ctrl_t ctrl, backend_handle_t hd,
+ db_request_t request,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
+gpg_error_t be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, enum kbxd_store_modes mode,
+ enum pubkey_types pktype,
+ const unsigned char *ubid,
+ const void *blob, size_t bloblen);
+gpg_error_t be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, const unsigned char *ubid);
+
+
#endif /*KBX_BACKEND_H*/
diff --git a/kbx/frontend.c b/kbx/frontend.c
index 1793a05a0..508bbc072 100644
--- a/kbx/frontend.c
+++ b/kbx/frontend.c
@@ -1,485 +1,487 @@
/* frontend.c - Database fronend code for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0+
*/
#include
#include
#include
#include
#include
#include "keyboxd.h"
#include
#include "../common/i18n.h"
#include "../common/userids.h"
#include "backend.h"
#include "frontend.h"
/* An object to keep infos about the database. */
struct
{
enum database_types db_type;
backend_handle_t backend_handle;
} the_database;
/* Take a lock for reading the databases. */
static void
take_read_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Take a lock for reading and writing the databases. */
static void
take_read_write_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Release a lock. It is valid to call this even if no lock has been
* taken in which case this is a nop. */
static void
release_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Set the database to use. Depending on the FILENAME suffix we
* decide which one to use. This function must be called at daemon
* startup because it employs no locking. If FILENAME has no
* directory separator, the file is expected or created below
* "$GNUPGHOME/public-keys.d/". In READONLY mode the file must exists;
* otherwise it is created. */
gpg_error_t
kbxd_set_database (ctrl_t ctrl, const char *filename_arg, int readonly)
{
gpg_error_t err;
char *filename;
enum database_types db_type = 0;
backend_handle_t handle = NULL;
unsigned int n;
/* Do tilde expansion etc. */
if (strchr (filename_arg, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (filename_arg, '/') /* Windows also accepts a slash. */
#endif
)
filename = make_filename (filename_arg, NULL);
else
filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR,
filename_arg, NULL);
/* If this is the first call to the function and the request is not
* for the cache backend, add the cache backend so that it will
* always be the first to be queried. */
if (the_database.db_type)
{
log_error ("error: only one database allowed\n");
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
/* Init the cache. */
err = be_cache_initialize ();
if (err)
goto leave;
n = strlen (filename);
if (db_type)
; /* We already know it. */
else if (n > 4 && !strcmp (filename + n - 4, ".kbx"))
db_type = DB_TYPE_KBX;
+ else if (n > 3 && !strcmp (filename + n - 3, ".db"))
+ db_type = DB_TYPE_SQLITE;
else
{
log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (db_type)
{
case DB_TYPE_NONE: /* NOTREACHED */
break;
case DB_TYPE_CACHE:
err = be_cache_add_resource (ctrl, &handle);
break;
case DB_TYPE_KBX:
err = be_kbx_add_resource (ctrl, &handle, filename, readonly);
break;
- }
+
+ case DB_TYPE_SQLITE:
+ err = be_sqlite_add_resource (ctrl, &handle, filename, readonly);
+ break;
+ }
if (err)
goto leave;
the_database.db_type = db_type;
the_database.backend_handle = handle;
handle = NULL;
leave:
if (err)
{
log_error ("error setting database '%s': %s\n",
filename, gpg_strerror (err));
be_generic_release_backend (ctrl, handle);
}
xfree (filename);
return err;
}
/* Release all per session objects. */
void
kbxd_release_session_info (ctrl_t ctrl)
{
if (!ctrl)
return;
be_release_request (ctrl->opgp_req);
ctrl->opgp_req = NULL;
be_release_request (ctrl->x509_req);
ctrl->x509_req = NULL;
}
/* Search for the keys described by (DESC,NDESC) and return them to
- * the caller. If RESET is set, the search state is first reset. */
+ * the caller. If RESET is set, the search state is first reset.
+ * Only a reset guarantees that changed search description in DESC are
+ * considered. */
gpg_error_t
kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
int reset)
{
gpg_error_t err;
int i;
db_request_t request;
- int start_at_ubid = 0;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
if (DBG_LOOKUP)
{
log_debug ("%s: %u search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
/* char *t = keydb_search_desc_dump (&desc[i]); */
/* log_debug ("%s %d: %s\n", __func__, i, t); */
/* xfree (t); */
}
}
take_read_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* If requested do a reset. Using the reset flag is faster than
* letting the caller do a separate call for an intial reset. */
if (!desc || reset)
{
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = 0; /* Nothing to do. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, the_database.backend_handle,
request, NULL, 0);
break;
+ case DB_TYPE_SQLITE:
+ err = be_sqlite_search (ctrl, the_database.backend_handle,
+ request, NULL, 0);
+ break;
+
default:
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (err)
{
log_error ("error during the %ssearch reset: %s\n",
reset? "initial ":"", gpg_strerror (err));
goto leave;
}
request->any_search = 0;
request->any_found = 0;
request->next_dbidx = 0;
if (!desc) /* Reset only mode */
{
err = 0;
goto leave;
}
}
/* Divert to the backend for the actual search. */
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = be_cache_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
/* Expected error codes from the cache lookup are:
* 0 - found and returned via the cache
* GPG_ERR_NOT_FOUND - marked in the cache as not available
* GPG_ERR_EOF - cache miss. */
break;
case DB_TYPE_KBX:
- if (start_at_ubid)
- {
- /* We need to set the startpoint for the search. */
- err = be_kbx_seek (ctrl, the_database.backend_handle, request,
- request->last_cached_ubid);
- if (err)
- {
- log_debug ("%s: seeking %s to an UBID failed: %s\n", __func__,
- strdbtype (the_database.db_type), gpg_strerror (err));
- break;
- }
- }
err = be_kbx_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
- if (start_at_ubid && gpg_err_code (err) == GPG_ERR_EOF)
- be_cache_mark_final (ctrl, request);
+ break;
+
+ case DB_TYPE_SQLITE:
+ err = be_sqlite_search (ctrl, the_database.backend_handle, request,
+ desc, ndesc);
break;
default:
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s => %s\n", __func__,
strdbtype (the_database.db_type), gpg_strerror (err));
request->any_search = 1;
- start_at_ubid = 0;
if (!err)
{
request->any_found = 1;
}
else if (gpg_err_code (err) == GPG_ERR_EOF)
{
if (the_database.db_type == DB_TYPE_CACHE && request->last_cached_valid)
{
if (request->last_cached_final)
goto leave;
- start_at_ubid = 1;
}
request->next_dbidx++;
/* FIXME: We need to see which pubkey type we need to insert. */
be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave (%s)", __func__, err? "not found" : "found");
return err;
}
/* Store; that is insert or update the key (BLOB,BLOBLEN). MODE
* controls whether only updates or only inserts are allowed. */
gpg_error_t
kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen,
enum kbxd_store_modes mode)
{
gpg_error_t err;
db_request_t request;
char ubid[UBID_LEN];
enum pubkey_types pktype;
int insert = 0;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* Check whether to insert or update. */
err = be_ubid_from_blob (blob, bloblen, &pktype, ubid);
if (err)
goto leave;
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
+ if (!err)
+ ; /* Found - need to update. */
+ else if (gpg_err_code (err) == GPG_ERR_EOF)
+ insert = 1; /* Not found - need to insert. */
+ else
+ {
+ log_debug ("%s: searching fingerprint failed: %s\n",
+ __func__, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (insert)
+ {
+ if (mode == KBXD_STORE_UPDATE)
+ err = gpg_error (GPG_ERR_CONFLICT);
+ else
+ err = be_kbx_insert (ctrl, the_database.backend_handle, request,
+ pktype, blob, bloblen);
+ }
+ else /* Update. */
+ {
+ if (mode == KBXD_STORE_INSERT)
+ err = gpg_error (GPG_ERR_CONFLICT);
+ else
+ err = be_kbx_update (ctrl, the_database.backend_handle, request,
+ pktype, blob, bloblen);
+ }
+ }
+ else if (the_database.db_type == DB_TYPE_SQLITE)
+ {
+ err = be_sqlite_store (ctrl, the_database.backend_handle, request,
+ mode, pktype, ubid, blob, bloblen);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
- if (!err)
- ; /* Found - need to update. */
- else if (gpg_err_code (err) == GPG_ERR_EOF)
- insert = 1; /* Not found - need to insert. */
- else
- {
- log_debug ("%s: searching fingerprint failed: %s\n",
- __func__, gpg_strerror (err));
- goto leave;
- }
-
- if (insert)
- {
- if (mode == KBXD_STORE_UPDATE)
- err = gpg_error (GPG_ERR_CONFLICT);
- else if (the_database.db_type == DB_TYPE_KBX)
- err = be_kbx_insert (ctrl, the_database.backend_handle, request,
- pktype, blob, bloblen);
- else
- err = gpg_error (GPG_ERR_INTERNAL);
- }
- else /* Update. */
- {
- if (mode == KBXD_STORE_INSERT)
- err = gpg_error (GPG_ERR_CONFLICT);
- else if (the_database.db_type == DB_TYPE_KBX)
- err = be_kbx_update (ctrl, the_database.backend_handle, request,
- pktype, blob, bloblen);
- else
- err = gpg_error (GPG_ERR_INTERNAL);
- }
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
/* Delete; remove the blob identified by UBID. */
gpg_error_t
kbxd_delete (ctrl_t ctrl, const unsigned char *ubid)
{
gpg_error_t err;
db_request_t request;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
+ if (!err)
+ ; /* Found - we can delete. */
+ else if (gpg_err_code (err) == GPG_ERR_EOF)
+ {
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ goto leave;
+ }
+ else
+ {
+ log_debug ("%s: searching primary fingerprint failed: %s\n",
+ __func__, gpg_strerror (err));
+ goto leave;
+ }
+ err = be_kbx_delete (ctrl, the_database.backend_handle, request);
+ }
+ else if (the_database.db_type == DB_TYPE_SQLITE)
+ {
+ err = be_sqlite_delete (ctrl, the_database.backend_handle, request, ubid);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
- if (!err)
- ; /* Found - we can delete. */
- else if (gpg_err_code (err) == GPG_ERR_EOF)
- {
- err = gpg_error (GPG_ERR_NOT_FOUND);
- goto leave;
- }
- else
- {
- log_debug ("%s: searching primary fingerprint failed: %s\n",
- __func__, gpg_strerror (err));
- goto leave;
- }
-
- if (the_database.db_type == DB_TYPE_KBX)
- err = be_kbx_delete (ctrl, the_database.backend_handle, request);
- else
- err = gpg_error (GPG_ERR_INTERNAL);
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
diff --git a/kbx/frontend.h b/kbx/frontend.h
index 20565215a..d38d442f0 100644
--- a/kbx/frontend.h
+++ b/kbx/frontend.h
@@ -1,47 +1,39 @@
/* frontend.h - Definitions for the keyboxd frontend
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#ifndef KBX_FRONTEND_H
#define KBX_FRONTEND_H
#include "keybox-search-desc.h"
-enum kbxd_store_modes
- {
- KBXD_STORE_AUTO = 0, /* Update or insert. */
- KBXD_STORE_INSERT, /* Allow only inserts. */
- KBXD_STORE_UPDATE /* Allow only updates. */
- };
-
-
gpg_error_t kbxd_set_database (ctrl_t ctrl,
const char *filename_arg, int readonly);
void kbxd_release_session_info (ctrl_t ctrl);
gpg_error_t kbxd_search (ctrl_t ctrl,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
int reset);
gpg_error_t kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen,
enum kbxd_store_modes mode);
gpg_error_t kbxd_delete (ctrl_t ctrl, const unsigned char *ubid);
#endif /*KBX_FRONTEND_H*/
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
index 25a97e9a3..59e320467 100644
--- a/kbx/keyboxd.c
+++ b/kbx/keyboxd.c
@@ -1,1845 +1,1847 @@
/* keyboxd.c - The GnuPG Keybox Daemon
* Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2000-2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0+
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include
#else /*!HAVE_W32_SYSTEM*/
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#define GNUPG_COMMON_NEED_AFLOCAL
#include "keyboxd.h"
#include /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "frontend.h"
/* Urrgs: Put this into a separate header - but it needs assuan.h first. */
extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg);
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oServer,
oDaemon,
oBatch,
oFakedSystemTime,
oListenBacklog,
oDisableCheckOwnSocket,
oDummy
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms except for
* Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
#else
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
#endif
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flag to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* Name of the communication socket used for client requests. */
static char *socket_name;
/* We need to keep track of the server's nonces (these are dummies for
* POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Keep track of the current log file so that we can avoid updating
* the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how keyboxd was started, the homedir inotify watch may
* not be reliable. This flag is set if we assume that inotify works
* reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
* Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int cygwin,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void kbxd_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void kbxd_init_default_ctrl (ctrl_t ctrl);
static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd);
static void check_own_socket (void);
static int check_for_running_kbxd (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
* Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
* This function is expected to be called only once. GETFNC is
* expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
return xstrconcat (libname, " ", getfnc (NULL), NULL);
}
/* Return strings describing this program. The case values are
* described in common/argparse.c:strusage. The values here override
* the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "keyboxd (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: keyboxd [options] (-h for help)");
break;
case 41: p = _("Syntax: keyboxd [options] [command [args]]\n"
"Public key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. Note that we don't fail here, because it is
* important to keep keyboxd running even after re-reading the options
* due to a SIGHUP. */
static void
set_debug (void)
{
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. */
static void
remove_socket (char *name)
{
if (name && *name)
{
gnupg_remove (name);
*name = 0;
}
}
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
if (!inhibit_socket_removal)
remove_socket (socket_name);
}
/* Handle options which are allowed to be reset after program start.
* Return true when the current option in PARGS could be handled and
* false if not. As a special feature, passing a value of NULL for
* PARGS, resets the options to the default. REREAD should be set
* true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
disable_check_own_socket = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oLogFile:
if (!reread)
return 0; /* not handeld */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
}
/* The main entry point. */
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
char *logfile = NULL;
int gpgconf_list = 0;
int debug_wait = 0;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
* file descriptors and the signal mask. This info is required to
* do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
set_strusage (my_strusage);
log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor);
setup_libgcrypt_logging ();
gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL);
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes, a config file was given so we do not try the default
* one. Instead we read the config file when it is
* encountered during main parsing of the command line. */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
}
if (default_config)
configname = make_filename (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if (parse_debug)
log_info (_("Note: no default option file '%s'\n"), configname);
/* Save the default confif file name so that
* reread_configuration is able to test whether the
* config file has been created in the meantime. */
xfree (config_filename);
config_filename = configname;
configname = NULL;
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror (errno));
exit (2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname);
default_config = 0;
}
while (optfile_parse (configfp, configname, &configlineno, &pargs, opts))
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* Config files may not be nested (silently ignore them). */
if (!configfp)
{
xfree (configname);
configname = xstrdup (pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
if (config_filename != configname)
{
xfree (config_filename);
config_filename = configname;
}
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit (2);
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* keyboxd usually does not output any messages because it runs in
* the background. For log files it is acceptable to have messages
* always encoded in utf-8. We switch here to utf-8, so that
* commands like --help still give native messages. It is far
* easier to switch only once instead of for every message and it
* actually helps when more then one thread is active (avoids an
* extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list)
{
/* We have been called without any command and thus we merely
* check whether an instance of us is already running. We do
* this right here so that we don't clobber a logfile with this
* check but print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_kbxd (0);
kbxd_exit (0);
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 2)
kbxd_exit (0);
else if (gpgconf_list)
{
char *filename;
char *filename_esc;
/* List options and default values in the gpgconf format. */
filename = make_filename (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, "keyboxd", GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename);
xfree (filename_esc);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
kbxd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
- kbxd_set_database (ctrl, "pubring.kbx", 0);
+ /* kbxd_set_database (ctrl, "pubring.kbx", 0); */
+ kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular daemon mode. */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Create the sockets. */
socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 0, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
* we restore it. That is not strictly necessary but some
* programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
exit (0);
/*NOTREACHED*/
} /* End parent */
/*
* This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach)
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Because we don't support running a program on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
{
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
- kbxd_set_database (ctrl, "pubring.kbx", 0);
+ /* kbxd_set_database (ctrl, "pubring.kbx", 0); */
+ kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
log_info ("%s %s started\n", strusage(11), strusage(13) );
handle_connections (fd);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
kbxd_exit (int rc)
{
/* As usual we run our cleanup handler. */
cleanup ();
/* at this time a bit annoying */
if ((opt.debug & DBG_MEMSTAT_VALUE))
gcry_control (GCRYCTL_DUMP_MEMORY_STATS );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
* set once at startup and dispatches progress messages to the
* corresponding threads of ours. */
static void
kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
* structure usually identified by an argument named CTRL. This
* function is called immediately after allocating the control
* structure. Its purpose is to setup the default values for that
* structure. Note that some values may have already been set. */
static void
kbxd_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to kbxd_init_default_ctrl. */
static void
kbxd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
kbxd_release_session_info (ctrl);
ctrl->magic = 0xdeadbeef;
unregister_progress_cb ();
xfree (ctrl->lc_messages);
}
/* Reread parts of the configuration. Note, that this function is
* obviously not thread-safe and should only be called from the PTH
* signal handler.
*
* Fixme: Due to the way the argument parsing works, we create a
* memory leak here for all string type arguments. There is currently
* no clean way to tell whether the memory for the argument has been
* allocated or points into the process' original arguments. Unless
* we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!config_filename)
return; /* No config file. */
fp = fopen (config_filename, "r");
if (!fp)
{
log_info (_("option file '%s': %s\n"),
config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
finalize_rereadable_options ();
set_debug ();
}
/* Return the file name of the socket we are using for requests. */
const char *
get_kbxd_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_kbxd_active_connection_count (void)
{
return active_connections;
}
/* Create a name for the socket in the home directory as using
* STANDARD_NAME. We also check for valid characters as well as
* against a maximum allowed length for a Unix domain socket is done.
* The function terminates the process in case of an error. The
* function returns a pointer to an allocated string with the absolute
* name of the socket used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
kbxd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
* or terminates the process in case of an error. If CYGWIN is set a
* Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
kbxd_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
if (assuan_sock_set_sockaddr_un (name, addr, NULL))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* Our error code mapping on W32CE returns EEXIST thus we also test
for this. */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a keyboxd is already running. */
if (!check_for_running_kbxd (1))
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a keyboxd is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the public keys exists and
* create it if not. This function won't fail as it is only a
* convenience function and not strictly necessary. */
static void
create_public_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwxr-x"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwxr-x"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
* same as the default one. This way we avoid to create arbitrary
* directories when a non-default home directory is used. To cope
* with HOME, we compare only the suffix if we see that the default
* homedir does start with a tilde. We don't stop here in case of
* problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_public_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
* and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
* other places too. This is only used when build for W32. */
void
kbxd_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
reread_configuration ();
}
/* A helper function to handle SIGUSR2. */
static void
kbxd_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - no action\n");
/* Nothing to do right now. */
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
* its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
kbxd_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
break;
case SIGUSR2:
kbxd_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
kbxd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
kbxd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
static void *
do_start_connection_thread (ctrl_t ctrl)
{
static unsigned int last_session_id;
unsigned int session_id;
active_connections++;
kbxd_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* Connection handler loop. Wait for connection requests and spawn a
* thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread },
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n", strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
# ifdef HAVE_W32CE_SYSTEM
/* Use a dummy event. */
sigs = 0;
ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
# else
events[0] = INVALID_HANDLE_VALUE;
# endif
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new keyboxd instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if ((events_set & 1))
kbxd_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
* next timeout. */
continue;
}
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
* separate thread so that check_own_thread can be called from the
* timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
* server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
* another keyboxd process started after us has taken ownership of our
* socket, we would linger around without any real task. Thus we
* better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
return;
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether a keyboxd is available and running. Prints an
* error if not. If SILENT is true, no messages are printed. Returns
* 0 if the agent is running. */
static int
check_for_running_kbxd (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no keyboxd running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("keyboxd running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h
index edef8975c..d7bb97ab9 100644
--- a/kbx/keyboxd.h
+++ b/kbx/keyboxd.h
@@ -1,156 +1,164 @@
/* keyboxd.h - Global definitions for keyboxd
* Copyright (C) 2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#ifndef KEYBOXD_H
#define KEYBOXD_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX
#include
#include
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
/* A large struct name "opt" to keep global flags */
struct
{
unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
int verbose; /* Verbosity level */
int quiet; /* Be as quiet as possible */
int dry_run; /* Don't change any persistent data */
int batch; /* Batch mode */
/* True if we are running detached from the tty. */
int running_detached;
} opt;
/* Bit values for the --debug option. */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
#define DBG_CLOCK_VALUE 4096 /* debug timings (required build option). */
#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
/* Test macros for the debug option. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
/* Declaration of a database request object. This is used for all
* database operation (search, insert, update, delete). */
struct db_request_s;
typedef struct db_request_s *db_request_t;
/* Forward reference for local definitions in command.c. */
struct server_local_s;
#if SIZEOF_UNSIGNED_LONG == 8
# define SERVER_CONTROL_MAGIC 0x6b6579626f786420
#else
# define SERVER_CONTROL_MAGIC 0x6b627864
#endif
/* Collection of data per session (aka connection). */
struct server_control_s
{
unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */
int refcount; /* Count additional references to this object. */
/* Private data used to fire up the connection thread. We use this
* structure do avoid an extra allocation for only a few bytes while
* spawning a new connection thread. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Private data of the server (kbxserver.c). */
struct server_local_s *server_local;
/* Environment settings for the connection. */
char *lc_messages;
/* Miscellaneous info on the connection. */
unsigned long client_pid;
int client_uid;
/* Two database request objects used with a connection. They are
* auto-created as needed. */
db_request_t opgp_req;
db_request_t x509_req;
/* Flags for the current request. */
unsigned int no_data_return : 1; /* Used by SEARCH and NEXT. */
};
/* This is a special version of the usual _() gettext macro. It
* assumes a server connection control variable with the name "ctrl"
* and uses that to translate a string according to the locale set for
* the connection. The macro LunderscoreIMPL is used by i18n to
* actually define the inline function when needed. */
#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
#define L_(a) keyboxd_Lunderscore (ctrl, (a))
#define LunderscorePROTO \
static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \
const char *string) \
GNUPG_GCC_ATTR_FORMAT_ARG(2);
#define LunderscoreIMPL \
static inline const char * \
keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \
{ \
return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
/* */: gettext (string); \
}
#else
#define L_(a) (a)
#endif
+enum kbxd_store_modes
+ {
+ KBXD_STORE_AUTO = 0, /* Update or insert. */
+ KBXD_STORE_INSERT, /* Allow only inserts. */
+ KBXD_STORE_UPDATE /* Allow only updates. */
+ };
+
+
/*-- keyboxd.c --*/
void kbxd_exit (int rc) GPGRT_ATTR_NORETURN;
void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl);
const char *get_kbxd_socket_name (void);
int get_kbxd_active_connection_count (void);
void kbxd_sighup_action (void);
/*-- kbxserver.c --*/
gpg_error_t kbxd_write_data_line (ctrl_t ctrl,
const void *buffer_arg, size_t size);
void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int);
#endif /*KEYBOXD_H*/