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