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; } }