diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c
index ff4f56773..25d4596af 100644
--- a/kbx/backend-kbx.c
+++ b/kbx/backend-kbx.c
@@ -1,427 +1,457 @@
/* 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.h b/kbx/backend.h
index c372d9071..3aa848714 100644
--- a/kbx/backend.h
+++ b/kbx/backend.h
@@ -1,149 +1,151 @@
/* 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). */
};
/* 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;
/* 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. */
KEYBOX_HANDLE kbx_hd;
/* 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);
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);
#endif /*KBX_BACKEND_H*/
diff --git a/kbx/frontend.c b/kbx/frontend.c
index de9373c8f..a8cbfc51e 100644
--- a/kbx/frontend.c
+++ b/kbx/frontend.c
@@ -1,471 +1,535 @@
/* 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 describe a single database. */
struct db_desc_s
{
enum database_types db_type;
backend_handle_t backend_handle;
};
typedef struct db_desc_s *db_desc_t;
/* The table of databases and the size of that table. */
static db_desc_t databases;
static unsigned int no_of_databases;
/* 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;
}
/* Add a new resource to the database. 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-v1.d/". In READONLY mode the file must
* exists; otherwise it is created. */
gpg_error_t
kbxd_add_resource (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, dbidx;
/* Do tilde expansion etc. */
if (!strcmp (filename_arg, "[cache]"))
{
filename = xstrdup (filename_arg);
db_type = DB_TYPE_CACHE;
}
else 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 (!no_of_databases && !db_type)
{
err = be_cache_initialize ();
/* err = kbxd_add_resource (ctrl, "[cache]", 0); */
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
{
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;
}
if (err)
goto leave;
/* All good, create an entry in the table. */
for (dbidx = 0; dbidx < no_of_databases; dbidx++)
if (!databases[dbidx].db_type)
break;
if (dbidx == no_of_databases)
{
/* No table yet or table is full. */
if (!databases)
{
/* Create first set of databases. Note that the initial
* type for all entries is DB_TYPE_NONE. */
dbidx = 4;
databases = xtrycalloc (dbidx, sizeof *databases);
if (!databases)
{
err = gpg_error_from_syserror ();
goto leave;
}
no_of_databases = dbidx;
dbidx = 0; /* Put into first slot. */
}
else
{
db_desc_t newdb;
dbidx = no_of_databases + (no_of_databases == 4? 12 : 16);
newdb = xtrycalloc (dbidx, sizeof *databases);
if (!databases)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (n=0; n < no_of_databases; n++)
newdb[n] = databases[n];
xfree (databases);
databases = newdb;
n = no_of_databases;
no_of_databases = dbidx;
dbidx = n; /* Put into first new slot. */
}
}
databases[dbidx].db_type = db_type;
databases[dbidx].backend_handle = handle;
handle = NULL;
leave:
if (err)
{
log_error ("error adding resource '%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. */
gpg_error_t
kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
int reset)
{
gpg_error_t err;
int i;
unsigned int dbidx;
db_desc_t db;
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 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)
{
for (dbidx=0; dbidx < no_of_databases; dbidx++)
{
db = databases + dbidx;
if (!db->db_type)
continue; /* Empty slot. */
switch (db->db_type)
{
case DB_TYPE_NONE: /* NOTREACHED */
break;
case DB_TYPE_CACHE:
err = 0; /* Nothing to do. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, db->backend_handle, request, NULL, 0);
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;
}
}
/* Move to the next non-empty slot. */
next_db:
for (dbidx=request->next_dbidx; (dbidx < no_of_databases
&& !databases[dbidx].db_type); dbidx++)
;
request->next_dbidx = dbidx;
if (!(dbidx < no_of_databases))
{
/* All databases have been searched. Put the non-found mark
* into the cache for all descriptors.
* 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;
}
db = databases + dbidx;
/* Divert to the backend for the actual search. */
switch (db->db_type)
{
case DB_TYPE_NONE:
/* NOTREACHED */
err = gpg_error (GPG_ERR_INTERNAL);
break;
case DB_TYPE_CACHE:
err = be_cache_search (ctrl, db->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, db->backend_handle, request,
request->last_cached_ubid);
if (err)
{
log_debug ("%s: seeking %s to an UBID failed: %s\n",
__func__, strdbtype (db->db_type), gpg_strerror (err));
break;
}
}
err = be_kbx_search (ctrl, db->backend_handle, request,
desc, ndesc);
if (start_at_ubid && gpg_err_code (err) == GPG_ERR_EOF)
be_cache_mark_final (ctrl, request);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (db %u of %u) => %s\n",
__func__, strdbtype (db->db_type), dbidx, no_of_databases,
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 (db->db_type == DB_TYPE_CACHE && request->last_cached_valid)
{
if (request->last_cached_final)
goto leave;
start_at_ubid = 1;
}
request->next_dbidx++;
goto next_db;
}
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;
unsigned int dbidx;
db_desc_t db;
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;
/* Check whether to insert or update. */
err = be_ubid_from_blob (blob, bloblen, &pktype, ubid);
if (err)
goto leave;
/* FIXME: We force the use of the KBX backend. */
for (dbidx=0; dbidx < no_of_databases; dbidx++)
if (databases[dbidx].db_type == DB_TYPE_KBX)
break;
if (!(dbidx < no_of_databases))
{
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
db = databases + dbidx;
err = be_kbx_seek (ctrl, db->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, db->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, db->backend_handle, request,
pktype, blob, bloblen);
}
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;
+ unsigned int dbidx;
+ db_desc_t db;
+
+ 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;
+
+ /* FIXME: We force the use of the KBX backend. */
+ for (dbidx=0; dbidx < no_of_databases; dbidx++)
+ if (databases[dbidx].db_type == DB_TYPE_KBX)
+ break;
+ if (!(dbidx < no_of_databases))
+ {
+ err = gpg_error (GPG_ERR_NOT_INITIALIZED);
+ goto leave;
+ }
+ db = databases + dbidx;
+
+ err = be_kbx_seek (ctrl, db->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, db->backend_handle, request);
+
+ 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 45a8dbdbd..d7647ba44 100644
--- a/kbx/frontend.h
+++ b/kbx/frontend.h
@@ -1,46 +1,47 @@
/* 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_add_resource (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/kbxserver.c b/kbx/kbxserver.c
index 511362004..5e603320a 100644
--- a/kbx/kbxserver.c
+++ b/kbx/kbxserver.c
@@ -1,834 +1,875 @@
/* kbxserver.c - Handle Assuan commands send to the 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
#include
#include
#include
#include
#include "keyboxd.h"
#include
#include "../common/i18n.h"
#include "../common/server-help.h"
#include "../common/userids.h"
#include "../common/asshelp.h"
#include "../common/host2net.h"
#include "frontend.h"
#define PARM_ERROR(t) assuan_set_error (ctx, \
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
/**/: gpg_error (e))
/* Control structure per connection. */
struct server_local_s
{
/* Data used to associate an Assuan context with local server data */
assuan_context_t assuan_ctx;
/* The session id (a counter). */
unsigned int session_id;
/* If this flag is set to true this process will be terminated after
* the end of this session. */
int stopme;
/* If the first both flags are set the assuan logging of data lines
* is suppressed. The count variable is used to show the number of
* non-logged bytes. */
size_t inhibit_data_logging_count;
unsigned int inhibit_data_logging : 1;
unsigned int inhibit_data_logging_now : 1;
/* This flag is set if the last search command was called with --more. */
unsigned int search_expecting_more : 1;
/* This flag is set if the last search command was successful. */
unsigned int search_any_found : 1;
/* The first is the current search description as parsed by the
* cmd_search. If more than one pattern is required, cmd_search
* also allocates and sets multi_search_desc and
* multi_search_desc_len. If a search description has ever been
* allocated the allocated size is stored at
* multi_search_desc_size. */
KEYBOX_SEARCH_DESC search_desc;
KEYBOX_SEARCH_DESC *multi_search_desc;
unsigned int multi_search_desc_size;
unsigned int multi_search_desc_len;
/* If not NULL write output to this stream instead of using D lines. */
estream_t outstream;
};
/* Return the assuan contxt from the local server info in CTRL. */
static assuan_context_t
get_assuan_ctx_from_ctrl (ctrl_t ctrl)
{
if (!ctrl || !ctrl->server_local)
return NULL;
return ctrl->server_local->assuan_ctx;
}
/* If OUTPUT has been used prepare the output FD for use. This needs
* to be called by all functions which will in any way use
* kbxd_write_data_line later. Whether the output goes to the output
* stream is decided by this function. */
static gpg_error_t
prepare_outstream (ctrl_t ctrl)
{
int fd;
log_assert (ctrl && ctrl->server_local);
if (ctrl->server_local->outstream)
return 0; /* Already enabled. */
fd = translate_sys2libc_fd
(assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1);
if (fd == -1)
return 0; /* No Output command active. */
ctrl->server_local->outstream = es_fdopen_nc (fd, "w");
if (!ctrl->server_local->outstream)
return gpg_err_code_from_syserror ();
return 0;
}
/* The usual writen function; here with diagnostic output. */
static gpg_error_t
kbxd_writen (estream_t fp, const void *buffer, size_t length)
{
gpg_error_t err;
size_t nwritten;
if (es_write (fp, buffer, length, &nwritten))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
else if (length != nwritten)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error writing OUTPUT: %s\n", "short write");
}
else
err = 0;
return err;
}
/* A wrapper around assuan_send_data which makes debugging the output
* in verbose mode easier. It also takes CTRL as argument. */
gpg_error_t
kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
gpg_error_t err;
if (!ctx) /* Oops - no assuan context. */
return gpg_error (GPG_ERR_NOT_PROCESSED);
/* Write toa file descriptor if enabled. */
if (ctrl && ctrl->server_local && ctrl->server_local->outstream)
{
unsigned char lenbuf[4];
ulongtobuf (lenbuf, size);
err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4);
if (!err)
err = kbxd_writen (ctrl->server_local->outstream, buffer, size);
if (!err && es_fflush (ctrl->server_local->outstream))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
goto leave;
}
/* If we do not want logging, enable it here. */
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
ctrl->server_local->inhibit_data_logging_now = 1;
if (0 && opt.verbose && buffer && size)
{
/* Ease reading of output by limiting the line length. */
size_t n, nbytes;
nbytes = size;
do
{
n = nbytes > 64? 64 : nbytes;
err = assuan_send_data (ctx, buffer, n);
if (err)
{
gpg_err_set_errno (EIO);
goto leave;
}
buffer += n;
nbytes -= n;
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
{
gpg_err_set_errno (EIO);
goto leave;
}
}
while (nbytes);
}
else
{
err = assuan_send_data (ctx, buffer, size);
if (err)
{
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
goto leave;
}
}
leave:
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
{
ctrl->server_local->inhibit_data_logging_count += size;
ctrl->server_local->inhibit_data_logging_now = 0;
}
return err;
}
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err && opt.verbose)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Handle OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static const char hlp_search[] =
"SEARCH [--no-data] [[--more] PATTERN]\n"
"\n"
"Search for the keys identified by PATTERN. With --more more\n"
"patterns to be used for the search are expected with the next\n"
"command. With --no-data only the search status is returned but\n"
"not the actual data. See also \"NEXT\".";
static gpg_error_t
cmd_search (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_more, opt_no_data;
gpg_error_t err;
unsigned int n, k;
opt_no_data = has_option (line, "--no-data");
opt_more = has_option (line, "--more");
line = skip_options (line);
ctrl->server_local->search_any_found = 0;
if (!*line)
{
if (opt_more)
{
err = set_error (GPG_ERR_INV_ARG, "--more but no pattern");
goto leave;
}
else if (!*line && ctrl->server_local->search_expecting_more)
{
/* It would be too surprising to first set a pattern but
* finally add no pattern to search the entire DB. */
err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern");
goto leave;
}
else /* No pattern - return the first item. */
{
memset (&ctrl->server_local->search_desc, 0,
sizeof ctrl->server_local->search_desc);
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST;
}
}
else
{
err = classify_user_id (line, &ctrl->server_local->search_desc, 0);
if (err)
goto leave;
}
if (opt_more || ctrl->server_local->search_expecting_more)
{
/* More pattern are expected - store the current one and return
* success. */
if (!ctrl->server_local->multi_search_desc_size)
{
n = 10;
ctrl->server_local->multi_search_desc
= xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc);
if (!ctrl->server_local->multi_search_desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->multi_search_desc_size = n;
}
if (ctrl->server_local->multi_search_desc_len
== ctrl->server_local->multi_search_desc_size)
{
KEYBOX_SEARCH_DESC *desc;
n = ctrl->server_local->multi_search_desc_size + 10;
desc = xtrycalloc (n, sizeof *desc);
if (!desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (k=0; k < ctrl->server_local->multi_search_desc_size; k++)
desc[k] = ctrl->server_local->multi_search_desc[k];
xfree (ctrl->server_local->multi_search_desc);
ctrl->server_local->multi_search_desc = desc;
ctrl->server_local->multi_search_desc_size = n;
}
/* Actually store. */
ctrl->server_local->multi_search_desc
[ctrl->server_local->multi_search_desc_len++]
= ctrl->server_local->search_desc;
if (opt_more)
{
/* We need to be called aagain with more pattern. */
ctrl->server_local->search_expecting_more = 1;
goto leave;
}
ctrl->server_local->search_expecting_more = 0;
/* Continue with the actual search. */
}
else
ctrl->server_local->multi_search_desc_len = 0;
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 1);
else
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1);
if (err)
goto leave;
/* Set a flag for use by NEXT. */
ctrl->server_local->search_any_found = 1;
leave:
if (err)
ctrl->server_local->multi_search_desc_len = 0;
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_next[] =
"NEXT [--no-data]\n"
"\n"
"Get the next search result from a previus search.";
static gpg_error_t
cmd_next (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_no_data;
gpg_error_t err;
opt_no_data = has_option (line, "--no-data");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (!ctrl->server_local->search_any_found)
{
err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH");
goto leave;
}
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
{
/* The next condition should never be tru but we better handle
* the first/next transition anyway. */
if (ctrl->server_local->multi_search_desc[0].mode
== KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 0);
}
else
{
/* We need to do the transition from first to next here. */
if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0);
}
if (err)
goto leave;
leave:
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_store[] =
"STORE [--update|--insert]\n"
"\n"
"Insert a key into the database. Whether to insert or update\n"
"the key is decided by looking at the primary key's fingerprint.\n"
"With option --update the key must already exist.\n"
"With option --insert the key must not already exist.\n"
"The actual key material is requested by this function using\n"
" INQUIRE BLOB";
static gpg_error_t
cmd_store (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_update, opt_insert;
enum kbxd_store_modes mode;
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
opt_update = has_option (line, "--update");
opt_insert = has_option (line, "--insert");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (opt_update && !opt_insert)
mode = KBXD_STORE_UPDATE;
else if (!opt_update && opt_insert)
mode = KBXD_STORE_INSERT;
else
mode = KBXD_STORE_AUTO;
/* Ask for the key material. */
err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data received. */
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = kbxd_store (ctrl, value, valuelen, mode);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
+static const char hlp_delete[] =
+ "DELETE \n"
+ "\n"
+ "Delete a key into the database. The UBID identifies the key.\n";
+static gpg_error_t
+cmd_delete (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ int n;
+ unsigned char ubid[UBID_LEN];
+
+ line = skip_options (line);
+ if (!*line)
+ {
+ err = set_error (GPG_ERR_INV_ARG, "UBID missing");
+ goto leave;
+ }
+
+ /* Skip an optional UBID identifier character. */
+ if (*line == '^' && line[1])
+ line++;
+ if ((n=hex2bin (line, ubid, UBID_LEN)) < 0)
+ {
+ err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID");
+ goto leave;
+ }
+ if (line[n])
+ {
+ err = set_error (GPG_ERR_INV_ARG, "garbage after UBID");
+ goto leave;
+ }
+
+ err = kbxd_delete (ctrl, ubid);
+
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
static const char hlp_getinfo[] =
"GETINFO \n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
"version - Return the version of the program.\n"
"pid - Return the process id of the server.\n"
"socket_name - Return the name of the socket.\n"
"session_id - Return the current session_id.\n"
"getenv NAME - Return value of envvar NAME\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char numbuf[50];
if (!strcmp (line, "version"))
{
const char *s = VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_kbxd_socket_name ();
if (!s)
s = "[none]";
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "session_id"))
{
snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
err = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
err = assuan_send_data (ctx, s, strlen (s));
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
static const char hlp_killkeyboxd[] =
"KILLKEYBOXD\n"
"\n"
"This command allows a user - given sufficient permissions -\n"
"to kill this keyboxd process.\n";
static gpg_error_t
cmd_killkeyboxd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return gpg_error (GPG_ERR_EOF);
}
static const char hlp_reloadkeyboxd[] =
"RELOADKEYBOXD\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadkeyboxd (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
kbxd_sighup_action ();
return 0;
}
static const char hlp_output[] =
"OUTPUT FD[=]\n"
"\n"
"Set the file descriptor to write the output data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used.";
/* Tell the assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SEARCH", cmd_search, hlp_search },
{ "NEXT", cmd_next, hlp_next },
{ "STORE", cmd_store, hlp_store },
+ { "DELETE", cmd_delete, hlp_delete },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "OUTPUT", NULL, hlp_output },
{ "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
{ "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd },
{ NULL, NULL }
};
int i, j, rc;
for (i=j=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Note that we do not reset the list of configured keyservers. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
(void)ctrl;
return 0;
}
/* This function is called by our assuan log handler to test whether a
* log message shall really be printed. The function must return
* false to inhibit the logging of MSG. CAT gives the requested log
* category. MSG might be NULL. */
int
kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)cat;
(void)msg;
if (!ctrl || !ctrl->server_local)
return 1; /* Can't decide - allow logging. */
if (!ctrl->server_local->inhibit_data_logging)
return 1; /* Not requested - allow logging. */
/* Disallow logging if *_now is true. */
return !ctrl->server_local->inhibit_data_logging_now;
}
/* Startup the server and run the main command loop. With FD = -1,
* use stdin/stdout. SESSION_ID is either 0 or a unique number
* identifying a session. */
void
kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id)
{
static const char hello[] = "Keyboxd " VERSION " at your service";
static char *hello_line;
int rc;
assuan_context_t ctx;
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
log_error (_("can't allocate control structure: %s\n"),
gpg_strerror (gpg_error_from_syserror ()));
xfree (ctrl);
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error (_("failed to allocate assuan context: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
if (fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd,
(ASSUAN_SOCKET_SERVER_ACCEPTED
|ASSUAN_SOCKET_SERVER_FDPASSING));
}
if (rc)
{
assuan_release (ctx);
log_error (_("failed to initialize the server: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error (_("failed to the register commands with Assuan: %s\n"),
gpg_strerror(rc));
kbxd_exit (2);
}
if (!hello_line)
{
hello_line = xtryasprintf
("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
/*opt.config_filename? opt.config_filename :*/ "[none]",
hello);
}
ctrl->server_local->assuan_ctx = ctx;
assuan_set_pointer (ctx, ctrl);
assuan_set_hello_line (ctx, hello_line);
assuan_register_option_handler (ctx, option_handler);
assuan_register_reset_notify (ctx, reset_notify);
ctrl->server_local->session_id = session_id;
/* The next call enable the use of status_printf. */
set_assuan_context_func (get_assuan_ctx_from_ctrl);
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
break;
if (rc)
{
log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
break;
}
#ifndef HAVE_W32_SYSTEM
if (opt.verbose)
{
assuan_peercred_t peercred;
if (!assuan_get_peercred (ctx, &peercred))
log_info ("connection from process %ld (%ld:%ld)\n",
(long)peercred->pid, (long)peercred->uid,
(long)peercred->gid);
}
#endif
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
assuan_close_output_fd (ctx);
set_assuan_context_func (NULL);
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);
if (ctrl->server_local->stopme)
kbxd_exit (0);
if (ctrl->refcount)
log_error ("oops: connection control structure still referenced (%d)\n",
ctrl->refcount);
else
{
xfree (ctrl->server_local->multi_search_desc);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
}