diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index 851f2dadf..438d300b0 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -1,328 +1,328 @@ /* 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 0; + 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 blobid[20]; err = keybox_get_data (part->kbx_hd, &buffer, &buflen, &pubkey_type); if (err) goto leave; gcry_md_hash_buffer (GCRY_MD_SHA1, blobid, buffer, buflen); err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, blobid); if (!err) be_cache_pubkey (ctrl, blobid, buffer, buflen, pubkey_type); xfree (buffer); } leave: return err; } /* Seek in the keybox to the given UBID. 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, 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, 20); /* 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; } diff --git a/kbx/frontend.c b/kbx/frontend.c index 806ff27db..6e0cbcb11 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -1,381 +1,385 @@ /* 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 = 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) - be_generic_release_backend (ctrl, handle); + { + 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; }