diff options
author | Werner Koch <wk@gnupg.org> | 2019-09-27 15:44:23 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2019-09-27 15:44:23 +0200 |
commit | 9698761933f7ade732178b45df2a8763e5801763 (patch) | |
tree | 5454a013e219a64dfd0feaf701285a65b7cfa431 /kbx | |
parent | build: Build gpg-pair-tool only when there is newer libgcrypt. (diff) | |
parent | kbx: Fix error code return in keyboxd. (diff) | |
download | gnupg2-9698761933f7ade732178b45df2a8763e5801763.tar.xz gnupg2-9698761933f7ade732178b45df2a8763e5801763.zip |
Merge branch 'switch-to-gpgk' into master
--
Resolved Conflicts:
* common/asshelp.c: Keep the new code in master for spawing under
Windows.
* g10/Makefile.am: Keep all new file.
* g10/photoid.c: Pass CTRL to pct_expando.
Signed-off-by: Werner Koch <wk@gnupg.org>
Diffstat (limited to 'kbx')
-rw-r--r-- | kbx/Makefile.am | 35 | ||||
-rw-r--r-- | kbx/backend-cache.c | 1190 | ||||
-rw-r--r-- | kbx/backend-kbx.c | 328 | ||||
-rw-r--r-- | kbx/backend-support.c | 171 | ||||
-rw-r--r-- | kbx/backend.h | 140 | ||||
-rw-r--r-- | kbx/frontend.c | 385 | ||||
-rw-r--r-- | kbx/frontend.h | 36 | ||||
-rw-r--r-- | kbx/kbxserver.c | 775 | ||||
-rw-r--r-- | kbx/keybox-blob.c | 21 | ||||
-rw-r--r-- | kbx/keybox-dump.c | 46 | ||||
-rw-r--r-- | kbx/keybox-file.c | 18 | ||||
-rw-r--r-- | kbx/keybox-search-desc.h | 11 | ||||
-rw-r--r-- | kbx/keybox-search.c | 96 | ||||
-rw-r--r-- | kbx/keybox-update.c | 4 | ||||
-rw-r--r-- | kbx/keybox.h | 6 | ||||
-rw-r--r-- | kbx/keyboxd-w32info.rc | 50 | ||||
-rw-r--r-- | kbx/keyboxd.c | 1845 | ||||
-rw-r--r-- | kbx/keyboxd.h | 156 |
18 files changed, 5295 insertions, 18 deletions
diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 8fca24afb..42c3c4be8 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -18,16 +18,20 @@ ## Process this file with automake to produce Makefile.in -EXTRA_DIST = mkerrors +EXTRA_DIST = mkerrors keyboxd-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am +if HAVE_W32_SYSTEM +resource_objs += keyboxd-w32info.o +endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) noinst_LIBRARIES = libkeybox.a libkeybox509.a bin_PROGRAMS = kbxutil +libexec_PROGRAMS = keyboxd if HAVE_W32CE_SYSTEM extra_libs = $(LIBASSUAN_LIBS) @@ -35,6 +39,9 @@ else extra_libs = endif +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) + common_sources = \ keybox.h keybox-defs.h keybox-search-desc.h \ keybox-util.c \ @@ -59,9 +66,31 @@ libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 # to do it this way. kbxutil_SOURCES = kbxutil.c $(common_sources) kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 -kbxutil_LDADD = ../common/libcommon.a \ +kbxutil_LDADD = $(common_libs) \ $(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \ $(NETLIBS) -$(PROGRAMS) : ../common/libcommon.a + +keyboxd_SOURCES = \ + keyboxd.c keyboxd.h \ + kbxserver.c \ + frontend.c frontend.h \ + backend.h backend-support.c \ + backend-cache.c \ + backend-kbx.c \ + $(common_sources) + +keyboxd_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \ + $(INCICONV) +keyboxd_LDADD = $(commonpth_libs) \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(resource_objs) +keyboxd_LDFLAGS = $(extra_bin_ldflags) +keyboxd_DEPENDENCIES = $(resource_objs) + + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(commonpth_libs) diff --git a/kbx/backend-cache.c b/kbx/backend-cache.c new file mode 100644 index 000000000..10a6f6bd9 --- /dev/null +++ b/kbx/backend-cache.c @@ -0,0 +1,1190 @@ +/* backend-cache.c - Cache 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* + * This cache backend is designed to be queried first and to deliver + * cached items (which may also be not-found). A set a maintenance + * functions is used used by the frontend to fill the cache. + * FIXME: Support x.509 + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +#include "keyboxd.h" +#include "../common/i18n.h" +#include "../common/host2net.h" +#include "backend.h" +#include "keybox-defs.h" + + +/* Standard values for the number of buckets and the threshold we use + * to flush items. */ +#define NO_OF_KEY_ITEM_BUCKETS 383 +#define KEY_ITEMS_PER_BUCKET_THRESHOLD 40 +#define NO_OF_BLOB_BUCKETS 383 +#define BLOBS_PER_BUCKET_THRESHOLD 20 + + +/* Our definition of the backend handle. */ +struct backend_handle_s +{ + enum database_types db_type; /* Always DB_TYPE_CACHE. */ + unsigned int backend_id; /* Always the id the backend. */ +}; + + +/* The object holding a blob. */ +typedef struct blob_s +{ + struct blob_s *next; + enum pubkey_types pktype; + unsigned int refcount; + unsigned int usecount; + unsigned int datalen; + unsigned char *data; /* The actual data of length DATALEN. */ + unsigned char ubid[20]; +} *blob_t; + + +static blob_t *blob_table; /* Hash table with the blobs. */ +static size_t blob_table_size; /* Number of allocated buckets. */ +static unsigned int blob_table_threshold; /* Max. # of items per bucket. */ +static unsigned int blob_table_added; /* Number of items added. */ +static unsigned int blob_table_dropped; /* Number of items dropped. */ +static blob_t blob_attic; /* List of freed blobs. */ + + +/* A list item to blob data. This is so that a next operation on a + * cached key item can actually work. Things are complicated because + * we do not want to force caching all object before we get a next + * request from the client. To accomplish this we keep a flag + * indicating that the search needs to continue instead of delivering + * the previous item from the cache. */ +typedef struct bloblist_s +{ + struct bloblist_s *next; + unsigned int final_kid:1; /* The final blob for KID searches. */ + unsigned int final_fpr:1; /* The final blob for FPR searches. */ + unsigned int ubid_valid:1; /* The blobid below is valid. */ + unsigned int subkey:1; /* The entry is for a subkey. */ + unsigned int fprlen:8; /* The length of the fingerprint or 0. */ + char fpr[32]; /* The buffer for the fingerprint. */ + unsigned char ubid[20]; /* The Unique-Blob-ID of the blob. */ +} *bloblist_t; + +static bloblist_t bloblist_attic; /* List of freed items. */ + +/* The cache object. For indexing we could use the fingerprint + * directly as a hash value. However, we use the keyid instead + * because the keyid is used by OpenPGP in encrypted packets and older + * signatures to identify a key. Since v4 OpenPGP keys the keyid is + * anyway a part of the fingerprint so it quickly extracted from a + * fingerprint. Note that v3 keys are not supported by gpg. + * FIXME: Add support for X.509. + */ +typedef struct key_item_s +{ + struct key_item_s *next; + bloblist_t blist; /* List of blobs or NULL for not-found. */ + unsigned int usecount; + unsigned int refcount; /* Reference counter for this item. */ + u32 kid_h; /* Upper 4 bytes of the keyid. */ + u32 kid_l; /* Lower 4 bytes of the keyid. */ +} *key_item_t; + +static key_item_t *key_table; /* Hash table with the keys. */ +static size_t key_table_size; /* Number of allocated buckets. */ +static unsigned int key_table_threshold; /* Max. # of items per bucket. */ +static unsigned int key_table_added; /* Number of items added. */ +static unsigned int key_table_dropped; /* Number of items dropped. */ +static key_item_t key_item_attic; /* List of freed items. */ + + + + +/* The hash function we use for the key_table. Must not call a system + * function. */ +static inline unsigned int +blob_table_hasher (const unsigned char *ubid) +{ + return (ubid[0] << 16 | ubid[1]) % blob_table_size; +} + + +/* Runtime allocation of the key table. This allows us to eventually + * add an option to control the size. */ +static gpg_error_t +blob_table_init (void) +{ + if (blob_table) + return 0; + blob_table_size = NO_OF_BLOB_BUCKETS; + blob_table_threshold = BLOBS_PER_BUCKET_THRESHOLD; + blob_table = xtrycalloc (blob_table_size, sizeof *blob_table); + if (!blob_table) + return gpg_error_from_syserror (); + return 0; +} + +/* Free a blob. This is done by moving it to the attic list. */ +static void +blob_unref (blob_t blob) +{ + void *p; + + if (!blob) + return; + log_assert (blob->refcount); + if (!--blob->refcount) + { + p = blob->data; + blob->data = NULL; + blob->next = blob_attic; + blob_attic = blob; + xfree (p); + } +} + + +/* Given the hash value and the ubid, find the blob in the bucket. + * Returns NULL if not found or the blob item if found. Always + * returns the the number of items searched, which is in the case of a + * not-found the length of the chain. */ +static blob_t +find_blob (unsigned int hash, const unsigned char *ubid, + unsigned int *r_count) +{ + blob_t b; + unsigned int count = 0; + + for (b = blob_table[hash]; b; b = b->next, count++) + if (!memcmp (b->ubid, ubid, 20)) + break; + if (r_count) + *r_count = count; + return b; +} + + +/* Helper for the qsort in key_table_put. */ +static int +compare_blobs (const void *arg_a, const void *arg_b) +{ + const blob_t a = *(const blob_t *)arg_a; + const blob_t b = *(const blob_t *)arg_b; + + /* Reverse sort on the usecount. */ + if (a->usecount > b->usecount) + return -1; + else if (a->usecount == b->usecount) + return 0; + else + return 1; +} + + +/* Put the blob (BLOBDATA, BLOBDATALEN) into the cache using UBID as + * the index. If it is already in the cache nothing happens. */ +static void +blob_table_put (const unsigned char *ubid, enum pubkey_types pktype, + const void *blobdata, unsigned int blobdatalen) +{ + unsigned int hash; + blob_t b; + unsigned int count, n; + void *blobdatacopy = NULL; + + hash = blob_table_hasher (ubid); + find_again: + b = find_blob (hash, ubid, &count); + if (b) + { + xfree (blobdatacopy); + return; /* Already got this blob. */ + } + + /* Create a copy of the blob if not yet done. */ + if (!blobdatacopy) + { + blobdatacopy = xtrymalloc (blobdatalen); + if (!blobdatacopy) + { + log_info ("Note: malloc failed while copying blob to the cache: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; /* Out of core - ignore. */ + } + memcpy (blobdatacopy, blobdata, blobdatalen); + } + + /* If the bucket is full remove a couple of items. */ + if (count >= blob_table_threshold) + { + blob_t list_head, *list_tailp, b_next; + blob_t *array; + int narray, idx; + + /* Unlink from the global list so that other threads don't + * disturb us. If another thread adds or removes something only + * one will be the winner. Bad luck for the dropped cache items + * but after all it is just a cache. */ + list_head = blob_table[hash]; + blob_table[hash] = NULL; + + /* Put all items into an array for sorting. */ + array = xtrycalloc (count, sizeof *array); + if (!array) + { + /* That's bad; give up all items of the bucket. */ + log_info ("Note: malloc failed while purging blobs from the " + "cache: %s\n", gpg_strerror (gpg_error_from_syserror ())); + goto leave_drop; + } + narray = 0; + for (b = list_head; b; b = b_next) + { + b_next = b->next; + array[narray++] = b; + b->next = NULL; + } + log_assert (narray == count); + + /* Sort the array and put half of it onto a new list. */ + qsort (array, narray, sizeof *array, compare_blobs); + list_head = NULL; + list_tailp = &list_head; + for (idx=0; idx < narray/2; idx++) + { + *list_tailp = array[idx]; + list_tailp = &array[idx]->next; + } + + /* Put the new list into the bucket. */ + b = blob_table[hash]; + blob_table[hash] = list_head; + list_head = b; + + /* Free the remaining items and the array. */ + for (; idx < narray; idx++) + { + blob_unref (array[idx]); + blob_table_dropped++; + } + xfree (array); + + leave_drop: + /* Free any items added in the meantime by other threads. This + * is also used in case of a malloc problem (which won't update + * the counters, though). */ + for ( ; list_head; list_head = b_next) + { + b_next = list_head->next; + blob_unref (list_head); + } + } + + /* Add an item to the bucket. We allocate a whole block of items + * for cache performance reasons. */ + if (!blob_attic) + { + blob_t b_block; + int b_blocksize = 256; + + b_block = xtrymalloc (b_blocksize * sizeof *b_block); + if (!b_block) + { + log_info ("Note: malloc failed while adding blob to the cache: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + xfree (blobdatacopy); + return; /* Out of core - ignore. */ + } + for (n = 0; n < b_blocksize; n++) + { + b = b_block + n; + b->next = blob_attic; + blob_attic = b; + } + + /* During the malloc another thread might have changed the + * bucket. Thus we need to start over. */ + goto find_again; + } + + /* We now know that there is an item in the attic. Put it into the + * chain. Note that we may not use any system call here. */ + b = blob_attic; + blob_attic = b->next; + b->next = NULL; + b->pktype = pktype; + b->data = blobdatacopy; + b->datalen = blobdatalen; + memcpy (b->ubid, ubid, 20); + b->usecount = 1; + b->refcount = 1; + b->next = blob_table[hash]; + blob_table[hash] = b; + blob_table_added++; +} + + +/* Given the UBID return a cached blob item. The caller must + * release that item using blob_unref. */ +static blob_t +blob_table_get (const unsigned char *ubid) +{ + unsigned int hash; + blob_t b; + + hash = blob_table_hasher (ubid); + b = find_blob (hash, ubid, NULL); + if (b) + { + b->usecount++; + b->refcount++; + return b; /* Found */ + } + + return NULL; +} + + + +/* The hash function we use for the key_table. Must not call a system + * function. */ +static inline unsigned int +key_table_hasher (u32 kid_l) +{ + return kid_l % key_table_size; +} + + +/* Runtime allocation of the key table. This allows us to eventually + * add an option to control the size. */ +static gpg_error_t +key_table_init (void) +{ + if (key_table) + return 0; + key_table_size = NO_OF_KEY_ITEM_BUCKETS; + key_table_threshold = KEY_ITEMS_PER_BUCKET_THRESHOLD; + key_table = xtrycalloc (key_table_size, sizeof *key_table); + if (!key_table) + return gpg_error_from_syserror (); + return 0; +} + +/* Free a key_item. This is done by moving it to the attic list. */ +static void +key_item_unref (key_item_t ki) +{ + bloblist_t bl, bl2; + + if (!ki) + return; + log_assert (ki->refcount); + if (!--ki->refcount) + { + bl = ki->blist; + ki->blist = NULL; + ki->next = key_item_attic; + key_item_attic = ki; + + if (bl) + { + for (bl2 = bl; bl2->next; bl2 = bl2->next) + ; + bl2->next = bloblist_attic; + bloblist_attic = bl; + } + } +} + + +/* Given the hash value and the search info, find the key item in the + * bucket. Return NULL if not found or the key item if fount. Always + * returns the the number of items searched, which is in the case of a + * not-found the length of the chain. Note that FPR may only be NULL + * if FPRLEN is 0. */ +static key_item_t +find_in_chain (unsigned int hash, u32 kid_h, u32 kid_l, + unsigned int *r_count) +{ + key_item_t ki = key_table[hash]; + unsigned int count = 0; + + for (; ki; ki = ki->next, count++) + if (ki->kid_h == kid_h && ki->kid_l == kid_l) + break; + if (r_count) + *r_count = count; + return ki; +} + + +/* Helper for the qsort in key_table_put. */ +static int +compare_key_items (const void *arg_a, const void *arg_b) +{ + const key_item_t a = *(const key_item_t *)arg_a; + const key_item_t b = *(const key_item_t *)arg_b; + + /* Reverse sort on the usecount. */ + if (a->usecount > b->usecount) + return -1; + else if (a->usecount == b->usecount) + return 0; + else + return 1; +} + + +/* Allocate new key items. They are put to the attic so that the + * caller can take them from there. On allocation failure a note + * is printed and an error returned. */ +static gpg_error_t +alloc_more_key_items (void) +{ + gpg_error_t err; + key_item_t kiblock, ki; + int kiblocksize = 256; + unsigned int n; + + kiblock = xtrymalloc (kiblocksize * sizeof *kiblock); + if (!kiblock) + { + err = gpg_error_from_syserror (); + log_info ("Note: malloc failed while adding to the cache: %s\n", + gpg_strerror (err)); + return err; + } + for (n = 0; n < kiblocksize; n++) + { + ki = kiblock + n; + ki->next = key_item_attic; + key_item_attic = ki; + } + return 0; +} + + +/* Allocate new bloblist items. They are put to the attic so that the + * caller can take them from there. On allocation failure a note is + * printed and an error returned. */ +static gpg_error_t +alloc_more_bloblist_items (void) +{ + gpg_error_t err; + bloblist_t bl; + bloblist_t blistblock; + int blistblocksize = 256; + unsigned int n; + + blistblock = xtrymalloc (blistblocksize * sizeof *blistblock); + if (!blistblock) + { + err = gpg_error_from_syserror (); + log_info ("Note: malloc failed while adding to the cache: %s\n", + gpg_strerror (err)); + return err; + } + for (n = 0; n < blistblocksize; n++) + { + bl = blistblock + n; + bl->next = bloblist_attic; + bloblist_attic = bl; + } + return 0; +} + + +/* Helper for key_table_put. This function assumes that + * bloblist_attaci is not NULL. Returns a new bloblist item. Be + * aware that no system calls may be done - even not log + * functions! */ +static bloblist_t +new_bloblist_item (const unsigned char *fpr, unsigned int fprlen, + const unsigned char *ubid, int subkey) +{ + bloblist_t bl; + + bl = bloblist_attic; + bloblist_attic = bl->next; + bl->next = NULL; + + if (ubid) + memcpy (bl->ubid, ubid, 20); + else + memset (bl->ubid, 0, 20); + bl->ubid_valid = 1; + bl->final_kid = 0; + bl->final_fpr = 0; + bl->subkey = !!subkey; + bl->fprlen = fprlen; + memcpy (bl->fpr, fpr, fprlen); + return bl; +} + + +/* If the list of key item in the bucken HASH is full remove a couple + * of them. On error a diagnostic is printed and an error code + * return. Note that the error code GPG_ERR_TRUE is returned if any + * flush and thus system calls were done. + */ +static gpg_error_t +maybe_flush_some_key_buckets (unsigned int hash, unsigned int count) +{ + gpg_error_t err; + key_item_t ki, list_head, *list_tailp, ki_next; + key_item_t *array; + int narray, idx; + + if (count < key_table_threshold) + return 0; /* Nothing to do. */ + + /* Unlink from the global list so that other threads don't disturb + * us. If another thread adds or removes something only one will be + * the winner. Bad luck for the dropped cache items but after all + * it is just a cache. */ + list_head = key_table[hash]; + key_table[hash] = NULL; + + /* Put all items into an array for sorting. */ + array = xtrycalloc (count, sizeof *array); + if (!array) + { + /* That's bad; give up all items of the bucket. */ + err = gpg_error_from_syserror (); + log_info ("Note: malloc failed while purging from the cache: %s\n", + gpg_strerror (err)); + goto leave; + } + narray = 0; + for (ki = list_head; ki; ki = ki_next) + { + ki_next = ki->next; + array[narray++] = ki; + ki->next = NULL; + } + log_assert (narray == count); + + /* Sort the array and put half of it onto a new list. */ + qsort (array, narray, sizeof *array, compare_key_items); + list_head = NULL; + list_tailp = &list_head; + for (idx=0; idx < narray/2; idx++) + { + *list_tailp = array[idx]; + list_tailp = &array[idx]->next; + } + + /* Put the new list into the bucket. */ + ki = key_table[hash]; + key_table[hash] = list_head; + list_head = ki; + + /* Free the remaining items and the array. */ + for (; idx < narray; idx++) + { + key_item_unref (array[idx]); + key_table_dropped++; + } + xfree (array); + err = gpg_error (GPG_ERR_TRUE); + + leave: + /* Free any items added in the meantime by other threads. This is + * also used in case of a malloc problem (which won't update the + * counters, though). */ + for ( ; list_head; list_head = ki_next) + { + ki_next = list_head->next; + key_item_unref (list_head); + } + return err; +} + + +/* Thsi is the core of + * key_table_put, + * key_table_put_no_fpr, + * key_table_put_no_kid. + */ +static void +do_key_table_put (u32 kid_h, u32 kid_l, + const unsigned char *fpr, unsigned int fprlen, + const unsigned char *ubid, int subkey) +{ + unsigned int hash; + key_item_t ki; + bloblist_t bl, bl_tail; + unsigned int count; + int do_find_again; + int mark_not_found = !fpr; + + hash = key_table_hasher (kid_l); + find_again: + do_find_again = 0; + ki = find_in_chain (hash, kid_h, kid_l, &count); + if (ki) + { + if (mark_not_found) + return; /* Can't put the mark because meanwhile a entry was + * added. */ + + for (bl = ki->blist; bl; bl = bl->next) + if (bl->fprlen + && bl->fprlen == fprlen + && !memcmp (bl->fpr, fpr, fprlen)) + break; + if (bl) + return; /* Already in the bloblist for the keyid */ + + /* Append to the list. */ + if (!bloblist_attic) + { + if (alloc_more_bloblist_items ()) + return; /* Out of core - ignore. */ + goto find_again; /* Need to start over due to the malloc. */ + } + for (bl_tail = NULL, bl = ki->blist; bl; bl_tail = bl, bl = bl->next) + ; + bl = new_bloblist_item (fpr, fprlen, ubid, subkey); + if (bl_tail) + bl_tail->next = bl; + else + ki->blist = bl; + + return; + } + + /* If the bucket is full remove a couple of items. */ + if (maybe_flush_some_key_buckets (hash, count)) + { + /* During the fucntion call another thread might have changed + * the bucket. Thus we need to start over. */ + do_find_again = 1; + } + + if (!key_item_attic) + { + if (alloc_more_key_items ()) + return; /* Out of core - ignore. */ + do_find_again = 1; + } + + if (!bloblist_attic) + { + if (alloc_more_bloblist_items ()) + return; /* Out of core - ignore. */ + do_find_again = 1; + } + + if (do_find_again) + goto find_again; + + /* We now know that there are items in the attics. Put them into + * the chain. Note that we may not use any system call here. */ + ki = key_item_attic; + key_item_attic = ki->next; + ki->next = NULL; + + if (mark_not_found) + ki->blist = NULL; + else + ki->blist = new_bloblist_item (fpr, fprlen, ubid, subkey); + + ki->kid_h = kid_h; + ki->kid_l = kid_l; + ki->usecount = 1; + ki->refcount = 1; + + ki->next = key_table[hash]; + key_table[hash] = ki; + key_table_added++; +} + + +/* Given the fingerprint (FPR,FPRLEN) put the UBID into the cache. + * SUBKEY indicates that the fingerprint is from a subkey. */ +static void +key_table_put (const unsigned char *fpr, unsigned int fprlen, + const unsigned char *ubid, int subkey) +{ + u32 kid_h, kid_l; + + if (fprlen < 20 || fprlen > 32) + return; /* No support for v3 keys or unknown key versions. */ + + if (fprlen == 20) /* v4 key */ + { + kid_h = buf32_to_u32 (fpr+12); + kid_l = buf32_to_u32 (fpr+16); + } + else /* v5 or later key */ + { + kid_h = buf32_to_u32 (fpr); + kid_l = buf32_to_u32 (fpr+4); + } + do_key_table_put (kid_h, kid_l, fpr, fprlen, ubid, subkey); +} + + +/* Given the fingerprint (FPR,FPRLEN) put a flag into the cache that + * this fingerprint was not found. */ +static void +key_table_put_no_fpr (const unsigned char *fpr, unsigned int fprlen) +{ + u32 kid_h, kid_l; + + if (fprlen < 20 || fprlen > 32) + return; /* No support for v3 keys or unknown key versions. */ + + if (fprlen == 20) /* v4 key */ + { + kid_h = buf32_to_u32 (fpr+12); + kid_l = buf32_to_u32 (fpr+16); + } + else /* v5 or later key */ + { + kid_h = buf32_to_u32 (fpr); + kid_l = buf32_to_u32 (fpr+4); + } + /* Note that our not-found chaching is only based on the keyid. */ + do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0); +} + + +/* Given the keyid (KID_H, KID_L) put a flag into the cache that this + * keyid was not found. */ +static void +key_table_put_no_kid (u32 kid_h, u32 kid_l) +{ + do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0); +} + + +/* Given the keyid or the fingerprint return the key item from the + * cache. The caller must release the result using key_item_unref. + * NULL is returned if not found. */ +static key_item_t +key_table_get (u32 kid_h, u32 kid_l) +{ + unsigned int hash; + key_item_t ki; + + hash = key_table_hasher (kid_l); + ki = find_in_chain (hash, kid_h, kid_l, NULL); + if (ki) + { + ki->usecount++; + ki->refcount++; + return ki; /* Found */ + } + + return NULL; +} + + +/* Return a key item by searching for the keyid. The caller must use + * key_item_unref on it. */ +static key_item_t +query_by_kid (u32 kid_h, u32 kid_l) +{ + return key_table_get (kid_h, kid_l); +} + + +/* Return a key item by searching for the fingerprint. The caller + * must use key_item_unref on it. Note that the returned key item may + * not actually carry the fingerprint; the caller needs to scan the + * bloblist of the keyitem. We can't do that here because the + * reference counting is done on the keyitem s and thus this needs to + * be returned. */ +static key_item_t +query_by_fpr (const unsigned char *fpr, unsigned int fprlen) +{ + u32 kid_h, kid_l; + + if (fprlen < 20 || fprlen > 32 ) + return NULL; /* No support for v3 keys or unknown key versions. */ + + if (fprlen == 20) /* v4 key */ + { + kid_h = buf32_to_u32 (fpr+12); + kid_l = buf32_to_u32 (fpr+16); + } + else /* v5 or later key */ + { + kid_h = buf32_to_u32 (fpr); + kid_l = buf32_to_u32 (fpr+4); + } + + return key_table_get (kid_h, kid_l); +} + + + + + +/* Install a new resource and return a handle for that backend. */ +gpg_error_t +be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd) +{ + gpg_error_t err; + backend_handle_t hd; + + (void)ctrl; + + *r_hd = NULL; + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->db_type = DB_TYPE_CACHE; + + hd->backend_id = be_new_backend_id (); + + err = blob_table_init (); + if (err) + goto leave; + + err = key_table_init (); + if (err) + goto leave; + + *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_cache_release_resource (ctrl_t ctrl, backend_handle_t hd) +{ + (void)ctrl; + + if (!hd) + return; + hd->db_type = DB_TYPE_NONE; + + /* Fixme: Free the key_table. */ + + xfree (hd); +} + + +/* 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. On a cache hit either 0 or + * GPG_ERR_NOT_FOUND is returned. The former returns the item; the + * latter indicates that the cache has known that the item won't be + * found in any databases. On a cache miss GPG_ERR_EOF is + * returned. */ +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) +{ + gpg_error_t err; + db_request_part_t reqpart; + unsigned int n; + blob_t b; + key_item_t ki; + bloblist_t bl; + int not_found = 0; + int descidx = 0; + int found_bykid = 0; + + log_assert (backend_hd && backend_hd->db_type == DB_TYPE_CACHE); + log_assert (request); + + err = be_find_request_part (backend_hd, request, &reqpart); + if (err) + goto leave; + + if (!desc) + { + /* Reset operation. */ + request->last_cached_valid = 0; + request->last_cached_final = 0; + reqpart->cache_seqno.fpr = 0; + reqpart->cache_seqno.kid = 0; + reqpart->cache_seqno.grip = 0; + reqpart->cache_seqno.ubid = 0; + err = 0; + goto leave; + } + + for (ki = NULL, n=0; n < ndesc && !ki; n++) + { + descidx = n; + switch (desc[n].mode) + { + case KEYDB_SEARCH_MODE_LONG_KID: + ki = query_by_kid (desc[n].u.kid[0], desc[n].u.kid[1]); + if (ki && ki->blist) + { + not_found = 0; + /* Note that in a bloblist all keyids are the same. */ + for (n=0, bl = ki->blist; bl; bl = bl->next) + if (n++ == reqpart->cache_seqno.kid) + break; + if (!bl) + { + key_item_unref (ki); + ki = NULL; + } + else + { + found_bykid = 1; + reqpart->cache_seqno.kid++; + } + } + else if (ki) + not_found = 1; + break; + + case KEYDB_SEARCH_MODE_FPR: + ki = query_by_fpr (desc[n].u.fpr, desc[n].fprlen); + if (ki && ki->blist) + { + not_found = 0; + for (n=0, bl = ki->blist; bl; bl = bl->next) + if (bl->fprlen + && bl->fprlen == desc[n].fprlen + && !memcmp (bl->fpr, desc[n].u.fpr, desc[n].fprlen) + && n++ == reqpart->cache_seqno.fpr) + break; + if (!bl) + { + key_item_unref (ki); + ki = NULL; + } + else + reqpart->cache_seqno.fpr++; + } + else if (ki) + not_found = 1; + break; + + /* case KEYDB_SEARCH_MODE_KEYGRIP: */ + /* ki = query_by_grip (desc[n].u.fpr, desc[n].fprlen); */ + /* break; */ + + case KEYDB_SEARCH_MODE_UBID: + /* This is the quite special UBID mode: If this is + * encountered in the search list we will return just this + * one and obviously look only into the blob cache. */ + if (reqpart->cache_seqno.ubid) + err = gpg_error (GPG_ERR_NOT_FOUND); + else + { + b = blob_table_get (desc[n].u.ubid); + if (b) + { + err = be_return_pubkey (ctrl, b->data, b->datalen, + b->pktype, desc[n].u.ubid); + blob_unref (b); + reqpart->cache_seqno.ubid++; + } + else + err = gpg_error (GPG_ERR_EOF); + } + goto leave; + + default: + ki = NULL; + break; + } + } + + if (not_found) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + key_item_unref (ki); + } + else if (ki) + { + if (bl && bl->ubid_valid) + { + memcpy (request->last_cached_ubid, bl->ubid, 20); + request->last_cached_valid = 1; + request->last_cached_fprlen = desc[descidx].fprlen; + memcpy (request->last_cached_fpr, + desc[descidx].u.fpr, desc[descidx].fprlen); + request->last_cached_kid_h = ki->kid_h; + request->last_cached_kid_l = ki->kid_l; + request->last_cached_valid = 1; + if ((bl->final_kid && found_bykid) + || (bl->final_fpr && !found_bykid)) + request->last_cached_final = 1; + else + request->last_cached_final = 0; + + b = blob_table_get (bl->ubid); + if (b) + { + err = be_return_pubkey (ctrl, b->data, b->datalen, + PUBKEY_TYPE_OPGP, bl->ubid); + blob_unref (b); + } + else + { + /* FIXME - return a different code so that the caller + * can lookup using the UBID. */ + err = gpg_error (GPG_ERR_MISSING_VALUE); + } + } + else if (bl) + err = gpg_error (GPG_ERR_MISSING_VALUE); + else + err = gpg_error (GPG_ERR_NOT_FOUND); + key_item_unref (ki); + } + else + err = gpg_error (GPG_ERR_EOF); + + leave: + return err; +} + + +/* Mark the last cached item as the final item. This is called when + * the actual database returned EOF in respond to a restart from the + * last cached UBID. */ +void +be_cache_mark_final (ctrl_t ctrl, db_request_t request) +{ + key_item_t ki; + bloblist_t bl, blfound; + + (void)ctrl; + + log_assert (request); + + if (!request->last_cached_valid) + return; + + if (!request->last_cached_fprlen) /* Was cached via keyid. */ + { + ki = query_by_kid (request->last_cached_kid_h, + request->last_cached_kid_l); + if (ki && (bl = ki->blist)) + { + for (blfound=NULL; bl; bl = bl->next) + blfound = bl; + if (blfound) + blfound->final_kid = 1; + } + key_item_unref (ki); + } + else /* Was cached via fingerprint. */ + { + ki = query_by_fpr (request->last_cached_fpr, + request->last_cached_fprlen); + if (ki && (bl = ki->blist)) + { + for (blfound=NULL; bl; bl = bl->next) + if (bl->fprlen + && bl->fprlen == request->last_cached_fprlen + && !memcmp (bl->fpr, request->last_cached_fpr, + request->last_cached_fprlen)) + blfound = bl; + if (blfound) + blfound->final_fpr = 1; + } + key_item_unref (ki); + } + + request->last_cached_valid = 0; +} + + +/* Put the key (BLOB,BLOBLEN) of PUBKEY_TYPE into the cache. */ +void +be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid, + const void *blob, unsigned int bloblen, + enum pubkey_types pubkey_type) +{ + gpg_error_t err; + + (void)ctrl; + + if (pubkey_type == PUBKEY_TYPE_OPGP) + { + struct _keybox_openpgp_info info; + struct _keybox_openpgp_key_info *kinfo; + + err = _keybox_parse_openpgp (blob, bloblen, NULL, &info); + if (err) + { + log_info ("cache: error parsing OpenPGP blob: %s\n", + gpg_strerror (err)); + return; + } + + blob_table_put (ubid, pubkey_type, blob, bloblen); + + kinfo = &info.primary; + key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 0); + if (info.nsubkeys) + for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next) + key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 1); + + _keybox_destroy_openpgp_info (&info); + } + +} + + +/* Put the a non-found mark for PUBKEY_TYPE into the cache. The + * indices are taken from the search descriptors (DESC,NDESC). */ +void +be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type, + KEYDB_SEARCH_DESC *desc, unsigned int ndesc) +{ + unsigned int n; + + (void)ctrl; + (void)pubkey_type; + + for (n=0; n < ndesc; n++) + { + switch (desc->mode) + { + case KEYDB_SEARCH_MODE_LONG_KID: + key_table_put_no_kid (desc[n].u.kid[0], desc[n].u.kid[1]); + break; + + case KEYDB_SEARCH_MODE_FPR: + key_table_put_no_fpr (desc[n].u.fpr, desc[n].fprlen); + break; + + default: + break; + } + } +} diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c new file mode 100644 index 000000000..438d300b0 --- /dev/null +++ b/kbx/backend-kbx.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +#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 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/backend-support.c b/kbx/backend-support.c new file mode 100644 index 000000000..62551cafa --- /dev/null +++ b/kbx/backend-support.c @@ -0,0 +1,171 @@ +/* backend-support.c - Supporting functions for the backend. + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +#include "keyboxd.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "backend.h" +#include "keybox.h" + + +/* Common definition part of all backend handle. All definitions of + * this structure must start with these fields. */ +struct backend_handle_s +{ + enum database_types db_type; + unsigned int backend_id; +}; + + + +/* Return a string with the name of the database type T. */ +const char * +strdbtype (enum database_types t) +{ + switch (t) + { + case DB_TYPE_NONE: return "none"; + case DB_TYPE_CACHE:return "cache"; + case DB_TYPE_KBX: return "keybox"; + } + return "?"; +} + + +/* Return a new backend ID. Backend IDs are used to identify backends + * without using the actual object. The number of backend resources + * is limited because they are specified in the config file. Thus an + * overflow check is not required. */ +unsigned int +be_new_backend_id (void) +{ + static unsigned int last; + + return ++last; +} + + +/* Release the backend described by HD. This is a generic function + * which dispatches to the the actual backend. */ +void +be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd) +{ + if (!hd) + return; + switch (hd->db_type) + { + case DB_TYPE_NONE: + xfree (hd); + break; + case DB_TYPE_CACHE: + be_cache_release_resource (ctrl, hd); + break; + case DB_TYPE_KBX: + be_kbx_release_resource (ctrl, hd); + break; + default: + log_error ("%s: faulty backend handle of type %d given\n", + __func__, hd->db_type); + } +} + + +/* Release the request object REQ. */ +void +be_release_request (db_request_t req) +{ + db_request_part_t part, partn; + + if (!req) + return; + + for (part = req->part; part; part = partn) + { + partn = part->next; + be_kbx_release_kbx_hd (part->kbx_hd); + xfree (part); + } +} + + +/* Given the backend handle BACKEND_HD and the REQUEST find or + * allocate a request part for that backend and store it at R_PART. + * On error R_PART is set to NULL and an error returned. */ +gpg_error_t +be_find_request_part (backend_handle_t backend_hd, db_request_t request, + db_request_part_t *r_part) +{ + gpg_error_t err; + db_request_part_t part; + + for (part = request->part; part; part = part->next) + if (part->backend_id == backend_hd->backend_id) + break; + if (!part) + { + part = xtrycalloc (1, sizeof *part); + if (!part) + return gpg_error_from_syserror (); + part->backend_id = backend_hd->backend_id; + if (backend_hd->db_type == DB_TYPE_KBX) + { + err = be_kbx_init_request_part (backend_hd, part); + if (err) + { + xfree (part); + return err; + } + } + part->next = request->part; + request->part = part; + } + *r_part = part; + return 0; +} + + +/* Return the public key (BUFFER,BUFLEN) which has the type + * PUBKEY_TYPE to the caller. */ +gpg_error_t +be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen, + enum pubkey_types pubkey_type, const unsigned char *ubid) +{ + gpg_error_t err; + char hexubid[41]; + + bin2hex (ubid, 20, hexubid); + err = status_printf (ctrl, "PUBKEY_INFO", "%d %s", pubkey_type, hexubid); + if (err) + goto leave; + + if (ctrl->no_data_return) + err = 0; + else + err = kbxd_write_data_line(ctrl, buffer, buflen); + + leave: + return err; +} diff --git a/kbx/backend.h b/kbx/backend.h new file mode 100644 index 000000000..675ec213d --- /dev/null +++ b/kbx/backend.h @@ -0,0 +1,140 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#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_CACHE_FPRLEN see + * above. The entry here is only valid if LAST_CACHE_VALID is set; + * if LAST_CACHE_FINAL is also set, this indicates that no further + * database searches are required. */ + unsigned char last_cached_ubid[20]; + 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); + + +/*-- backend-cache.c --*/ +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, unsigned char *ubid); + + +#endif /*KBX_BACKEND_H*/ diff --git a/kbx/frontend.c b/kbx/frontend.c new file mode 100644 index 000000000..6e0cbcb11 --- /dev/null +++ b/kbx/frontend.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +#include "keyboxd.h" +#include <assuan.h> +#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) + { + 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; +} diff --git a/kbx/frontend.h b/kbx/frontend.h new file mode 100644 index 000000000..55d041fb0 --- /dev/null +++ b/kbx/frontend.h @@ -0,0 +1,36 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef KBX_FRONTEND_H +#define KBX_FRONTEND_H + +#include "keybox-search-desc.h" + + +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); + + +#endif /*KBX_FRONTEND_H*/ diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c new file mode 100644 index 000000000..929ee6116 --- /dev/null +++ b/kbx/kbxserver.c @@ -0,0 +1,775 @@ +/* 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include "keyboxd.h" +#include <assuan.h> +#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_getinfo[] = + "GETINFO <what>\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" + "\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 }, + { "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; + } +} diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 7e4dcfaaa..0fced7e1c 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -67,6 +67,7 @@ - u16 Blob flags bit 0 = contains secret key material (not used) bit 1 = ephemeral blob (e.g. used while querying external resources) + bit 2 = blob has an UBID field. - u32 Offset to the OpenPGP keyblock or the X.509 DER encoded certificate - u32 The length of the keyblock or certificate @@ -143,7 +144,10 @@ IDs go here. - bN Space for the keyblock or certificate. - bN RFU. This is the remaining space after keyblock and before - the checksum. It is not covered by the checksum. + the checksum. Not part of the SHA-1 checksum. + - bN Only if blob flags bit 2 is set: 20 octet Unique Blob-ID (UBID). + This is the SHA-1 checksum of the keyblock or certificate. + This is not part of the SHA-1 checksum below. - b20 SHA-1 checksum (useful for KS synchronization?) Note, that KBX versions before GnuPG 2.1 used an MD5 checksum. However it was only created but never checked. @@ -173,6 +177,10 @@ #include "../common/gettime.h" +#include "../common/host2net.h" + + +#define get32(a) buf32_to_ulong ((a)) /* special values of the signature status */ @@ -559,7 +567,7 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral, put32 ( a, 0 ); /* blob length, needs fixup */ put8 ( a, blobtype); put8 ( a, want_fpr32? 2:1 ); /* blob type version */ - put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ + put16 ( a, as_ephemeral? 6:4 ); /* blob flags */ put32 ( a, 0 ); /* offset to the raw data, needs fixup */ put32 ( a, 0 ); /* length of the raw data, needs fixup */ @@ -686,8 +694,8 @@ create_blob_finish (KEYBOXBLOB blob) unsigned char *pp; size_t n; - /* Write a placeholder for the checksum */ - put_membuf (a, NULL, 20); + /* Write placeholders for the UBID and the checksum */ + put_membuf (a, NULL, 40); /* get the memory area */ n = 0; /* (Just to avoid compiler warning.) */ @@ -721,8 +729,11 @@ create_blob_finish (KEYBOXBLOB blob) blob->fixups = NULL; } + /* Compute and store the UBID. (image_off) (image_len) */ + gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 40, p + get32 (p+8), get32 (p+12)); + /* Compute and store the SHA-1 checksum. */ - gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 20); + gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 40); pp = xtrymalloc (n); if ( !pp ) diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index c595208d0..55a47a403 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -63,6 +63,41 @@ print_string (FILE *fp, const byte *p, size_t n, int delim) } +static void +print_ubib (const byte *buffer, size_t length, FILE *fp) +{ + const byte *p; + int i; + size_t image_off, image_len; + unsigned char digest[20]; + + fprintf (fp, "UBIB: "); + if (length < 40) + { + fputs ("[blob too short for a stored UBIB]\n", fp); + return; + } + + p = buffer + length - 40; + for (i=0; i < 20; p++, i++) + fprintf (fp, "%02X", *p); + + image_off = get32 (buffer+8); + image_len = get32 (buffer+12); + if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length) + { + fputs (" [image claims to be longer than the blob]\n", fp); + return; + } + + gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer+image_off,image_len); + if (memcmp (digest, buffer + length - 40, 20)) + fputs (" [does not match the image]\n", fp); + else + fputc ('\n', fp); +} + + static int print_checksum (const byte *buffer, size_t length, size_t unhashed, FILE *fp) { @@ -171,6 +206,7 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) ulong unhashed; const byte *p; int is_fpr32; /* blob ersion 2 */ + int have_ubib = 0; buffer = _keybox_get_blob_image (blob, &length); @@ -237,6 +273,14 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) fputs ("ephemeral", fp); any++; } + if ((n & 4)) + { + if (any) + putc (',', fp); + fputs ("ubid", fp); + any++; + have_ubib = 1; + } putc (')', fp); } putc ('\n', fp); @@ -422,6 +466,8 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) n = get32 ( buffer + length - unhashed); fprintf (fp, "Storage-Flags: %08lx\n", n ); } + if (have_ubib) + print_ubib (buffer, length, fp); print_checksum (buffer, length, unhashed, fp); return 0; } diff --git a/kbx/keybox-file.c b/kbx/keybox-file.c index 046e32123..7a1b43aa7 100644 --- a/kbx/keybox-file.c +++ b/kbx/keybox-file.c @@ -146,9 +146,9 @@ _keybox_write_blob (KEYBOXBLOB blob, FILE *fp) } -/* Write a fresh header type blob. */ -int -_keybox_write_header_blob (FILE *fp, int for_openpgp) +/* Write a fresh header type blob. Either FP or STREAM must be used. */ +gpg_error_t +_keybox_write_header_blob (FILE *fp, estream_t stream, int for_openpgp) { unsigned char image[32]; u32 val; @@ -174,7 +174,15 @@ _keybox_write_header_blob (FILE *fp, int for_openpgp) image[20+2] = (val >> 8); image[20+3] = (val ); - if (fwrite (image, 32, 1, fp) != 1) - return gpg_error_from_syserror (); + if (fp) + { + if (fwrite (image, 32, 1, fp) != 1) + return gpg_error_from_syserror (); + } + else + { + if (es_fwrite (image, 32, 1, stream) != 1) + return gpg_error_from_syserror (); + } return 0; } diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 7286d2ae3..7fa97e97b 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -42,11 +42,21 @@ typedef enum { KEYDB_SEARCH_MODE_SN, KEYDB_SEARCH_MODE_SUBJECT, KEYDB_SEARCH_MODE_KEYGRIP, + KEYDB_SEARCH_MODE_UBID, KEYDB_SEARCH_MODE_FIRST, KEYDB_SEARCH_MODE_NEXT } KeydbSearchMode; +/* Identifiers for the public key types we use in GnuPG. */ +enum pubkey_types + { + PUBKEY_TYPE_UNKNOWN = 0, + PUBKEY_TYPE_OPGP = 1, + PUBKEY_TYPE_X509 = 2 + }; + + /* Forward declaration. See g10/packet.h. */ struct gpg_pkt_user_id_s; typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t; @@ -70,6 +80,7 @@ struct keydb_search_desc unsigned char fpr[32]; u32 kid[2]; /* Note that this is in native endianness. */ unsigned char grip[20]; + unsigned char ubid[20]; } u; byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */ int exact; /* Use exactly this key ('!' suffix in gpg). */ diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 77469a24c..95ad07251 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -696,6 +696,35 @@ has_keygrip (KEYBOXBLOB blob, const unsigned char *grip) return 0; } +static inline int +has_ubid (KEYBOXBLOB blob, const unsigned char *ubid) +{ + size_t length; + const unsigned char *buffer; + size_t image_off, image_len; + unsigned char ubid_blob[20]; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /*GPG_ERR_TOO_SHORT*/ + + if ((get16 (buffer + 6) & 4)) + { + /* The blob has a stored UBID. */ + return !memcmp (ubid, buffer + length - 40, 20); + } + else + { + /* Need to compute the UBID. */ + image_off = get32 (buffer+8); + image_len = get32 (buffer+12); + if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length) + return 0; /*GPG_ERR_TOO_SHORT*/ + + gcry_md_hash_buffer (GCRY_MD_SHA1, ubid_blob, buffer+image_off,image_len); + return !memcmp (ubid, ubid_blob, 20); + } +} static inline int has_issuer (KEYBOXBLOB blob, const char *name) @@ -1119,6 +1148,10 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, if (has_keygrip (blob, desc[n].u.grip)) goto found; break; + case KEYDB_SEARCH_MODE_UBID: + if (has_ubid (blob, desc[n].u.ubid)) + goto found; + break; case KEYDB_SEARCH_MODE_FIRST: goto found; break; @@ -1180,11 +1213,70 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, a successful search operation. */ +/* Return the raw data from the last found blob. Caller must release + * the value stored at R_BUFFER. If called with NULL for R_BUFFER + * only the needed length for the buffer and the public key type is + * returned. */ +gpg_error_t +keybox_get_data (KEYBOX_HANDLE hd, void **r_buffer, size_t *r_length, + enum pubkey_types *r_pubkey_type) +{ + const unsigned char *buffer; + size_t length; + size_t image_off, image_len; + + if (r_buffer) + *r_buffer = NULL; + if (r_length) + *r_length = 0; + if (r_pubkey_type) + *r_pubkey_type = PUBKEY_TYPE_UNKNOWN; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + if (!hd->found.blob) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + switch (blob_get_type (hd->found.blob)) + { + case KEYBOX_BLOBTYPE_PGP: + if (r_pubkey_type) + *r_pubkey_type = PUBKEY_TYPE_OPGP; + break; + case KEYBOX_BLOBTYPE_X509: + if (r_pubkey_type) + *r_pubkey_type = PUBKEY_TYPE_X509; + break; + default: + return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); + } + + buffer = _keybox_get_blob_image (hd->found.blob, &length); + if (length < 40) + return gpg_error (GPG_ERR_TOO_SHORT); + image_off = get32 (buffer+8); + image_len = get32 (buffer+12); + if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length) + return gpg_error (GPG_ERR_TOO_SHORT); + + if (r_length) + *r_length = image_len; + if (r_buffer) + { + *r_buffer = xtrymalloc (image_len); + if (!*r_buffer) + return gpg_error_from_syserror (); + memcpy (*r_buffer, buffer + image_off, image_len); + } + + return 0; +} + /* Return the last found keyblock. Returns 0 on success and stores a * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the - * number of the key or user id which was matched the search criteria; - * if not known they are set to 0. */ + * index of the key or user id which matched the search criteria; if + * not known they are set to 0. */ gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, int *r_pk_no, int *r_uid_no) diff --git a/kbx/keybox-update.c b/kbx/keybox-update.c index e25596b26..fbcaec7b9 100644 --- a/kbx/keybox-update.c +++ b/kbx/keybox-update.c @@ -182,7 +182,7 @@ blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob, if (!newfp ) return gpg_error_from_syserror (); - rc = _keybox_write_header_blob (newfp, for_openpgp); + rc = _keybox_write_header_blob (newfp, NULL, for_openpgp); if (rc) { fclose (newfp); @@ -730,7 +730,7 @@ keybox_compress (KEYBOX_HANDLE hd) } /* The header blob is missing. Insert it. */ - rc = _keybox_write_header_blob (newfp, hd->for_openpgp); + rc = _keybox_write_header_blob (newfp, NULL, hd->for_openpgp); if (rc) break; any_changes = 1; diff --git a/kbx/keybox.h b/kbx/keybox.h index 4d941571e..8a22580dd 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -81,9 +81,13 @@ gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout); /*-- keybox-file.c --*/ /* Fixme: This function does not belong here: Provide a better interface to create a new keybox file. */ -int _keybox_write_header_blob (FILE *fp, int openpgp_flag); +gpg_error_t _keybox_write_header_blob (FILE *fp, estream_t stream, + int openpgp_flag); /*-- keybox-search.c --*/ +gpg_error_t keybox_get_data (KEYBOX_HANDLE hd, + void **r_buffer, size_t *r_length, + enum pubkey_types *r_pubkey_type); gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, int *r_uid_no, int *r_pk_no); #ifdef KEYBOX_WITH_X509 diff --git a/kbx/keyboxd-w32info.rc b/kbx/keyboxd-w32info.rc new file mode 100644 index 000000000..421747499 --- /dev/null +++ b/kbx/keyboxd-w32info.rc @@ -0,0 +1,50 @@ +/* keyboxd-w32info.rc -*- c -*- + * Copyright (C) 2018 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s public key daemon\0" + VALUE "InternalName", "keyboxd\0" + VALUE "OriginalFilename", "keyboxd.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c new file mode 100644 index 000000000..d39b35749 --- /dev/null +++ b/kbx/keyboxd.c @@ -0,0 +1,1845 @@ +/* keyboxd.c - The GnuPG Keybox Daemon + * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc. + * Copyright (C) 2000-2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#include <sys/stat.h> +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# include <winsock2.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <sys/socket.h> +# include <sys/un.h> +#endif /*!HAVE_W32_SYSTEM*/ +#include <unistd.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#include <npth.h> + +#define GNUPG_COMMON_NEED_AFLOCAL +#include "keyboxd.h" +#include <assuan.h> /* Malloc hooks and socket wrappers. */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/asshelp.h" +#include "../common/init.h" +#include "../common/gc-opt-flags.h" +#include "../common/exechelp.h" +#include "frontend.h" + + +/* Urrgs: Put this into a separate header - but it needs assuan.h first. */ +extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, + const char *msg); + + +enum cmd_and_opt_values + { + aNull = 0, + oQuiet = 'q', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oLogFile, + oServer, + oDaemon, + oBatch, + oFakedSystemTime, + oListenBacklog, + oDisableCheckOwnSocket, + + oDummy + }; + + +static ARGPARSE_OPTS opts[] = { + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_group (301, N_("@Options:\n ")), + + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), + + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + + ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + + ARGPARSE_s_n (oBatch, "batch", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + + ARGPARSE_end () /* End of list */ +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_CLOCK_VALUE , "clock" }, + { DBG_LOOKUP_VALUE , "lookup" }, + { 77, NULL } /* 77 := Do not exit on "help" or "?". */ + }; + +/* The timer tick used for housekeeping stuff. Note that on Windows + * we use a SetWaitableTimer seems to signal earlier than about 2 + * seconds. Thus we use 4 seconds on all platforms except for + * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check + * our own socket in standard socket mode. If that value is 0 we + * don't check at all. All values are in seconds. */ +#if defined(HAVE_W32CE_SYSTEM) +# define TIMERTICK_INTERVAL (60) +# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ +#else +# define TIMERTICK_INTERVAL (4) +# define CHECK_OWN_SOCKET_INTERVAL (60) +#endif + +/* The list of open file descriptors at startup. Note that this list + * has been allocated using the standard malloc. */ +#ifndef HAVE_W32_SYSTEM +static int *startup_fd_list; +#endif + +/* The signal mask at startup and a flag telling whether it is valid. */ +#ifdef HAVE_SIGPROCMASK +static sigset_t startup_signal_mask; +static int startup_signal_mask_valid; +#endif + +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + +/* Counter for the currently running own socket checks. */ +static int check_own_socket_running; + +/* Flag to indicate that we shall not watch our own socket. */ +static int disable_check_own_socket; + +/* Flag to inhibit socket removal in cleanup. */ +static int inhibit_socket_removal; + +/* Name of the communication socket used for client requests. */ +static char *socket_name; + +/* We need to keep track of the server's nonces (these are dummies for + * POSIX systems). */ +static assuan_sock_nonce_t socket_nonce; + +/* Value for the listen() backlog argument. We use the same value for + * all sockets - 64 is on current Linux half of the default maximum. + * Let's try this as default. Change at runtime with --listen-backlog. */ +static int listen_backlog = 64; + +/* Name of a config file, which will be reread on a HUP if it is not NULL. */ +static char *config_filename; + +/* Keep track of the current log file so that we can avoid updating + * the log file after a SIGHUP if it didn't changed. Malloced. */ +static char *current_logfile; + +/* This flag is true if the inotify mechanism for detecting the + * removal of the homedir is active. This flag is used to disable the + * alternative but portable stat based check. */ +static int have_homedir_inotify; + +/* Depending on how keyboxd was started, the homedir inotify watch may + * not be reliable. This flag is set if we assume that inotify works + * reliable. */ +static int reliable_homedir_inotify; + +/* Number of active connections. */ +static int active_connections; + +/* This object is used to dispatch progress messages from Libgcrypt to + * the right thread. Given that we will have at max only a few dozen + * connections at a time, using a linked list is the easiest way to + * handle this. */ +struct progress_dispatch_s +{ + struct progress_dispatch_s *next; + /* The control object of the connection. If this is NULL no + * connection is associated with this item and it is free for reuse + * by new connections. */ + ctrl_t ctrl; + + /* The thread id of (npth_self) of the connection. */ + npth_t tid; + + /* The callback set by the connection. This is similar to the + * Libgcrypt callback but with the control object passed as the + * first argument. */ + void (*cb)(ctrl_t ctrl, + const char *what, int printchar, + int current, int total); +}; +struct progress_dispatch_s *progress_dispatch_list; + + + + +/* + * Local prototypes. + */ + +static char *create_socket_name (char *standard_name, int with_homedir); +static gnupg_fd_t create_server_socket (char *name, int cygwin, + assuan_sock_nonce_t *nonce); +static void create_directories (void); + +static void kbxd_libgcrypt_progress_cb (void *data, const char *what, + int printchar, + int current, int total); +static void kbxd_init_default_ctrl (ctrl_t ctrl); +static void kbxd_deinit_default_ctrl (ctrl_t ctrl); + +static void handle_connections (gnupg_fd_t listen_fd); +static void check_own_socket (void); +static int check_for_running_kbxd (int silent); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + + +/* + * Functions. + */ + +/* Allocate a string describing a library version by calling a GETFNC. + * This function is expected to be called only once. GETFNC is + * expected to have a semantic like gcry_check_version (). */ +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + return xstrconcat (libname, " ", getfnc (NULL), NULL); +} + + +/* Return strings describing this program. The case values are + * described in common/argparse.c:strusage. The values here override + * the default values given by strusage. */ +static const char * +my_strusage (int level) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 11: p = "keyboxd (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug + reporting address. This is so that we can change the + reporting address without breaking the translations. */ + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + + case 1: + case 40: p = _("Usage: keyboxd [options] (-h for help)"); + break; + case 41: p = _("Syntax: keyboxd [options] [command [args]]\n" + "Public key management for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + + +/* Setup the debugging. Note that we don't fail here, because it is + * important to keep keyboxd running even after re-reading the options + * due to a SIGHUP. */ +static void +set_debug (void) +{ + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + +/* Helper for cleanup to remove one socket with NAME. */ +static void +remove_socket (char *name) +{ + if (name && *name) + { + gnupg_remove (name); + *name = 0; + } +} + + +/* Cleanup code for this program. This is either called has an atexit + handler or directly. */ +static void +cleanup (void) +{ + static int done; + + if (done) + return; + done = 1; + if (!inhibit_socket_removal) + remove_socket (socket_name); +} + + +/* Handle options which are allowed to be reset after program start. + * Return true when the current option in PARGS could be handled and + * false if not. As a special feature, passing a value of NULL for + * PARGS, resets the options to the default. REREAD should be set + * true if it is not the initial option parsing. */ +static int +parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) +{ + if (!pargs) + { /* reset mode */ + opt.quiet = 0; + opt.verbose = 0; + opt.debug = 0; + disable_check_own_socket = 0; + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + + case oDebug: + parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); + break; + case oDebugAll: opt.debug = ~0; break; + + case oLogFile: + if (!reread) + return 0; /* not handeld */ + if (!current_logfile || !pargs->r.ret_str + || strcmp (current_logfile, pargs->r.ret_str)) + { + log_set_file (pargs->r.ret_str); + xfree (current_logfile); + current_logfile = xtrystrdup (pargs->r.ret_str); + } + break; + + case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; + + default: + return 0; /* not handled */ + } + + return 1; /* handled */ +} + + +/* Fixup some options after all have been processed. */ +static void +finalize_rereadable_options (void) +{ +} + + +static void +thread_init_once (void) +{ + static int npth_initialized = 0; + + if (!npth_initialized) + { + npth_initialized++; + npth_init (); + } + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + /* Now that we have set the syscall clamp we need to tell Libgcrypt + * that it should get them from libgpg-error. Note that Libgcrypt + * has already been initialized but at that point nPth was not + * initialized and thus Libgcrypt could not set its system call + * clamp. */ + gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); +} + + +static void +initialize_modules (void) +{ + thread_init_once (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); +} + + +/* The main entry point. */ +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int default_config =1; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + char *logfile = NULL; + int gpgconf_list = 0; + int debug_wait = 0; + struct assuan_malloc_hooks malloc_hooks; + + early_system_init (); + + /* Before we do anything else we save the list of currently open + * file descriptors and the signal mask. This info is required to + * do the exec call properly. We don't need it on Windows. */ +#ifndef HAVE_W32_SYSTEM + startup_fd_list = get_all_open_fds (); +#endif /*!HAVE_W32_SYSTEM*/ +#ifdef HAVE_SIGPROCMASK + if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) + startup_signal_mask_valid = 1; +#endif /*HAVE_SIGPROCMASK*/ + + /* Set program name etc. */ + set_strusage (my_strusage); + log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_sock_init (); + assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); + setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor); + + setup_libgcrypt_logging (); + gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL); + + /* Set default options. */ + parse_rereadable_options (NULL, 0); /* Reset them to default values. */ + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* Yes, a config file was given so we do not try the default + * one. Instead we read the config file when it is + * encountered during main parsing of the command line. */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + gnupg_set_homedir (pargs.r.ret_str); + } + + if (default_config) + configname = make_filename (gnupg_homedir (), + "keyboxd" EXTSEP_S "conf", NULL); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if (parse_debug) + log_info (_("Note: no default option file '%s'\n"), configname); + /* Save the default confif file name so that + * reread_configuration is able to test whether the + * config file has been created in the meantime. */ + xfree (config_filename); + config_filename = configname; + configname = NULL; + } + else + { + log_error (_("option file '%s': %s\n"), + configname, strerror (errno)); + exit (2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from '%s'\n"), configname); + default_config = 0; + } + + while (optfile_parse (configfp, configname, &configlineno, &pargs, opts)) + { + if (parse_rereadable_options (&pargs, 0)) + continue; /* Already handled */ + switch (pargs.r_opt) + { + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oBatch: opt.batch=1; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oOptions: + /* Config files may not be nested (silently ignore them). */ + if (!configfp) + { + xfree (configname); + configname = xstrdup (pargs.r.ret_str); + goto next_pass; + } + break; + case oNoGreeting: /* Dummy option. */ break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + case oFakedSystemTime: + { + time_t faked_time = isotime2epoch (pargs.r.ret_str); + if (faked_time == (time_t)(-1)) + faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); + gnupg_set_time (faked_time, 0); + } + break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + default : pargs.err = configfp? 1:2; break; + } + } + if (configfp) + { + fclose (configfp); + configfp = NULL; + /* Keep a copy of the name so that it can be read on SIGHUP. */ + if (config_filename != configname) + { + xfree (config_filename); + config_filename = configname; + } + configname = NULL; + goto next_pass; + } + + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit (2); + + finalize_rereadable_options (); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + +#ifdef ENABLE_NLS + /* keyboxd usually does not output any messages because it runs in + * the background. For log files it is acceptable to have messages + * always encoded in utf-8. We switch here to utf-8, so that + * commands like --help still give native messages. It is far + * easier to switch only once instead of for every message and it + * actually helps when more then one thread is active (avoids an + * extra copy step). */ + bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); +#endif + + if (!pipe_server && !is_daemon && !gpgconf_list) + { + /* We have been called without any command and thus we merely + * check whether an instance of us is already running. We do + * this right here so that we don't clobber a logfile with this + * check but print the status directly to stderr. */ + opt.debug = 0; + set_debug (); + check_for_running_kbxd (0); + kbxd_exit (0); + } + + set_debug (); + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + /* Try to create missing directories. */ + create_directories (); + + if (debug_wait && pipe_server) + { + thread_init_once (); + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (gpgconf_list == 2) + kbxd_exit (0); + else if (gpgconf_list) + { + char *filename; + char *filename_esc; + + /* List options and default values in the gpgconf format. */ + filename = make_filename (gnupg_homedir (), + "keyboxd" EXTSEP_S "conf", NULL); + filename_esc = percent_escape (filename, NULL); + + es_printf ("%s-%s.conf:%lu:\"%s\n", + GPGCONF_NAME, "keyboxd", GC_OPT_FLAG_DEFAULT, filename_esc); + xfree (filename); + xfree (filename_esc); + + es_printf ("verbose:%lu:\n" + "quiet:%lu:\n" + "log-file:%lu:\n", + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); + + kbxd_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX + | GPGRT_LOG_WITH_TIME + | GPGRT_LOG_WITH_PID)); + current_logfile = xstrdup (logfile); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + + initialize_modules (); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + kbxd_exit (1); + } + kbxd_init_default_ctrl (ctrl); + + kbxd_add_resource (ctrl, "pubring.kbx", 0); + + kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0); + kbxd_deinit_default_ctrl (ctrl); + xfree (ctrl); + } + else if (!is_daemon) + ; /* NOTREACHED */ + else + { /* Regular daemon mode. */ + gnupg_fd_t fd; +#ifndef HAVE_W32_SYSTEM + pid_t pid; +#endif + + /* Create the sockets. */ + socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1); + fd = create_server_socket (socket_name, 0, &socket_nonce); + + fflush (NULL); + +#ifdef HAVE_W32_SYSTEM + + (void)nodetach; + initialize_modules (); + +#else /*!HAVE_W32_SYSTEM*/ + + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* We are the parent */ + + /* Close the socket FD. */ + close (fd); + + /* The signal mask might not be correct right now and thus + * we restore it. That is not strictly necessary but some + * programs falsely assume a cleared signal mask. */ + +#ifdef HAVE_SIGPROCMASK + if (startup_signal_mask_valid) + { + if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) + log_error ("error restoring signal mask: %s\n", + strerror (errno)); + } + else + log_info ("no saved signal mask\n"); +#endif /*HAVE_SIGPROCMASK*/ + + *socket_name = 0; /* Don't let cleanup() remove the socket - + the child should do this from now on */ + + exit (0); + /*NOTREACHED*/ + } /* End parent */ + + /* + * This is the child + */ + + initialize_modules (); + + /* Detach from tty and put process into a new session */ + if (!nodetach) + { + int i; + unsigned int oldflags; + + /* Close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( ! close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); + opt.running_detached = 1; + + /* Because we don't support running a program on the command + * line we can assume that the inotify things works and thus + * we can avoid the regular stat calls. */ + reliable_homedir_inotify = 1; + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + { + ctrl_t ctrl; + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + kbxd_exit (1); + } + kbxd_init_default_ctrl (ctrl); + kbxd_add_resource (ctrl, "pubring.kbx", 0); + kbxd_deinit_default_ctrl (ctrl); + xfree (ctrl); + } + + log_info ("%s %s started\n", strusage(11), strusage(13) ); + handle_connections (fd); + assuan_sock_close (fd); + } + + return 0; +} + + +/* Exit entry point. This function should be called instead of a + plain exit. */ +void +kbxd_exit (int rc) +{ + /* As usual we run our cleanup handler. */ + cleanup (); + + /* at this time a bit annoying */ + if ((opt.debug & DBG_MEMSTAT_VALUE)) + gcry_control (GCRYCTL_DUMP_MEMORY_STATS ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +/* This is our callback function for gcrypt progress messages. It is + * set once at startup and dispatches progress messages to the + * corresponding threads of ours. */ +static void +kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar, + int current, int total) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + (void)data; + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch && dispatch->cb) + dispatch->cb (dispatch->ctrl, what, printchar, current, total); +} + + +/* If a progress dispatcher callback has been associated with the + * current connection unregister it. */ +static void +unregister_progress_cb (void) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch) + { + dispatch->ctrl = NULL; + dispatch->cb = NULL; + } +} + + +/* Setup a progress callback CB for the current connection. Using a + * CB of NULL disables the callback. */ +void +kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, + int printchar, int current, int total), + ctrl_t ctrl) +{ + struct progress_dispatch_s *dispatch, *firstfree; + npth_t mytid = npth_self (); + + firstfree = NULL; + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + { + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (!dispatch->ctrl && !firstfree) + firstfree = dispatch; + } + if (!dispatch) /* None allocated: Reuse or allocate a new one. */ + { + if (firstfree) + { + dispatch = firstfree; + } + else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) + { + dispatch->next = progress_dispatch_list; + progress_dispatch_list = dispatch; + } + else + { + log_error ("error allocating new progress dispatcher slot: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + dispatch->ctrl = ctrl; + dispatch->tid = mytid; + } + + dispatch->cb = cb; +} + + +/* Each thread has its own local variables conveyed by a control + * structure usually identified by an argument named CTRL. This + * function is called immediately after allocating the control + * structure. Its purpose is to setup the default values for that + * structure. Note that some values may have already been set. */ +static void +kbxd_init_default_ctrl (ctrl_t ctrl) +{ + ctrl->magic = SERVER_CONTROL_MAGIC; +} + + +/* Release all resources allocated by default in the control + structure. This is the counterpart to kbxd_init_default_ctrl. */ +static void +kbxd_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + kbxd_release_session_info (ctrl); + ctrl->magic = 0xdeadbeef; + unregister_progress_cb (); + xfree (ctrl->lc_messages); +} + + +/* Reread parts of the configuration. Note, that this function is + * obviously not thread-safe and should only be called from the PTH + * signal handler. + * + * Fixme: Due to the way the argument parsing works, we create a + * memory leak here for all string type arguments. There is currently + * no clean way to tell whether the memory for the argument has been + * allocated or points into the process' original arguments. Unless + * we have a mechanism to tell this, we need to live on with this. */ +static void +reread_configuration (void) +{ + ARGPARSE_ARGS pargs; + FILE *fp; + unsigned int configlineno = 0; + int dummy; + + if (!config_filename) + return; /* No config file. */ + + fp = fopen (config_filename, "r"); + if (!fp) + { + log_info (_("option file '%s': %s\n"), + config_filename, strerror(errno) ); + return; + } + + parse_rereadable_options (NULL, 1); /* Start from the default values. */ + + memset (&pargs, 0, sizeof pargs); + dummy = 0; + pargs.argc = &dummy; + pargs.flags = 1; /* do not remove the args */ + while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) + { + if (pargs.r_opt < -1) + pargs.err = 1; /* Print a warning. */ + else /* Try to parse this option - ignore unchangeable ones. */ + parse_rereadable_options (&pargs, 1); + } + fclose (fp); + finalize_rereadable_options (); + set_debug (); +} + + +/* Return the file name of the socket we are using for requests. */ +const char * +get_kbxd_socket_name (void) +{ + const char *s = socket_name; + + return (s && *s)? s : NULL; +} + + +/* Return the number of active connections. */ +int +get_kbxd_active_connection_count (void) +{ + return active_connections; +} + + +/* Create a name for the socket in the home directory as using + * STANDARD_NAME. We also check for valid characters as well as + * against a maximum allowed length for a Unix domain socket is done. + * The function terminates the process in case of an error. The + * function returns a pointer to an allocated string with the absolute + * name of the socket used. */ +static char * +create_socket_name (char *standard_name, int with_homedir) +{ + char *name; + + if (with_homedir) + name = make_filename (gnupg_socketdir (), standard_name, NULL); + else + name = make_filename (standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + kbxd_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. Returns the file descriptor + * or terminates the process in case of an error. If CYGWIN is set a + * Cygwin compatible socket is created (Windows only). */ +static gnupg_fd_t +create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == ASSUAN_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + kbxd_exit (2); + } + + if (cygwin) + assuan_sock_set_flag (fd, "cygwin", 1); + + unaddr = xmalloc (sizeof *unaddr); + addr = (struct sockaddr*)unaddr; + + if (assuan_sock_set_sockaddr_un (name, addr, NULL)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), name); + else + log_error ("error preparing socket '%s': %s\n", + name, gpg_strerror (gpg_error_from_syserror ())); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + kbxd_exit (2); + } + + len = SUN_LEN (unaddr); + rc = assuan_sock_bind (fd, addr, len); + + /* Our error code mapping on W32CE returns EEXIST thus we also test + for this. */ + if (rc == -1 + && (errno == EADDRINUSE +#ifdef HAVE_W32_SYSTEM + || errno == EEXIST +#endif + )) + { + /* Check whether a keyboxd is already running. */ + if (!check_for_running_kbxd (1)) + { + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); + log_set_file (NULL); + log_error (_("a keyboxd is already running - " + "not starting a new one\n")); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + kbxd_exit (2); + } + gnupg_remove (unaddr->sun_path); + rc = assuan_sock_bind (fd, addr, len); + } + if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) + log_error (_("error getting nonce for the socket\n")); + if (rc == -1) + { + /* We use gpg_strerror here because it allows us to get strings + for some W32 socket error codes. */ + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); + + assuan_sock_close (fd); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + kbxd_exit (2); + } + + if (gnupg_chmod (unaddr->sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + unaddr->sun_path, strerror (errno)); + + if (listen (FD2INT(fd), listen_backlog ) == -1) + { + log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + kbxd_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + xfree (unaddr); + return fd; +} + + +/* Check that the directory for storing the public keys exists and + * create it if not. This function won't fail as it is only a + * convenience function and not strictly necessary. */ +static void +create_public_keys_directory (const char *home) +{ + char *fname; + struct stat statbuf; + + fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL); + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwxr-x")) + log_error (_("can't create directory '%s': %s\n"), + fname, strerror (errno) ); + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + if (gnupg_chmod (fname, "-rwxr-x")) + log_error (_("can't set permissions of '%s': %s\n"), + fname, strerror (errno)); + xfree (fname); +} + + +/* Create the directory only if the supplied directory name is the + * same as the default one. This way we avoid to create arbitrary + * directories when a non-default home directory is used. To cope + * with HOME, we compare only the suffix if we see that the default + * homedir does start with a tilde. We don't stop here in case of + * problems because other functions will throw an error anyway.*/ +static void +create_directories (void) +{ + struct stat statbuf; + const char *defhome = standard_homedir (); + char *home; + + home = make_filename (gnupg_homedir (), NULL); + if (stat (home, &statbuf)) + { + if (errno == ENOENT) + { + if ( +#ifdef HAVE_W32_SYSTEM + ( !compare_filenames (home, defhome) ) +#else + (*defhome == '~' + && (strlen (home) >= strlen (defhome+1) + && !strcmp (home + strlen(home) + - strlen (defhome+1), defhome+1))) + || (*defhome != '~' && !strcmp (home, defhome) ) +#endif + ) + { + if (gnupg_mkdir (home, "-rwx")) + log_error (_("can't create directory '%s': %s\n"), + home, strerror (errno) ); + else + { + if (!opt.quiet) + log_info (_("directory '%s' created\n"), home); + } + } + } + else + log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); + } + else if ( !S_ISDIR(statbuf.st_mode)) + { + log_error (_("can't use '%s' as home directory\n"), home); + } + else /* exists and is a directory. */ + { + create_public_keys_directory (home); + } + xfree (home); +} + + + +/* This is the worker for the ticker. It is called every few seconds + * and may only do fast operations. */ +static void +handle_tick (void) +{ + static time_t last_minute; + struct stat statbuf; + + if (!last_minute) + last_minute = time (NULL); + + /* Code to be run from time to time. */ +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) + { + check_own_socket (); + last_minute = time (NULL); + } +#endif + + + /* Check whether the homedir is still available. */ + if (!shutdown_pending + && (!have_homedir_inotify || !reliable_homedir_inotify) + && stat (gnupg_homedir (), &statbuf) && errno == ENOENT) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } +} + + +/* A global function which allows us to call the reload stuff from + * other places too. This is only used when build for W32. */ +void +kbxd_sighup_action (void) +{ + log_info ("SIGHUP received - " + "re-reading configuration and flushing cache\n"); + + reread_configuration (); +} + + +/* A helper function to handle SIGUSR2. */ +static void +kbxd_sigusr2_action (void) +{ + if (opt.verbose) + log_info ("SIGUSR2 received - no action\n"); + /* Nothing to do right now. */ +} + + +#ifndef HAVE_W32_SYSTEM +/* The signal handler for this program. It is expected to be run in + * its own thread and not in the context of a signal handler. */ +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + kbxd_sighup_action (); + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + /* Fixme: We need to see how to integrate pth dumping into our + logging system. */ + /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ + break; + + case SIGUSR2: + kbxd_sigusr2_action (); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i open connections\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + kbxd_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + kbxd_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif + +/* Check the nonce on a new connection. This is a NOP unless we + are using our Unix domain socket emulation under Windows. */ +static int +check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) +{ + if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) + { + log_info (_("error reading nonce on fd %d: %s\n"), + FD2INT(ctrl->thread_startup.fd), strerror (errno)); + assuan_sock_close (ctrl->thread_startup.fd); + xfree (ctrl); + return -1; + } + else + return 0; +} + + +static void * +do_start_connection_thread (ctrl_t ctrl) +{ + static unsigned int last_session_id; + unsigned int session_id; + + active_connections++; + kbxd_init_default_ctrl (ctrl); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d started\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + session_id = ++last_session_id; + if (!session_id) + session_id = ++last_session_id; + kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d terminated\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + kbxd_deinit_default_ctrl (ctrl); + xfree (ctrl); + active_connections--; + return NULL; +} + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce)) + { + log_error ("handler 0x%lx nonce check FAILED\n", + (unsigned long) npth_self()); + return NULL; + } + + return do_start_connection_thread (ctrl); +} + + +/* Connection handler loop. Wait for connection requests and spawn a + * thread after accepting a connection. */ +static void +handle_connections (gnupg_fd_t listen_fd) +{ + gpg_error_t err; + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int ret; + gnupg_fd_t fd; + int nfd; + int saved_errno; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#endif + int sock_inotify_fd = -1; + int home_inotify_fd = -1; + struct { + const char *name; + void *(*func) (void *arg); + gnupg_fd_t l_fd; + } listentbl[] = { + { "std", start_connection_thread }, + }; + + + ret = npth_attr_init(&tattr); + if (ret) + log_fatal ("error allocating thread attributes: %s\n", strerror (ret)); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifndef HAVE_W32_SYSTEM + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); +#else +# ifdef HAVE_W32CE_SYSTEM + /* Use a dummy event. */ + sigs = 0; + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); +# else + events[0] = INVALID_HANDLE_VALUE; +# endif +#endif + + if (disable_check_own_socket) + sock_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by socket removal: %s\n", + gpg_strerror (err)); + } + + if (disable_check_own_socket) + home_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, + gnupg_homedir ()))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by homedir removal: %s\n", + gpg_strerror (err)); + } + else + have_homedir_inotify = 1; + + FD_ZERO (&fdset); + FD_SET (FD2INT (listen_fd), &fdset); + nfd = FD2INT (listen_fd); + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + if (sock_inotify_fd > nfd) + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + + listentbl[0].l_fd = listen_fd; + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + for (;;) + { + /* Shutdown test. */ + if (shutdown_pending) + { + if (!active_connections) + break; /* ready */ + + /* Do not accept new connections but keep on running the + * loop to cope with the timer events. + * + * Note that we do not close the listening socket because a + * client trying to connect to that socket would instead + * restart a new keyboxd instance - which is unlikely the + * intention of a shutdown. */ + FD_ZERO (&fdset); + nfd = -1; + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + } + + read_fdset = fdset; + + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + /* Timeout. */ + handle_tick (); + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + } + npth_timersub (&abstime, &curtime, &timeout); + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + npth_sigev_sigmask ()); + saved_errno = errno; + + { + int signo; + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); + } +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + events, &events_set); + saved_errno = errno; + + /* This is valid even if npth_eselect returns an error. */ + if ((events_set & 1)) + kbxd_sigusr2_action (); +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + if (ret <= 0) + { + /* Interrupt or timeout. Will be handled when calculating the + * next timeout. */ + continue; + } + + /* The inotify fds are set even when a shutdown is pending (see + * above). So we must handle them in any case. To avoid that + * they trigger a second time we close them immediately. */ + if (sock_inotify_fd != -1 + && FD_ISSET (sock_inotify_fd, &read_fdset) + && gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME)) + { + shutdown_pending = 1; + close (sock_inotify_fd); + sock_inotify_fd = -1; + log_info ("socket file has been removed - shutting down\n"); + } + + if (home_inotify_fd != -1 + && FD_ISSET (home_inotify_fd, &read_fdset)) + { + shutdown_pending = 1; + close (home_inotify_fd); + home_inotify_fd = -1; + log_info ("homedir has been removed - shutting down\n"); + } + + if (!shutdown_pending) + { + int idx; + ctrl_t ctrl; + npth_t thread; + + for (idx=0; idx < DIM(listentbl); idx++) + { + if (listentbl[idx].l_fd == GNUPG_INVALID_FD) + continue; + if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) + continue; + + plen = sizeof paddr; + fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), + (struct sockaddr *)&paddr, &plen)); + if (fd == GNUPG_INVALID_FD) + { + log_error ("accept failed for %s: %s\n", + listentbl[idx].name, strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) + { + log_error ("error allocating connection data for %s: %s\n", + listentbl[idx].name, strerror (errno) ); + assuan_sock_close (fd); + } + else + { + ctrl->thread_startup.fd = fd; + ret = npth_create (&thread, &tattr, + listentbl[idx].func, ctrl); + if (ret) + { + log_error ("error spawning connection handler for %s:" + " %s\n", listentbl[idx].name, strerror (ret)); + assuan_sock_close (fd); + xfree (ctrl); + } + } + } + } + } + + if (sock_inotify_fd != -1) + close (sock_inotify_fd); + if (home_inotify_fd != -1) + close (home_inotify_fd); + cleanup (); + log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); + npth_attr_destroy (&tattr); +} + + + +/* Helper for check_own_socket. */ +static gpg_error_t +check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *mb = opaque; + put_membuf (mb, buffer, length); + return 0; +} + + +/* The thread running the actual check. We need to run this in a + * separate thread so that check_own_thread can be called from the + * timer tick. */ +static void * +check_own_socket_thread (void *arg) +{ + int rc; + char *sockname = arg; + assuan_context_t ctx = NULL; + membuf_t mb; + char *buffer; + + check_own_socket_running++; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + goto leave; + } + assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); + + rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + if (rc) + { + log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); + goto leave; + } + + init_membuf (&mb, 100); + rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, + NULL, NULL, NULL, NULL); + put_membuf (&mb, "", 1); + buffer = get_membuf (&mb, NULL); + if (rc || !buffer) + { + log_error ("sending command \"%s\" to my own socket failed: %s\n", + "GETINFO pid", gpg_strerror (rc)); + rc = 1; + } + else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) + { + log_error ("socket is now serviced by another server\n"); + rc = 1; + } + else if (opt.verbose > 1) + log_error ("socket is still served by this server\n"); + + xfree (buffer); + + leave: + xfree (sockname); + if (ctx) + assuan_release (ctx); + if (rc) + { + /* We may not remove the socket as it is now in use by another + * server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; + log_info ("this process is useless - shutting down\n"); + } + check_own_socket_running--; + return NULL; +} + + +/* Check whether we are still listening on our own socket. In case + * another keyboxd process started after us has taken ownership of our + * socket, we would linger around without any real task. Thus we + * better check once in a while whether we are really needed. */ +static void +check_own_socket (void) +{ + char *sockname; + npth_t thread; + npth_attr_t tattr; + int err; + + if (disable_check_own_socket) + return; + + if (check_own_socket_running || shutdown_pending) + return; /* Still running or already shutting down. */ + + sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); + if (!sockname) + return; /* Out of memory. */ + + err = npth_attr_init (&tattr); + if (err) + return; + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + npth_attr_destroy (&tattr); +} + + + +/* Figure out whether a keyboxd is available and running. Prints an + * error if not. If SILENT is true, no messages are printed. Returns + * 0 if the agent is running. */ +static int +check_for_running_kbxd (int silent) +{ + gpg_error_t err; + char *sockname; + assuan_context_t ctx = NULL; + + sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); + if (!sockname) + return gpg_error_from_syserror (); + + err = assuan_new (&ctx); + if (!err) + err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + xfree (sockname); + if (err) + { + if (!silent) + log_error (_("no keyboxd running in this session\n")); + + if (ctx) + assuan_release (ctx); + return -1; + } + + if (!opt.quiet && !silent) + log_info ("keyboxd running and available\n"); + + assuan_release (ctx); + return 0; +} diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h new file mode 100644 index 000000000..edef8975c --- /dev/null +++ b/kbx/keyboxd.h @@ -0,0 +1,156 @@ +/* keyboxd.h - Global definitions for keyboxd + * Copyright (C) 2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef KEYBOXD_H +#define KEYBOXD_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX +#include <gpg-error.h> + +#include <gcrypt.h> +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/sysutils.h" /* (gnupg_fd_t) */ + + +/* A large struct name "opt" to keep global flags */ +struct +{ + unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ + int verbose; /* Verbosity level */ + int quiet; /* Be as quiet as possible */ + int dry_run; /* Don't change any persistent data */ + int batch; /* Batch mode */ + + /* True if we are running detached from the tty. */ + int running_detached; + +} opt; + + +/* Bit values for the --debug option. */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ +#define DBG_CLOCK_VALUE 4096 /* debug timings (required build option). */ +#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ + +/* Test macros for the debug option. */ +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) +#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) + + +/* Declaration of a database request object. This is used for all + * database operation (search, insert, update, delete). */ +struct db_request_s; +typedef struct db_request_s *db_request_t; + +/* Forward reference for local definitions in command.c. */ +struct server_local_s; + +#if SIZEOF_UNSIGNED_LONG == 8 +# define SERVER_CONTROL_MAGIC 0x6b6579626f786420 +#else +# define SERVER_CONTROL_MAGIC 0x6b627864 +#endif + +/* Collection of data per session (aka connection). */ +struct server_control_s +{ + unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */ + int refcount; /* Count additional references to this object. */ + + /* Private data used to fire up the connection thread. We use this + * structure do avoid an extra allocation for only a few bytes while + * spawning a new connection thread. */ + struct { + gnupg_fd_t fd; + } thread_startup; + + /* Private data of the server (kbxserver.c). */ + struct server_local_s *server_local; + + /* Environment settings for the connection. */ + char *lc_messages; + + /* Miscellaneous info on the connection. */ + unsigned long client_pid; + int client_uid; + + /* Two database request objects used with a connection. They are + * auto-created as needed. */ + db_request_t opgp_req; + db_request_t x509_req; + + /* Flags for the current request. */ + unsigned int no_data_return : 1; /* Used by SEARCH and NEXT. */ +}; + + +/* This is a special version of the usual _() gettext macro. It + * assumes a server connection control variable with the name "ctrl" + * and uses that to translate a string according to the locale set for + * the connection. The macro LunderscoreIMPL is used by i18n to + * actually define the inline function when needed. */ +#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) +#define L_(a) keyboxd_Lunderscore (ctrl, (a)) +#define LunderscorePROTO \ + static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \ + const char *string) \ + GNUPG_GCC_ATTR_FORMAT_ARG(2); +#define LunderscoreIMPL \ + static inline const char * \ + keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \ + { \ + return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ + /* */: gettext (string); \ + } +#else +#define L_(a) (a) +#endif + + +/*-- keyboxd.c --*/ +void kbxd_exit (int rc) GPGRT_ATTR_NORETURN; +void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, + int printchar, int current, int total), + ctrl_t ctrl); +const char *get_kbxd_socket_name (void); +int get_kbxd_active_connection_count (void); +void kbxd_sighup_action (void); + + +/*-- kbxserver.c --*/ +gpg_error_t kbxd_write_data_line (ctrl_t ctrl, + const void *buffer_arg, size_t size); +void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int); + +#endif /*KEYBOXD_H*/ |