diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 8325f2564..1fcbd119f 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -1,841 +1,839 @@ /* protect-tool.c - A tool to test the secret key protection * Copyright (C) 2002, 2003, 2004, 2006 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "agent.h" #include "../common/i18n.h" #include "../common/get-passphrase.h" #include "../common/sysutils.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oArmor = 'a', oPassphrase = 'P', oProtect = 'p', oUnprotect = 'u', oNoVerbose = 500, oShadow, oShowShadowInfo, oShowKeygrip, oS2Kcalibration, oCanonical, oStore, oForce, oHaveCert, oNoFailOnExist, oHomedir, oPrompt, oStatusMsg, oDebugUseOCB, oAgentProgram }; struct rsa_secret_key_s { gcry_mpi_t n; /* public modulus */ gcry_mpi_t e; /* public exponent */ gcry_mpi_t d; /* exponent */ gcry_mpi_t p; /* prime p. */ gcry_mpi_t q; /* prime q. */ gcry_mpi_t u; /* inverse of p mod q. */ }; static int opt_armor; static int opt_canonical; static int opt_store; static int opt_force; static int opt_no_fail_on_exist; static int opt_have_cert; static const char *opt_passphrase; static char *opt_prompt; static int opt_status_msg; static const char *opt_agent_program; static int opt_debug_use_ocb; static char *get_passphrase (int promptno); static void release_passphrase (char *pw); static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (oProtect, "protect", "protect a private key"), ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"), ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"), ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"), ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""), ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", "verbose"), ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"), ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"), ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"), ARGPARSE_s_n (oHaveCert, "have-cert", "certificate to export provided on STDIN"), ARGPARSE_s_n (oStore, "store", "store the created key in the appropriate place"), ARGPARSE_s_n (oForce, "force", "force overwriting"), ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oPrompt, "prompt", "|ESCSTRING|use ESCSTRING as prompt in pinentry"), ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */ ARGPARSE_end () }; static const char * my_strusage (int level) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-protect-tool (" GNUPG_NAME ")"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n"); break; case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n" "Secret key maintenance tool\n"); break; default: p = NULL; } return p; } /* static void */ /* print_mpi (const char *text, gcry_mpi_t a) */ /* { */ /* char *buf; */ /* void *bufaddr = &buf; */ /* int rc; */ /* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */ /* if (rc) */ /* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */ /* else */ /* { */ /* log_info ("%s: %s\n", text, buf); */ /* gcry_free (buf); */ /* } */ /* } */ static unsigned char * make_canonical (const char *fname, const char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; unsigned char *result; rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); if (rc) { log_error ("invalid S-Expression in '%s' (off=%u): %s\n", fname, (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * make_advanced (const unsigned char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; char *result; rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen); if (rc) { log_error ("invalid canonical S-Expression (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * read_file (const char *fname, size_t *r_length) { - FILE *fp; + estream_t fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; - fp = stdin; -#ifdef HAVE_DOSISH_SYSTEM - setmode ( fileno(fp) , O_BINARY ); -#endif + fp = es_stdin; + es_set_binary (fp); buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); - nread = fread (buf+buflen, 1, NCHUNK, fp); - if (nread < NCHUNK && ferror (fp)) + nread = es_fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && es_ferror (fp)) { log_error ("error reading '[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; - fp = fopen (fname, "rb"); + fp = es_fopen (fname, "rb"); if (!fp) { log_error ("can't open '%s': %s\n", fname, strerror (errno)); return NULL; } - if (fstat (fileno(fp), &st)) + if (fstat (es_fileno (fp), &st)) { log_error ("can't stat '%s': %s\n", fname, strerror (errno)); - fclose (fp); + es_fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); - if (fread (buf, buflen, 1, fp) != 1) + if (es_fread (buf, buflen, 1, fp) != 1) { log_error ("error reading '%s': %s\n", fname, strerror (errno)); - fclose (fp); + es_fclose (fp); xfree (buf); return NULL; } - fclose (fp); + es_fclose (fp); } *r_length = buflen; return buf; } static unsigned char * read_key (const char *fname) { char *buf; size_t buflen; unsigned char *key; buf = read_file (fname, &buflen); if (!buf) return NULL; if (buflen >= 4 && !memcmp (buf, "Key:", 4)) { log_error ("Extended key format is not supported by this tool\n"); return NULL; } key = make_canonical (fname, buf, buflen); xfree (buf); return key; } static void read_and_protect (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; key = read_key (fname); if (!key) return; pw = get_passphrase (1); rc = agent_protect (key, pw, &result, &resultlen, 0, opt_debug_use_ocb? 1 : -1); release_passphrase (pw); xfree (key); if (rc) { log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_unprotect (ctrl_t ctrl, const char *fname) { gpg_error_t err; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; gnupg_isotime_t protected_at; key = read_key (fname); if (!key) return; err = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), protected_at, &result, &resultlen); release_passphrase (pw); xfree (key); if (err) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); log_error ("unprotecting the key failed: %s\n", gpg_strerror (err)); return; } if (opt.verbose) { if (*protected_at) log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n", protected_at, protected_at+4, protected_at+6, protected_at+9, protected_at+11, protected_at+13); else log_info ("key protection done at [unknown]\n"); } err = fixup_when_ecc_private_key (result, &resultlen); if (err) { log_error ("malformed key: %s\n", gpg_strerror (err)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_shadow (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; unsigned char dummy_info[] = "(8:313233342:43)"; key = read_key (fname); if (!key) return; rc = agent_shadow_key (key, dummy_info, &result); xfree (key); if (rc) { log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); return; } resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL); log_assert (resultlen); if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void show_shadow_info (const char *fname) { int rc; unsigned char *key; const unsigned char *info; size_t infolen; key = read_key (fname); if (!key) return; rc = agent_get_shadow_info (key, &info); xfree (key); if (rc) { log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); return; } infolen = gcry_sexp_canon_len (info, 0, NULL,NULL); log_assert (infolen); if (opt_armor) { char *p = make_advanced (info, infolen); if (!p) return; fwrite (p, strlen (p), 1, stdout); xfree (p); } else fwrite (info, infolen, 1, stdout); } static void show_file (const char *fname) { unsigned char *key; size_t keylen; char *p; key = read_key (fname); if (!key) return; keylen = gcry_sexp_canon_len (key, 0, NULL,NULL); log_assert (keylen); if (opt_canonical) { fwrite (key, keylen, 1, stdout); } else { p = make_advanced (key, keylen); if (p) { fwrite (p, strlen (p), 1, stdout); xfree (p); } } xfree (key); } static void show_keygrip (const char *fname) { unsigned char *key; gcry_sexp_t private; unsigned char grip[20]; int i; key = read_key (fname); if (!key) return; if (gcry_sexp_new (&private, key, 0, 0)) { log_error ("gcry_sexp_new failed\n"); return; } xfree (key); if (!gcry_pk_get_keygrip (private, grip)) { log_error ("can't calculate keygrip\n"); return; } gcry_sexp_release (private); for (i=0; i < 20; i++) printf ("%02X", grip[i]); putchar ('\n'); } int main (int argc, char **argv ) { gpgrt_argparse_t pargs; int cmd = 0; const char *fname; ctrl_t ctrl; early_system_init (); gpgrt_set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); setup_libgcrypt_logging (); gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oArmor: opt_armor=1; break; case oCanonical: opt_canonical=1; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oAgentProgram: opt_agent_program = pargs.r.ret_str; break; case oProtect: cmd = oProtect; break; case oUnprotect: cmd = oUnprotect; break; case oShadow: cmd = oShadow; break; case oShowShadowInfo: cmd = oShowShadowInfo; break; case oShowKeygrip: cmd = oShowKeygrip; break; case oS2Kcalibration: cmd = oS2Kcalibration; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; case oStore: opt_store = 1; break; case oForce: opt_force = 1; break; case oNoFailOnExist: opt_no_fail_on_exist = 1; break; case oHaveCert: opt_have_cert = 1; break; case oPrompt: opt_prompt = pargs.r.ret_str; break; case oStatusMsg: opt_status_msg = 1; break; case oDebugUseOCB: opt_debug_use_ocb = 1; break; default: pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) exit (2); fname = "-"; if (argc == 1) fname = *argv; else if (argc > 1) gpgrt_usage (1); /* Allocate an CTRL object. An empty object should be sufficient. */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno)); agent_exit (1); } /* Set the information which can't be taken from envvars. */ gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT, opt.verbose, opt_agent_program, NULL, NULL, NULL); if (opt_prompt) opt_prompt = percent_plus_unescape (opt_prompt, 0); if (cmd == oProtect) read_and_protect (fname); else if (cmd == oUnprotect) read_and_unprotect (ctrl, fname); else if (cmd == oShadow) read_and_shadow (fname); else if (cmd == oShowShadowInfo) show_shadow_info (fname); else if (cmd == oShowKeygrip) show_keygrip (fname); else if (cmd == oS2Kcalibration) { if (!opt.verbose) opt.verbose++; /* We need to see something. */ get_standard_s2k_count (); } else show_file (fname); xfree (ctrl); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* Return the passphrase string and ask the agent if it has not been set from the command line PROMPTNO select the prompt to display: 0 = default 1 = taken from the option --prompt 2 = for unprotecting a pkcs#12 object 3 = for protecting a new pkcs#12 object 4 = for protecting an imported pkcs#12 in our system */ static char * get_passphrase (int promptno) { char *pw; int err; const char *desc; char *orig_codeset; int repeat = 0; if (opt_passphrase) return xstrdup (opt_passphrase); orig_codeset = i18n_switchto_utf8 (); if (promptno == 1 && opt_prompt) { desc = opt_prompt; } else if (promptno == 2) { desc = _("Please enter the passphrase to unprotect the " "PKCS#12 object."); } else if (promptno == 3) { desc = _("Please enter the passphrase to protect the " "new PKCS#12 object."); repeat = 1; } else if (promptno == 4) { desc = _("Please enter the passphrase to protect the " "imported object within the GnuPG system."); repeat = 1; } else desc = _("Please enter the passphrase or the PIN\n" "needed to complete this operation."); i18n_switchback (orig_codeset); err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc, repeat, repeat, 1, &pw); if (err) { if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) log_info (_("cancelled\n")); else log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); agent_exit (0); } log_assert (pw); return pw; } static void release_passphrase (char *pw) { if (pw) { wipememory (pw, strlen (pw)); xfree (pw); } } /* Stub function. */ int agent_key_available (const unsigned char *grip) { (void)grip; return -1; /* Not available. */ } char * agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) { (void)ctrl; (void)key; (void)cache_mode; return NULL; } gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode) { gpg_error_t err; unsigned char *passphrase; size_t size; (void)ctrl; (void)desc_text; (void)prompt_text; (void)initial_errtext; (void)keyinfo; (void)cache_mode; *pininfo->pin = 0; /* Reset the PIN. */ passphrase = get_passphrase (0); size = strlen (passphrase); if (size >= pininfo->max_length) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (&pininfo->pin, passphrase, size); xfree (passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; err = pininfo->check_cb (pininfo); } else err = 0; return err; } /* Replacement for the function in findkey.c. Here we write the key * to stdout. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, time_t timestamp) { char hexgrip[40+4+1]; char *p; (void)force; (void)serialno; (void)keyref; (void)timestamp; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); p = make_advanced (buffer, length); if (p) { printf ("# Begin dump of %s\n%s%s# End dump of %s\n", hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip); xfree (p); } return 0; } diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c index 1ea10a8ad..1560fd30e 100644 --- a/dirmngr/dirmngr-client.c +++ b/dirmngr/dirmngr-client.c @@ -1,941 +1,941 @@ /* dirmngr-client.c - A client for the dirmngr daemon * Copyright (C) 2004, 2007 g10 Code GmbH * Copyright (C) 2002, 2003 Free Software Foundation, Inc. * * This file is part of DirMngr. * * DirMngr 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 2 of the License, or * (at your option) any later version. * * DirMngr 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 #include #include #include "../common/logging.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/asshelp.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/init.h" /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oLocal = 'l', oUrl = 'u', oOCSP = 500, oPing, oCacheCert, oValidate, oLookup, oLoadCRL, oSquidMode, oPEM, oEscapedPEM, oForceDefaultResponder }; /* The list of options as used by the argparse.c code. */ static gpgrt_opt_t opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, { oPing, "ping", 0, N_("check whether a dirmngr is running")}, { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, { oValidate, "validate", 0, N_("validate a certificate")}, { oLookup, "lookup", 0, N_("lookup a certificate")}, { oLocal, "local", 0, N_("lookup only locally stored certificates")}, { oUrl, "url", 0, N_("expect an URL for --lookup")}, { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, { oPEM, "pem", 0, N_("expect certificates in PEM format")}, { oForceDefaultResponder, "force-default-responder", 0, N_("force the use of the default OCSP responder")}, ARGPARSE_end () }; /* The usual structure for the program flags. */ static struct { int quiet; int verbose; const char *dirmngr_program; int force_default_responder; int pem; int escaped_pem; /* PEM is additional percent encoded. */ int url; /* Expect an URL. */ int local; /* Lookup up only local certificates. */ int use_ocsp; } opt; /* Communication structure for the certificate inquire callback. */ struct inq_cert_parm_s { assuan_context_t ctx; const unsigned char *cert; size_t certlen; }; /* Base64 conversion tables. */ static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static unsigned char asctobin[256]; /* runtime initialized */ /* Build the helptable for radix64 to bin conversion. */ static void init_asctobin (void) { static int initialized; int i; unsigned char *s; if (initialized) return; initialized = 1; for (i=0; i < 256; i++ ) asctobin[i] = 255; /* Used to detect invalid characters. */ for (s=bintoasc, i=0; *s; s++, i++) asctobin[*s] = i; } /* Prototypes. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen); static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename); static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern); static gpg_error_t squid_loop_body (assuan_context_t ctx); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "dirmngr-client (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr-client [options] " "[certfile|pattern] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr-client [options] [certfile|pattern]\n" "Test an X.509 certificate against a CRL or do an OCSP check\n" "The process returns 0 if the certificate is valid, 1 if it is\n" "not valid and other error codes for general failures\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv ) { gpgrt_argparse_t pargs; assuan_context_t ctx; gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; int cmd_ping = 0; int cmd_cache_cert = 0; int cmd_validate = 0; int cmd_lookup = 0; int cmd_loadcrl = 0; int cmd_squid_mode = 0; early_system_init (); gpgrt_set_strusage (my_strusage); log_set_prefix ("dirmngr-client", GPGRT_LOG_WITH_PREFIX); /* Register our string mapper with gpgrt. Usually done in * init_common_subsystems, but we don't need that here. */ gpgrt_set_fixed_string_mapper (map_static_macro_string); /* For W32 we need to initialize the socket subsystem. Because we don't use Pth we need to do this explicit. */ #ifdef HAVE_W32_SYSTEM { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif /*HAVE_W32_SYSTEM*/ /* Init Assuan. */ assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); /* Setup I18N. */ i18n_init(); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oQuiet: opt.quiet++; break; case oOCSP: opt.use_ocsp++; break; case oPing: cmd_ping = 1; break; case oCacheCert: cmd_cache_cert = 1; break; case oValidate: cmd_validate = 1; break; case oLookup: cmd_lookup = 1; break; case oUrl: opt.url = 1; break; case oLocal: opt.local = 1; break; case oLoadCRL: cmd_loadcrl = 1; break; case oPEM: opt.pem = 1; break; case oSquidMode: opt.pem = 1; opt.escaped_pem = 1; cmd_squid_mode = 1; break; case oForceDefaultResponder: opt.force_default_responder = 1; break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); if (log_get_errorcount (0)) exit (2); if (cmd_ping) err = 0; else if (cmd_lookup || cmd_loadcrl) { if (!argc) gpgrt_usage (1); err = 0; } else if (cmd_squid_mode) { err = 0; if (argc) gpgrt_usage (1); } else if (!argc) { err = read_certificate (NULL, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); } else if (argc == 1) { err = read_certificate (*argv, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from '%s': %s\n"), *argv, gpg_strerror (err)); } else { err = 0; gpgrt_usage (1); } if (log_get_errorcount (0)) exit (2); if (certbuflen > 20000) { log_error (_("certificate too large to make any sense\n")); exit (2); } err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program ? opt.dirmngr_program : gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR), ! cmd_ping, opt.verbose, 0, NULL, NULL); if (err) { log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (err)); exit (2); } if (cmd_ping) ; else if (cmd_squid_mode) { while (!(err = squid_loop_body (ctx))) ; if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; } else if (cmd_lookup) { int last_err = 0; for (; argc; argc--, argv++) { err = do_lookup (ctx, *argv); if (err) { log_error (_("lookup failed: %s\n"), gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_loadcrl) { int last_err = 0; for (; argc; argc--, argv++) { err = do_loadcrl (ctx, *argv); if (err) { log_error (_("loading CRL '%s' failed: %s\n"), *argv, gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_cache_cert) { err = do_cache (ctx, certbuf, certbuflen); xfree (certbuf); } else if (cmd_validate) { err = do_validate (ctx, certbuf, certbuflen); xfree (certbuf); } else { err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); } assuan_release (ctx); if (cmd_ping) { if (!opt.quiet) log_info (_("a dirmngr daemon is up and running\n")); return 0; } else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode) return err? 1:0; else if (cmd_cache_cert) { if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE ) { if (!opt.quiet) log_info (_("certificate already cached\n")); } else if (err) { log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return 1; } return 0; } else if (cmd_validate && err) { log_error (_("validation of certificate failed: %s\n"), gpg_strerror (err)); return 1; } else if (!err) { if (!opt.quiet) log_info (_("certificate is valid\n")); return 0; } else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) { if (!opt.quiet) log_info (_("certificate has been revoked\n")); return 1; } else { log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); return 2; } } /* Print status line from the assuan protocol. */ static gpg_error_t status_cb (void *opaque, const char *line) { (void)opaque; if (opt.verbose > 2) log_info (_("got status: '%s'\n"), line); return 0; } /* Print data as retrieved by the lookup function. */ static gpg_error_t data_cb (void *opaque, const void *buffer, size_t length) { gpg_error_t err; struct b64state *state = opaque; if (buffer) { err = b64enc_write (state, buffer, length); if (err) log_error (_("error writing base64 encoding: %s\n"), gpg_strerror (err)); } return 0; } /* Read the first PEM certificate from the file FNAME. If fname is NULL the next certificate is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { - FILE *fp; + estream_t fp; int c; int pos; int value; unsigned char *buf; size_t bufsize, buflen; enum { s_init, s_idle, s_lfseen, s_begin, s_b64_0, s_b64_1, s_b64_2, s_b64_3, s_waitend } state = s_init; init_asctobin (); - fp = fname? fopen (fname, "r") : stdin; + fp = fname? es_fopen (fname, "r") : es_stdin; if (!fp) - return gpg_error_from_errno (errno); + return gpg_error_from_syserror (); pos = 0; value = 0; bufsize = 8192; buf = xmalloc (bufsize); buflen = 0; - while ((c=getc (fp)) != EOF) + while ((c=es_getc (fp)) != EOF) { int escaped_c = 0; if (opt.escaped_pem) { if (c == '%') { char tmp[2]; - if ((c = getc(fp)) == EOF) + if ((c = es_getc(fp)) == EOF) break; tmp[0] = c; - if ((c = getc(fp)) == EOF) + if ((c = es_getc(fp)) == EOF) break; tmp[1] = c; if (!hexdigitp (tmp) || !hexdigitp (tmp+1)) { log_error ("invalid percent escape sequence\n"); state = s_idle; /* Force an error. */ /* Skip to end of line. */ - while ( (c=getc (fp)) != EOF && c != '\n') + while ( (c=es_getc (fp)) != EOF && c != '\n') ; goto ready; } c = xtoi_2 (tmp); escaped_c = 1; } else if (c == '\n') goto ready; /* Ready. */ } switch (state) { case s_idle: if (c == '\n') { state = s_lfseen; pos = 0; } break; case s_init: state = s_lfseen; /* fall through */ case s_lfseen: if (c != "-----BEGIN "[pos]) state = s_idle; else if (pos == 10) state = s_begin; else pos++; break; case s_begin: if (c == '\n') state = s_b64_0; break; case s_b64_0: case s_b64_1: case s_b64_2: case s_b64_3: { if (buflen >= bufsize) { bufsize += 8192; buf = xrealloc (buf, bufsize); } if (c == '-') state = s_waitend; else if ((c = asctobin[c & 0xff]) == 255 ) ; /* Just skip invalid base64 characters. */ else if (state == s_b64_0) { value = c << 2; state = s_b64_1; } else if (state == s_b64_1) { value |= (c>>4)&3; buf[buflen++] = value; value = (c<<4)&0xf0; state = s_b64_2; } else if (state == s_b64_2) { value |= (c>>2)&15; buf[buflen++] = value; value = (c<<6)&0xc0; state = s_b64_3; } else { value |= c&0x3f; buf[buflen++] = value; state = s_b64_0; } } break; case s_waitend: /* Note that we do not check that the base64 decoder has been left in the expected state. We assume that the PEM header is just fine. However we need to wait for the real LF and not a trailing percent escaped one. */ if (c== '\n' && !escaped_c) goto ready; break; default: BUG(); } } ready: if (fname) - fclose (fp); + es_fclose (fp); if (state == s_init && c == EOF) { xfree (buf); return gpg_error (GPG_ERR_EOF); } else if (state != s_waitend) { log_error ("no certificate or invalid encoded\n"); xfree (buf); return gpg_error (GPG_ERR_INV_ARMOR); } *rbuf = buf; *rbuflen = buflen; return 0; } /* Read a binary certificate from the file FNAME. If fname is NULL the file is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { gpg_error_t err; - FILE *fp; + estream_t fp; unsigned char *buf; size_t nread, bufsize, buflen; if (opt.pem) return read_pem_certificate (fname, rbuf, rbuflen); else if (fname) { /* A filename has been given. Let's just assume it is in PEM format and decode it, and fall back to interpreting it as binary certificate if that fails. */ err = read_pem_certificate (fname, rbuf, rbuflen); if (! err) return 0; } - fp = fname? fopen (fname, "rb") : stdin; + fp = fname? es_fopen (fname, "rb") : es_stdin; if (!fp) - return gpg_error_from_errno (errno); + return gpg_error_from_syserror (); buf = NULL; bufsize = buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); - nread = fread (buf+buflen, 1, NCHUNK, fp); - if (nread < NCHUNK && ferror (fp)) + nread = es_fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && es_ferror (fp)) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); xfree (buf); if (fname) - fclose (fp); + es_fclose (fp); return err; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK if (fname) - fclose (fp); + es_fclose (fp); *rbuf = buf; *rbuflen = buflen; return 0; } /* Callback for the inquire fiunction to send back the certificate. */ static gpg_error_t inq_cert (void *opaque, const char *line) { struct inq_cert_parm_s *parm = opaque; gpg_error_t err; if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) { err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); } else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back and empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDISSUERCERT", 14) && (line[14] == ' ' || !line[14])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else { log_info (_("unsupported inquiry '%s'\n"), line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); /* Note that this error will let assuan_transact terminate immediately instead of return the error to the caller. It is not clear whether this is the desired behaviour - it may change in future. */ } return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, (opt.use_ocsp && opt.force_default_responder ? "CHECKOCSP --force-default-responder" : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "CACHECERT", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using dirmngrs internal validate feature. Return a proper error code. */ static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "VALIDATE", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Load a CRL into the dirmngr. */ static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename) { gpg_error_t err; const char *s; char *fname, *line, *p; if (opt.url) fname = xstrdup (filename); else { #ifdef HAVE_CANONICALIZE_FILE_NAME fname = canonicalize_file_name (filename); if (!fname) { log_error ("error canonicalizing '%s': %s\n", filename, strerror (errno)); return gpg_error (GPG_ERR_GENERAL); } #else fname = xstrdup (filename); #endif if (*fname != '/') { log_error (_("absolute file name expected\n")); return gpg_error (GPG_ERR_GENERAL); } } line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); p = stpcpy (line, "LOADCRL "); if (opt.url) p = stpcpy (p, "--url "); for (s = fname; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); xfree (line); xfree (fname); return err; } /* Do a LDAP lookup using PATTERN and print the result in a base-64 encoded format. */ static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern) { gpg_error_t err; const unsigned char *s; char *line, *p; struct b64state state; if (opt.verbose) log_info (_("looking up '%s'\n"), pattern); err = b64enc_start (&state, stdout, NULL); if (err) return err; line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); p = stpcpy (line, "LOOKUP "); if (opt.url) p = stpcpy (p, "--url "); if (opt.local) p = stpcpy (p, "--cache-only "); for (s=pattern; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, data_cb, &state, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); err = b64enc_finish (&state); xfree (line); return err; } /* The body of an endless loop: Read a line from stdin, retrieve the certificate from it, validate it and print "ERR" or "OK" to stdout. Continue. */ static gpg_error_t squid_loop_body (assuan_context_t ctx) { gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; err = read_pem_certificate (NULL, &certbuf, &certbuflen); if (gpg_err_code (err) == GPG_ERR_EOF) return err; if (err) { log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); puts ("ERROR"); return 0; } err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); if (!err) { if (opt.verbose) log_info (_("certificate is valid\n")); puts ("OK"); } else { if (!opt.quiet) { if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) log_info (_("certificate has been revoked\n")); else log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); } puts ("ERROR"); } fflush (stdout); return 0; } diff --git a/g10/keydb.c b/g10/keydb.c index 2db231136..3938d7e16 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -1,1953 +1,1953 @@ /* keydb.c - key database dispatcher * Copyright (C) 2001-2013 Free Software Foundation, Inc. * Copyright (C) 2001-2015 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 . */ #include #include #include #include #include #include #include #include #include "gpg.h" #include "../common/util.h" #include "../common/sysutils.h" #include "options.h" #include "main.h" /*try_make_homedir ()*/ #include "packet.h" #include "keyring.h" #include "../kbx/keybox.h" #include "keydb.h" #include "../common/i18n.h" #include "keydb-private.h" /* For struct keydb_handle_s */ static int active_handles; static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; static int used_resources; /* A pointer used to check for the primary key database by comparing to the struct resource_item's TOKEN. */ static void *primary_keydb; /* Whether we have successfully registered any resource. */ static int any_registered; /* Looking up keys is expensive. To hide the cost, we cache whether keys exist in the key database. Then, if we know a key does not exist, we don't have to spend time looking it up. This particularly helps the --list-sigs and --check-sigs commands. The cache stores the results in a hash using separate chaining. Concretely: we use the LSB of the keyid to index the hash table and each bucket consists of a linked list of entries. An entry consists of the 64-bit key id. If a key id is not in the cache, then we don't know whether it is in the DB or not. To simplify the cache consistency protocol, we simply flush the whole cache whenever a key is inserted or updated. */ #define KID_NOT_FOUND_CACHE_BUCKETS 256 static struct kid_not_found_cache_bucket * kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS]; struct kid_not_found_cache_bucket { struct kid_not_found_cache_bucket *next; u32 kid[2]; }; struct { unsigned int count; /* The current number of entries in the hash table. */ unsigned int peak; /* The peak of COUNT. */ unsigned int flushes; /* The number of flushes. */ } kid_not_found_stats; struct { unsigned int handles; /* Number of handles created. */ unsigned int locks; /* Number of locks taken. */ unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls. */ unsigned int get_keyblocks; /* Number of keydb_get_keyblock calls. */ unsigned int build_keyblocks; /* Number of build_keyblock_image calls. */ unsigned int update_keyblocks;/* Number of update_keyblock calls. */ unsigned int insert_keyblocks;/* Number of update_keyblock calls. */ unsigned int delete_keyblocks;/* Number of delete_keyblock calls. */ unsigned int search_resets; /* Number of keydb_search_reset calls. */ unsigned int found; /* Number of successful keydb_search calls. */ unsigned int found_cached; /* Ditto but from the cache. */ unsigned int notfound; /* Number of failed keydb_search calls. */ unsigned int notfound_cached; /* Ditto but from the cache. */ } keydb_stats; static int lock_all (KEYDB_HANDLE hd); static void unlock_all (KEYDB_HANDLE hd); /* Check whether the keyid KID is in key id is definitely not in the database. Returns: 0 - Indeterminate: the key id is not in the cache; we don't know whether the key is in the database or not. If you want a definitive answer, you'll need to perform a lookup. 1 - There is definitely no key with this key id in the database. We searched for a key with this key id previously, but we didn't find it in the database. */ static int kid_not_found_p (u32 *kid) { struct kid_not_found_cache_bucket *k; for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) { if (DBG_CACHE) log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n", (ulong)kid[0], (ulong)kid[1]); return 1; } if (DBG_CACHE) log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n", (ulong)kid[0], (ulong)kid[1]); return 0; } /* Insert the keyid KID into the kid_not_found_cache. FOUND is whether the key is in the key database or not. Note this function does not check whether the key id is already in the cache. As such, kid_not_found_p() should be called first. */ static void kid_not_found_insert (u32 *kid) { struct kid_not_found_cache_bucket *k; if (DBG_CACHE) log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n", (ulong)kid[0], (ulong)kid[1]); k = xmalloc (sizeof *k); k->kid[0] = kid[0]; k->kid[1] = kid[1]; k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k; kid_not_found_stats.count++; } /* Flush the kid not found cache. */ static void kid_not_found_flush (void) { struct kid_not_found_cache_bucket *k, *knext; int i; if (DBG_CACHE) log_debug ("keydb: kid_not_found_flush\n"); if (!kid_not_found_stats.count) return; for (i=0; i < DIM(kid_not_found_cache); i++) { for (k = kid_not_found_cache[i]; k; k = knext) { knext = k->next; xfree (k); } kid_not_found_cache[i] = NULL; } if (kid_not_found_stats.count > kid_not_found_stats.peak) kid_not_found_stats.peak = kid_not_found_stats.count; kid_not_found_stats.count = 0; kid_not_found_stats.flushes++; } static void keyblock_cache_clear (struct keydb_handle_s *hd) { hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY; iobuf_close (hd->keyblock_cache.iobuf); hd->keyblock_cache.iobuf = NULL; hd->keyblock_cache.resource = -1; hd->keyblock_cache.offset = -1; } /* Handle the creation of a keyring or a keybox if it does not yet exist. Take into account that other processes might have the keyring/keybox already locked. This lock check does not work if the directory itself is not yet available. If IS_BOX is true the filename is expected to refer to a keybox. If FORCE_CREATE is true the keyring or keybox will be created. Return 0 if it is okay to access the specified file. */ static gpg_error_t maybe_create_keyring_or_box (char *filename, int is_box, int force_create) { gpg_err_code_t ec; dotlock_t lockhd = NULL; IOBUF iobuf; int rc; mode_t oldmask; char *last_slash_in_filename; char *bak_fname = NULL; char *tmp_fname = NULL; int save_slash; /* A quick test whether the filename already exists. */ if (!gnupg_access (filename, F_OK)) return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES); /* If we don't want to create a new file at all, there is no need to go any further - bail out right here. */ if (!force_create) return gpg_error (GPG_ERR_ENOENT); /* First of all we try to create the home directory. Note, that we don't do any locking here because any sane application of gpg would create the home directory by itself and not rely on gpg's tricky auto-creation which is anyway only done for certain home directory name pattern. */ last_slash_in_filename = strrchr (filename, DIRSEP_C); #if HAVE_W32_SYSTEM { /* Windows may either have a slash or a backslash. Take care of it. */ char *p = strrchr (filename, '/'); if (!last_slash_in_filename || p > last_slash_in_filename) last_slash_in_filename = p; } #endif /*HAVE_W32_SYSTEM*/ if (!last_slash_in_filename) return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should not happen though. */ save_slash = *last_slash_in_filename; *last_slash_in_filename = 0; if (gnupg_access(filename, F_OK)) { static int tried; if (!tried) { tried = 1; try_make_homedir (filename); } if ((ec = gnupg_access (filename, F_OK))) { rc = gpg_error (ec); *last_slash_in_filename = save_slash; goto leave; } } *last_slash_in_filename = save_slash; /* To avoid races with other instances of gpg trying to create or update the keyring (it is removed during an update for a short time), we do the next stuff in a locked state. */ lockhd = dotlock_create (filename, 0); if (!lockhd) { rc = 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 keyring is not really useful at all. */ if (opt.verbose) log_info ("can't allocate lock for '%s': %s\n", filename, gpg_strerror (rc)); if (!force_create) return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */ else return rc; } if ( dotlock_take (lockhd, -1) ) { rc = gpg_error_from_syserror (); /* This is something bad. Probably a stale lockfile. */ log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc)); goto leave; } /* Now the real test while we are locked. */ /* Gpg either uses pubring.gpg or pubring.kbx and thus different * lock files. Now, when one gpg process is updating a pubring.gpg * and thus holding the corresponding lock, a second gpg process may * get to here at the time between the two rename operation used by * the first process to update pubring.gpg. The lock taken above * may not protect the second process if it tries to create a * pubring.kbx file which would be protected by a different lock * file. * * We can detect this case by checking that the two temporary files * used by the update code exist at the same time. In that case we * do not create a new file but act as if FORCE_CREATE has not been * given. Obviously there is a race between our two checks but the * worst thing is that we won't create a new file, which is better * than to accidentally creating one. */ rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname); if (rc) goto leave; if (!gnupg_access (filename, F_OK)) { rc = 0; /* Okay, we may access the file now. */ goto leave; } if (!gnupg_access (bak_fname, F_OK) && !gnupg_access (tmp_fname, F_OK)) { /* Very likely another process is updating a pubring.gpg and we should not create a pubring.kbx. */ rc = gpg_error (GPG_ERR_ENOENT); goto leave; } /* The file does not yet exist, create it now. */ oldmask = umask (077); if (is_secured_filename (filename)) { iobuf = NULL; gpg_err_set_errno (EPERM); } else iobuf = iobuf_create (filename, 0); umask (oldmask); if (!iobuf) { rc = gpg_error_from_syserror (); if (is_box) log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); else log_error (_("error creating keyring '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } iobuf_close (iobuf); /* Must invalidate that ugly cache */ iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename); /* 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. */ if (is_box) { estream_t fp = es_fopen (filename, "wb"); if (!fp) rc = gpg_error_from_syserror (); else { rc = _keybox_write_header_blob (fp, 1); es_fclose (fp); } if (rc) { if (is_box) log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); else log_error (_("error creating keyring '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } } if (!opt.quiet) { if (is_box) log_info (_("keybox '%s' created\n"), filename); else log_info (_("keyring '%s' created\n"), filename); } rc = 0; leave: if (lockhd) { dotlock_release (lockhd); dotlock_destroy (lockhd); } xfree (bak_fname); xfree (tmp_fname); return rc; } /* Helper for keydb_add_resource. Opens FILENAME to figure out the resource type. Returns the specified file's likely type. If the file does not exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0. Otherwise, tries to figure out the file's type. This is either KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has the OpenPGP flag set, then R_OPENPGP is also set. */ static KeydbResourceType rt_from_file (const char *filename, int *r_found, int *r_openpgp) { u32 magic; unsigned char verbuf[4]; - FILE *fp; + estream_t fp; KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; *r_found = *r_openpgp = 0; - fp = fopen (filename, "rb"); + fp = es_fopen (filename, "rb"); if (fp) { *r_found = 1; - if (fread (&magic, 4, 1, fp) == 1 ) + if (es_fread (&magic, 4, 1, fp) == 1 ) { if (magic == 0x13579ace || magic == 0xce9a5713) ; /* GDBM magic - not anymore supported. */ - else if (fread (&verbuf, 4, 1, fp) == 1 + else if (es_fread (&verbuf, 4, 1, fp) == 1 && verbuf[0] == 1 - && fread (&magic, 4, 1, fp) == 1 + && es_fread (&magic, 4, 1, fp) == 1 && !memcmp (&magic, "KBXf", 4)) { if ((verbuf[3] & 0x02)) *r_openpgp = 1; rt = KEYDB_RESOURCE_TYPE_KEYBOX; } else rt = KEYDB_RESOURCE_TYPE_KEYRING; } else /* Maybe empty: assume keyring. */ rt = KEYDB_RESOURCE_TYPE_KEYRING; - fclose (fp); + es_fclose (fp); } return rt; } char * keydb_search_desc_dump (struct keydb_search_desc *desc) { char b[MAX_FORMATTED_FINGERPRINT_LEN + 1]; char fpr[2 * MAX_FINGERPRINT_LEN + 1]; #if MAX_FINGERPRINT_LEN < UBID_LEN || MAX_FINGERPRINT_LEN < KEYGRIP_LEN #error MAX_FINGERPRINT_LEN is shorter than KEYGRIP or UBID length. #endif switch (desc->mode) { case KEYDB_SEARCH_MODE_EXACT: return xasprintf ("EXACT: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_SUBSTR: return xasprintf ("SUBSTR: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAIL: return xasprintf ("MAIL: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAILSUB: return xasprintf ("MAILSUB: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAILEND: return xasprintf ("MAILEND: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_WORDS: return xasprintf ("WORDS: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_SHORT_KID: return xasprintf ("SHORT_KID: '%s'", format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b))); case KEYDB_SEARCH_MODE_LONG_KID: return xasprintf ("LONG_KID: '%s'", format_keyid (desc->u.kid, KF_LONG, b, sizeof (b))); case KEYDB_SEARCH_MODE_FPR: bin2hex (desc->u.fpr, desc->fprlen, fpr); return xasprintf ("FPR%02d: '%s'", desc->fprlen, format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_ISSUER: return xasprintf ("ISSUER: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_ISSUER_SN: return xasprintf ("ISSUER_SN: '#%.*s/%s'", (int)desc->snlen,desc->sn, desc->u.name); case KEYDB_SEARCH_MODE_SN: return xasprintf ("SN: '%.*s'", (int)desc->snlen, desc->sn); case KEYDB_SEARCH_MODE_SUBJECT: return xasprintf ("SUBJECT: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_KEYGRIP: bin2hex (desc[0].u.grip, KEYGRIP_LEN, fpr); return xasprintf ("KEYGRIP: %s", fpr); case KEYDB_SEARCH_MODE_UBID: bin2hex (desc[0].u.ubid, UBID_LEN, fpr); return xasprintf ("UBID: %s", fpr); case KEYDB_SEARCH_MODE_FIRST: return xasprintf ("FIRST"); case KEYDB_SEARCH_MODE_NEXT: return xasprintf ("NEXT"); default: return xasprintf ("Bad search mode (%d)", desc->mode); } } /* Register a resource (keyring or keybox). The first keyring or * keybox that is added using this function is created if it does not * already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set. * * FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants. * * URL must have the following form: * * gnupg-ring:filename = plain keyring * gnupg-kbx:filename = keybox file * filename = check file's type (create as a plain keyring) * * Note: on systems with drive letters (Windows) invalid URLs (i.e., * those with an unrecognized part before the ':' such as "c:\...") * will silently be treated as bare filenames. On other systems, such * URLs will cause this function to return GPG_ERR_GENERAL. * * If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring * and the file ends in ".gpg", then this function also checks if a * file with the same name, but the extension ".kbx" exists, is a * keybox and the OpenPGP flag is set. If so, this function opens * that resource instead. * * If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and * the URL ends in ".kbx", then this function will try opening the * same URL, but with the extension ".gpg". If that file is a keybox * with the OpenPGP flag set or it is a keyring, then we use that * instead. * * If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the * file should be created and the file's extension is ".gpg" then we * replace the extension with ".kbx". * * If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a * keyring (not a keybox), then this resource is considered the * primary resource. This is used by keydb_locate_writable(). If * another primary keyring is set, then that keyring is considered the * primary. * * If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a * keyring (not a keybox), then the keyring is marked as read only and * operations just as keyring_insert_keyblock will return * GPG_ERR_ACCESS. */ gpg_error_t keydb_add_resource (const char *url, unsigned int flags) { /* The file named by the URL (i.e., without the prototype). */ const char *resname = url; char *filename = NULL; int create; int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY); int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT); int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF); gpg_error_t err = 0; KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; void *token; /* Create the resource if it is the first registered one. */ create = (!read_only && !any_registered); if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) ) { rt = KEYDB_RESOURCE_TYPE_KEYRING; resname += 11; } else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kbx:", 10) ) { rt = KEYDB_RESOURCE_TYPE_KEYBOX; resname += 10; } #if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) else if (strchr (resname, ':')) { log_error ("invalid key resource URL '%s'\n", url ); err = gpg_error (GPG_ERR_GENERAL); goto leave; } #endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ if (*resname != DIRSEP_C #ifdef HAVE_W32_SYSTEM && *resname != '/' /* Fixme: does not handle drive letters. */ #endif ) { /* Do tilde expansion etc. */ if (strchr (resname, DIRSEP_C) #ifdef HAVE_W32_SYSTEM || strchr (resname, '/') /* Windows also accepts this. */ #endif ) filename = make_filename (resname, NULL); else filename = make_filename (gnupg_homedir (), resname, NULL); } else filename = xstrdup (resname); /* See whether we can determine the filetype. */ if (rt == KEYDB_RESOURCE_TYPE_NONE) { int found, openpgp_flag; int pass = 0; size_t filenamelen; check_again: filenamelen = strlen (filename); rt = rt_from_file (filename, &found, &openpgp_flag); if (found) { /* The file exists and we have the resource type in RT. Now let us check whether in addition to the "pubring.gpg" a "pubring.kbx with openpgp keys exists. This is so that GPG 2.1 will use an existing "pubring.kbx" by default iff that file has been created or used by 2.1. This check is needed because after creation or use of the kbx file with 2.1 an older version of gpg may have created a new pubring.gpg for its own use. */ if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg")) { strcpy (filename+filenamelen-4, ".kbx"); if ((rt_from_file (filename, &found, &openpgp_flag) == KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag) rt = KEYDB_RESOURCE_TYPE_KEYBOX; else /* Restore filename */ strcpy (filename+filenamelen-4, ".gpg"); } } else if (!pass && is_gpgvdef && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx")) { /* Not found but gpgv's default "trustedkeys.kbx" file has been requested. We did not found it so now check whether a "trustedkeys.gpg" file exists and use that instead. */ KeydbResourceType rttmp; strcpy (filename+filenamelen-4, ".gpg"); rttmp = rt_from_file (filename, &found, &openpgp_flag); if (found && ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag) || (rttmp == KEYDB_RESOURCE_TYPE_KEYRING))) rt = rttmp; else /* Restore filename */ strcpy (filename+filenamelen-4, ".kbx"); } else if (!pass && is_default && create && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg")) { /* The file does not exist, the default resource has been requested, the file shall be created, and the file has a ".gpg" suffix. Change the suffix to ".kbx" and try once more. This way we achieve that we open an existing ".gpg" keyring, but create a new keybox file with an ".kbx" suffix. */ strcpy (filename+filenamelen-4, ".kbx"); pass++; goto check_again; } else /* No file yet: create keybox. */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; } switch (rt) { case KEYDB_RESOURCE_TYPE_NONE: log_error ("unknown type of key resource '%s'\n", url ); err = gpg_error (GPG_ERR_GENERAL); goto leave; case KEYDB_RESOURCE_TYPE_KEYRING: err = maybe_create_keyring_or_box (filename, 0, create); if (err) goto leave; if (keyring_register_filename (filename, read_only, &token)) { if (used_resources >= MAX_KEYDB_RESOURCES) err = gpg_error (GPG_ERR_RESOURCE_LIMIT); else { if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) primary_keydb = token; all_resources[used_resources].type = rt; all_resources[used_resources].u.kr = NULL; /* Not used here */ all_resources[used_resources].token = token; used_resources++; } } else { /* This keyring was already registered, so ignore it. However, we can still mark it as primary even if it was already registered. */ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) primary_keydb = token; } break; case KEYDB_RESOURCE_TYPE_KEYBOX: { err = maybe_create_keyring_or_box (filename, 1, create); if (err) goto leave; err = keybox_register_file (filename, 0, &token); if (!err) { if (used_resources >= MAX_KEYDB_RESOURCES) err = gpg_error (GPG_ERR_RESOURCE_LIMIT); else { KEYBOX_HANDLE kbxhd; if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) primary_keydb = token; all_resources[used_resources].type = rt; all_resources[used_resources].u.kb = NULL; /* Not used here */ all_resources[used_resources].token = token; /* Do a compress run if needed and no other user is * currently using the keybox. */ kbxhd = keybox_new_openpgp (token, 0); if (kbxhd) { if (!keybox_lock (kbxhd, 1, 0)) { keybox_compress (kbxhd); keybox_lock (kbxhd, 0, 0); } keybox_release (kbxhd); } used_resources++; } } else if (gpg_err_code (err) == GPG_ERR_EEXIST) { /* Already registered. We will mark it as the primary key if requested. */ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) primary_keydb = token; } } break; default: log_error ("resource type of '%s' not supported\n", url); err = gpg_error (GPG_ERR_GENERAL); goto leave; } /* fixme: check directory permissions and print a warning */ leave: if (err) { log_error (_("keyblock resource '%s': %s\n"), filename, gpg_strerror (err)); write_status_error ("add_keyblock_resource", err); } else any_registered = 1; xfree (filename); return err; } void keydb_dump_stats (void) { log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n", keydb_stats.handles, keydb_stats.locks, keydb_stats.parse_keyblocks, keydb_stats.get_keyblocks); log_info (" build=%u update=%u insert=%u delete=%u\n", keydb_stats.build_keyblocks, keydb_stats.update_keyblocks, keydb_stats.insert_keyblocks, keydb_stats.delete_keyblocks); log_info (" reset=%u found=%u not=%u cache=%u not=%u\n", keydb_stats.search_resets, keydb_stats.found, keydb_stats.notfound, keydb_stats.found_cached, keydb_stats.notfound_cached); log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n", kid_not_found_stats.count, kid_not_found_stats.peak, kid_not_found_stats.flushes); } /* keydb_new diverts to here in non-keyboxd mode. HD is just the * calloced structure with the handle type initialized. */ gpg_error_t internal_keydb_init (KEYDB_HANDLE hd) { gpg_error_t err = 0; int i, j; int die = 0; int reterrno; log_assert (!hd->use_keyboxd); hd->found = -1; hd->saved_found = -1; hd->is_reset = 1; log_assert (used_resources <= MAX_KEYDB_RESOURCES); for (i=j=0; ! die && i < used_resources; i++) { switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYRING: hd->active[j].type = all_resources[i].type; hd->active[j].token = all_resources[i].token; hd->active[j].u.kr = keyring_new (all_resources[i].token); if (!hd->active[j].u.kr) { reterrno = errno; die = 1; } j++; break; case KEYDB_RESOURCE_TYPE_KEYBOX: hd->active[j].type = all_resources[i].type; hd->active[j].token = all_resources[i].token; hd->active[j].u.kb = keybox_new_openpgp (all_resources[i].token, 0); if (!hd->active[j].u.kb) { reterrno = errno; die = 1; } j++; break; } } hd->used = j; active_handles++; keydb_stats.handles++; if (die) err = gpg_error_from_errno (reterrno); return err; } /* Free all non-keyboxd resources owned by the database handle. * keydb_release diverts to here. */ void internal_keydb_deinit (KEYDB_HANDLE hd) { int i; log_assert (!hd->use_keyboxd); log_assert (active_handles > 0); active_handles--; hd->keep_lock = 0; unlock_all (hd); for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: keyring_release (hd->active[i].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_release (hd->active[i].u.kb); break; } } keyblock_cache_clear (hd); } /* Take a lock on the files immediately and not only during insert or * update. This lock is released with keydb_release. */ gpg_error_t internal_keydb_lock (KEYDB_HANDLE hd) { gpg_error_t err; log_assert (!hd->use_keyboxd); err = lock_all (hd); if (!err) hd->keep_lock = 1; return err; } /* Set a flag on the handle to suppress use of cached results. This * is required for updating a keyring and for key listings. Fixme: * Using a new parameter for keydb_new might be a better solution. */ void keydb_disable_caching (KEYDB_HANDLE hd) { if (hd && !hd->use_keyboxd) hd->no_caching = 1; } /* Return the file name of the resource in which the current search * result was found or, if there is no search result, the filename of * the current resource (i.e., the resource that the file position * points to). Note: the filename is not necessarily the URL used to * open it! * * This function only returns NULL if no handle is specified, in all * other error cases an empty string is returned. */ const char * keydb_get_resource_name (KEYDB_HANDLE hd) { int idx; const char *s = NULL; if (!hd) return NULL; if (hd->use_keyboxd) return "[keyboxd]"; if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else idx = 0; switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: s = NULL; break; case KEYDB_RESOURCE_TYPE_KEYRING: s = keyring_get_resource_name (hd->active[idx].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: s = keybox_get_resource_name (hd->active[idx].u.kb); break; } return s? s: ""; } static int lock_all (KEYDB_HANDLE hd) { int i, rc = 0; /* Fixme: This locking scheme may lead to a deadlock if the resources are not added in the same order by all processes. We are currently only allowing one resource so it is not a problem. [Oops: Who claimed the latter] To fix this we need to use a lock file to protect lock_all. */ for (i=0; !rc && i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: rc = keyring_lock (hd->active[i].u.kr, 1); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_lock (hd->active[i].u.kb, 1, -1); break; } } if (rc) { /* Revert the already taken locks. */ for (i--; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: keyring_lock (hd->active[i].u.kr, 0); break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_lock (hd->active[i].u.kb, 0, 0); break; } } } else { hd->locked = 1; keydb_stats.locks++; } return rc; } static void unlock_all (KEYDB_HANDLE hd) { int i; if (!hd->locked || hd->keep_lock) return; for (i=hd->used-1; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: keyring_lock (hd->active[i].u.kr, 0); break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_lock (hd->active[i].u.kb, 0, 0); break; } } hd->locked = 0; } /* Save the last found state and invalidate the current selection * (i.e., the entry selected by keydb_search() is invalidated and * something like keydb_get_keyblock() will return an error). This * does not change the file position. This makes it possible to do * something like: * * keydb_search (hd, ...); // Result 1. * keydb_push_found_state (hd); * keydb_search_reset (hd); * keydb_search (hd, ...); // Result 2. * keydb_pop_found_state (hd); * keydb_get_keyblock (hd, ...); // -> Result 1. * * Note: it is only possible to save a single save state at a time. * In other words, the save stack only has room for a single * instance of the state. */ /* FIXME(keyboxd): This function is used only at one place - see how * we can avoid it. */ void keydb_push_found_state (KEYDB_HANDLE hd) { if (!hd) return; if (hd->found < 0 || hd->found >= hd->used) { hd->saved_found = -1; return; } switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: keyring_push_found_state (hd->active[hd->found].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_push_found_state (hd->active[hd->found].u.kb); break; } hd->saved_found = hd->found; hd->found = -1; } /* Restore the previous save state. If the saved state is NULL or invalid, this is a NOP. */ /* FIXME(keyboxd): This function is used only at one place - see how * we can avoid it. */ void keydb_pop_found_state (KEYDB_HANDLE hd) { if (!hd) return; hd->found = hd->saved_found; hd->saved_found = -1; if (hd->found < 0 || hd->found >= hd->used) return; switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: keyring_pop_found_state (hd->active[hd->found].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_pop_found_state (hd->active[hd->found].u.kb); break; } } /* Parse the keyblock in IOBUF and return at R_KEYBLOCK. */ gpg_error_t keydb_parse_keyblock (iobuf_t iobuf, int pk_no, int uid_no, kbnode_t *r_keyblock) { gpg_error_t err; struct parse_packet_ctx_s parsectx; PACKET *pkt; kbnode_t keyblock = NULL; kbnode_t node, *tail; int in_cert, save_mode; int pk_count, uid_count; *r_keyblock = NULL; pkt = xtrymalloc (sizeof *pkt); if (!pkt) return gpg_error_from_syserror (); init_packet (pkt); init_parse_packet (&parsectx, iobuf); save_mode = set_packet_list_mode (0); in_cert = 0; tail = NULL; pk_count = uid_count = 0; while ((err = parse_packet (&parsectx, pkt)) != -1) { if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET) { free_packet (pkt, &parsectx); init_packet (pkt); continue; } if (err) { es_fflush (es_stdout); log_error ("parse_keyblock_image: read error: %s\n", gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_INV_PACKET) { free_packet (pkt, &parsectx); init_packet (pkt); continue; } err = gpg_error (GPG_ERR_INV_KEYRING); break; } /* Filter allowed packets. */ switch (pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: case PKT_USER_ID: case PKT_ATTRIBUTE: case PKT_SIGNATURE: case PKT_RING_TRUST: break; /* Allowed per RFC. */ default: log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype); free_packet(pkt, &parsectx); init_packet(pkt); continue; } /* Other sanity checks. */ if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY) { log_error ("parse_keyblock_image: first packet in a keybox blob " "is not a public key packet\n"); err = gpg_error (GPG_ERR_INV_KEYRING); break; } if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY || pkt->pkttype == PKT_SECRET_KEY)) { log_error ("parse_keyblock_image: " "multiple keyblocks in a keybox blob\n"); err = gpg_error (GPG_ERR_INV_KEYRING); break; } in_cert = 1; node = new_kbnode (pkt); switch (pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: if (++pk_count == pk_no) node->flag |= 1; break; case PKT_USER_ID: if (++uid_count == uid_no) node->flag |= 2; break; default: break; } if (!keyblock) keyblock = node; else *tail = node; tail = &node->next; pkt = xtrymalloc (sizeof *pkt); if (!pkt) { err = gpg_error_from_syserror (); break; } init_packet (pkt); } set_packet_list_mode (save_mode); if (err == -1 && keyblock) err = 0; /* Got the entire keyblock. */ if (err) release_kbnode (keyblock); else { *r_keyblock = keyblock; keydb_stats.parse_keyblocks++; } free_packet (pkt, &parsectx); deinit_parse_packet (&parsectx); xfree (pkt); return err; } /* Return the keyblock last found by keydb_search() in *RET_KB. * keydb_get_keyblock divert to here in the non-keyboxd mode. * * On success, the function returns 0 and the caller must free *RET_KB * using release_kbnode(). Otherwise, the function returns an error * code. * * The returned keyblock has the kbnode flag bit 0 set for the node * with the public key used to locate the keyblock or flag bit 1 set * for the user ID node. */ gpg_error_t internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) { gpg_error_t err = 0; log_assert (!hd->use_keyboxd); if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED) { err = iobuf_seek (hd->keyblock_cache.iobuf, 0); if (err) { log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n"); keyblock_cache_clear (hd); } else { err = keydb_parse_keyblock (hd->keyblock_cache.iobuf, hd->keyblock_cache.pk_no, hd->keyblock_cache.uid_no, ret_kb); if (err) keyblock_cache_clear (hd); if (DBG_CLOCK) log_clock ("%s leave (cached mode)", __func__); return err; } } if (hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_VALUE_NOT_FOUND); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYRING: err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb); break; case KEYDB_RESOURCE_TYPE_KEYBOX: { iobuf_t iobuf; int pk_no, uid_no; err = keybox_get_keyblock (hd->active[hd->found].u.kb, &iobuf, &pk_no, &uid_no); if (!err) { err = keydb_parse_keyblock (iobuf, pk_no, uid_no, ret_kb); if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED) { hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED; hd->keyblock_cache.iobuf = iobuf; hd->keyblock_cache.pk_no = pk_no; hd->keyblock_cache.uid_no = uid_no; } else { iobuf_close (iobuf); } } } break; } if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED) keyblock_cache_clear (hd); if (!err) keydb_stats.get_keyblocks++; return err; } /* Update the keyblock KB (i.e., extract the fingerprint and find the * corresponding keyblock in the keyring). * keydb_update_keyblock diverts to here in the non-keyboxd mode. * * This doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. Note: * if there isn't a keyblock in the keyring corresponding to KB, then * this function returns GPG_ERR_VALUE_NOT_FOUND. * * This function selects the matching record and modifies the current * file position to point to the record just after the selected entry. * Thus, if you do a subsequent search using HD, you should first do a * keydb_search_reset. Further, if the selected record is important, * you should use keydb_push_found_state and keydb_pop_found_state to * save and restore it. */ gpg_error_t internal_keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; PKT_public_key *pk; KEYDB_SEARCH_DESC desc; size_t len; log_assert (!hd->use_keyboxd); pk = kb->pkt->pkt.public_key; kid_not_found_flush (); keyblock_cache_clear (hd); if (opt.dry_run) return 0; err = lock_all (hd); if (err) return err; #ifdef USE_TOFU tofu_notice_key_changed (ctrl, kb); #endif memset (&desc, 0, sizeof (desc)); fingerprint_from_pk (pk, desc.u.fpr, &len); if (len == 20 || len == 32) { desc.mode = KEYDB_SEARCH_MODE_FPR; desc.fprlen = len; } else log_bug ("%s: Unsupported key length: %zu\n", __func__, len); keydb_search_reset (hd); err = keydb_search (hd, &desc, 1, NULL); if (err) return gpg_error (GPG_ERR_VALUE_NOT_FOUND); log_assert (hd->found >= 0 && hd->found < hd->used); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYRING: err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb); break; case KEYDB_RESOURCE_TYPE_KEYBOX: { iobuf_t iobuf; err = build_keyblock_image (kb, &iobuf); if (!err) { keydb_stats.build_keyblocks++; err = keybox_update_keyblock (hd->active[hd->found].u.kb, iobuf_get_temp_buffer (iobuf), iobuf_get_temp_length (iobuf)); iobuf_close (iobuf); } } break; } unlock_all (hd); if (!err) keydb_stats.update_keyblocks++; return err; } /* Insert a keyblock into one of the underlying keyrings or keyboxes. * keydb_insert_keyblock diverts to here in the non-keyboxd mode. * * Be default, the keyring / keybox from which the last search result * came is used. If there was no previous search result (or * keydb_search_reset was called), then the keyring / keybox where the * next search would start is used (i.e., the current file position). * * Note: this doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. */ gpg_error_t internal_keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; int idx; log_assert (!hd->use_keyboxd); kid_not_found_flush (); keyblock_cache_clear (hd); if (opt.dry_run) return 0; if (hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if (hd->current >= 0 && hd->current < hd->used) idx = hd->current; else return gpg_error (GPG_ERR_GENERAL); err = lock_all (hd); if (err) return err; switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYRING: err = keyring_insert_keyblock (hd->active[idx].u.kr, kb); break; case KEYDB_RESOURCE_TYPE_KEYBOX: { /* We need to turn our kbnode_t list of packets into a proper keyblock first. This is required by the OpenPGP key parser included in the keybox code. Eventually we can change this kludge to have the caller pass the image. */ iobuf_t iobuf; err = build_keyblock_image (kb, &iobuf); if (!err) { keydb_stats.build_keyblocks++; err = keybox_insert_keyblock (hd->active[idx].u.kb, iobuf_get_temp_buffer (iobuf), iobuf_get_temp_length (iobuf)); iobuf_close (iobuf); } } break; } unlock_all (hd); if (!err) keydb_stats.insert_keyblocks++; return err; } /* Delete the currently selected keyblock. If you haven't done a * search yet on this database handle (or called keydb_search_reset), * then this will return an error. * * Returns 0 on success or an error code, if an error occurs. */ gpg_error_t internal_keydb_delete_keyblock (KEYDB_HANDLE hd) { gpg_error_t rc; log_assert (!hd->use_keyboxd); kid_not_found_flush (); keyblock_cache_clear (hd); if (hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_VALUE_NOT_FOUND); if (opt.dry_run) return 0; rc = lock_all (hd); if (rc) return rc; switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYRING: rc = keyring_delete_keyblock (hd->active[hd->found].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_delete (hd->active[hd->found].u.kb); break; } unlock_all (hd); if (!rc) keydb_stats.delete_keyblocks++; return rc; } /* A database may consists of multiple keyrings / key boxes. This * sets the "file position" to the start of the first keyring / key * box that is writable (i.e., doesn't have the read-only flag set). * * This first tries the primary keyring (the last keyring (not * keybox!) added using keydb_add_resource() and with * KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it * tries the keyrings / keyboxes in the order in which they were * added. */ gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd) { gpg_error_t rc; if (!hd) return GPG_ERR_INV_ARG; if (hd->use_keyboxd) return 0; /* No need for this here. */ rc = keydb_search_reset (hd); /* this does reset hd->current */ if (rc) return rc; /* If we have a primary set, try that one first */ if (primary_keydb) { for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) { if(hd->active[hd->current].token == primary_keydb) { if(keyring_is_writable (hd->active[hd->current].token)) return 0; else break; } } rc = keydb_search_reset (hd); /* this does reset hd->current */ if (rc) return rc; } for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) { switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); break; case KEYDB_RESOURCE_TYPE_KEYRING: if (keyring_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (keybox_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; } } return gpg_error (GPG_ERR_NOT_FOUND); } /* Rebuild the on-disk caches of all key resources. */ void keydb_rebuild_caches (ctrl_t ctrl, int noisy) { int i, rc; if (opt.use_keyboxd) return; /* No need for this here. */ for (i=0; i < used_resources; i++) { if (!keyring_is_writable (all_resources[i].token)) continue; switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYRING: rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy); if (rc) log_error (_("failed to rebuild keyring cache: %s\n"), gpg_strerror (rc)); break; case KEYDB_RESOURCE_TYPE_KEYBOX: /* N/A. */ break; } } } /* Return the number of skipped blocks (because they were too large to read from a keybox) since the last search reset. */ unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd) { /*FIXME(keyboxd): Do we need this? */ return hd && !hd->use_keyboxd? hd->skipped_long_blobs : 0; } /* Clears the current search result and resets the handle's position * so that the next search starts at the beginning of the database * (the start of the first resource). * keydb_search_reset diverts to here in the non-keyboxd mode. * * Returns 0 on success and an error code if an error occurred. * (Currently, this function always returns 0 if HD is valid.) */ gpg_error_t internal_keydb_search_reset (KEYDB_HANDLE hd) { gpg_error_t rc = 0; int i; log_assert (!hd->use_keyboxd); keyblock_cache_clear (hd); hd->skipped_long_blobs = 0; hd->current = 0; hd->found = -1; /* Now reset all resources. */ for (i=0; !rc && i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYRING: rc = keyring_search_reset (hd->active[i].u.kr); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_search_reset (hd->active[i].u.kb); break; } } hd->is_reset = 1; if (!rc) keydb_stats.search_resets++; return rc; } /* Search the database for keys matching the search description. If * the DB contains any legacy keys, these are silently ignored. * keydb_search diverts to here in the non-keyboxd mode. * * DESC is an array of search terms with NDESC entries. The search * terms are or'd together. That is, the next entry in the DB that * matches any of the descriptions will be returned. * * Note: this function resumes searching where the last search left * off (i.e., at the current file position). If you want to search * from the start of the database, then you need to first call * keydb_search_reset(). * * If no key matches the search description, returns * GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error * occurred, returns an error code. * * The returned key is considered to be selected and the raw data can, * for instance, be returned by calling keydb_get_keyblock(). */ gpg_error_t internal_keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t *descindex) { gpg_error_t rc; int was_reset = hd->is_reset; /* If an entry is already in the cache, then don't add it again. */ int already_in_cache = 0; int fprlen; log_assert (!hd->use_keyboxd); if (!any_registered) { write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN)); return gpg_error (GPG_ERR_NOT_FOUND); } if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 ) { if (DBG_CLOCK) log_clock ("%s leave (not found, cached)", __func__); keydb_stats.notfound_cached++; return gpg_error (GPG_ERR_NOT_FOUND); } /* NB: If one of the exact search modes below is used in a loop to walk over all keys (with the same fingerprint) the caching must have been disabled for the handle. */ if (desc[0].mode == KEYDB_SEARCH_MODE_FPR) fprlen = desc[0].fprlen; else fprlen = 0; if (!hd->no_caching && ndesc == 1 && fprlen && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED && hd->keyblock_cache.fprlen == fprlen && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen) /* Make sure the current file position occurs before the cached result to avoid an infinite loop. */ && (hd->current < hd->keyblock_cache.resource || (hd->current == hd->keyblock_cache.resource && (keybox_offset (hd->active[hd->current].u.kb) <= hd->keyblock_cache.offset)))) { /* (DESCINDEX is already set). */ if (DBG_CLOCK) log_clock ("%s leave (cached)", __func__); hd->current = hd->keyblock_cache.resource; /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record. Seek just beyond that. */ keybox_seek (hd->active[hd->current].u.kb, hd->keyblock_cache.offset + 1); keydb_stats.found_cached++; return 0; } rc = -1; while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) && hd->current >= 0 && hd->current < hd->used) { if (DBG_LOOKUP) log_debug ("%s: searching %s (resource %d of %d)\n", __func__, hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING ? "keyring" : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX ? "keybox" : "unknown type"), hd->current, hd->used); switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); /* we should never see it here */ break; case KEYDB_RESOURCE_TYPE_KEYRING: rc = keyring_search (hd->active[hd->current].u.kr, desc, ndesc, descindex, 1); break; case KEYDB_RESOURCE_TYPE_KEYBOX: do rc = keybox_search (hd->active[hd->current].u.kb, desc, ndesc, KEYBOX_BLOBTYPE_PGP, descindex, &hd->skipped_long_blobs); while (rc == GPG_ERR_LEGACY_KEY); break; } if (DBG_LOOKUP) log_debug ("%s: searched %s (resource %d of %d) => %s\n", __func__, hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING ? "keyring" : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX ? "keybox" : "unknown type"), hd->current, hd->used, rc == -1 ? "EOF" : gpg_strerror (rc)); if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) { /* EOF -> switch to next resource */ hd->current++; } else if (!rc) hd->found = hd->current; } hd->is_reset = 0; rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) ? gpg_error (GPG_ERR_NOT_FOUND) : rc); keyblock_cache_clear (hd); if (!hd->no_caching && !rc && ndesc == 1 && fprlen && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX) { hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED; hd->keyblock_cache.resource = hd->current; /* The current offset is at the start of the next record. Since a record is at least 1 byte, we just use offset - 1, which is within the record. */ hd->keyblock_cache.offset = keybox_offset (hd->active[hd->current].u.kb) - 1; memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen); hd->keyblock_cache.fprlen = fprlen; } if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset && !already_in_cache) kid_not_found_insert (desc[0].u.kid); if (!rc) keydb_stats.found++; else keydb_stats.notfound++; return rc; } /* Return the first non-legacy key in the database. * * If you want the very first key in the database, you can directly * call keydb_search with the search description * KEYDB_SEARCH_MODE_FIRST. */ gpg_error_t keydb_search_first (KEYDB_HANDLE hd) { gpg_error_t err; KEYDB_SEARCH_DESC desc; err = keydb_search_reset (hd); if (err) return err; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; return keydb_search (hd, &desc, 1, NULL); } /* Return the next key (not the next matching key!). * * Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this * function silently skips legacy keys. */ gpg_error_t keydb_search_next (KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_NEXT; return keydb_search (hd, &desc, 1, NULL); } /* This is a convenience function for searching for keys with a long * key id. * * Note: this function resumes searching where the last search left * off. If you want to search the whole database, then you need to * first call keydb_search_reset(). */ gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_LONG_KID; desc.u.kid[0] = kid[0]; desc.u.kid[1] = kid[1]; return keydb_search (hd, &desc, 1, NULL); } /* This is a convenience function for searching for keys with a long * (20 byte) fingerprint. * * Note: this function resumes searching where the last search left * off. If you want to search the whole database, then you need to * first call keydb_search_reset(). */ gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; memcpy (desc.u.fpr, fpr, fprlen); desc.fprlen = fprlen; return keydb_search (hd, &desc, 1, NULL); } diff --git a/g10/plaintext.c b/g10/plaintext.c index f9e0a4296..3e169d93f 100644 --- a/g10/plaintext.c +++ b/g10/plaintext.c @@ -1,814 +1,775 @@ /* plaintext.c - process plaintext packets * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, * 2006, 2009, 2010 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 . */ #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM # include /* for setmode() */ #endif #include "gpg.h" #include "../common/util.h" #include "options.h" #include "packet.h" #include "../common/ttyio.h" #include "filter.h" #include "main.h" #include "../common/status.h" #include "../common/i18n.h" /* Get the output filename. On success, the actual filename that is used is set in *FNAMEP and a filepointer is returned in *FP. EMBEDDED_NAME AND EMBEDDED_NAMELEN are normally stored in a plaintext packet. EMBEDDED_NAMELEN should not include any NUL terminator (EMBEDDED_NAME does not need to be NUL terminated). DATA is the iobuf containing the input data. We just use it to get the input file's filename. On success, the caller is responsible for calling xfree on *FNAMEP and calling es_close on *FPP. */ gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen, iobuf_t data, char **fnamep, estream_t *fpp) { gpg_error_t err = 0; char *fname = NULL; estream_t fp = NULL; int nooutput = 0; /* Create the filename as C string. */ if (opt.outfp) { fname = xtrystrdup ("[FP]"); if (!fname) { err = gpg_error_from_syserror (); goto leave; } } else if (opt.outfile && !(opt.flags.use_embedded_filename && opt.flags.dummy_outfile)) { fname = xtrystrdup (opt.outfile); if (!fname) { err = gpg_error_from_syserror (); goto leave; } } else if (embedded_namelen == 8 && !memcmp (embedded_name, "_CONSOLE", 8)) { log_info (_("data not saved; use option \"--output\" to save it\n")); nooutput = 1; } else if (!opt.flags.use_embedded_filename) { if (data) fname = make_outfile_name (iobuf_get_real_fname (data)); if (!fname) fname = ask_outfile_name (embedded_name, embedded_namelen); if (!fname) { err = gpg_error (GPG_ERR_GENERAL); /* Can't create file. */ goto leave; } } else fname = utf8_to_native (embedded_name, embedded_namelen, 0); if (nooutput) ; else if (opt.outfp) { fp = opt.outfp; es_set_binary (fp); } else if (iobuf_is_pipe_filename (fname) || !*fname) { /* Special file name, no filename, or "-" given; write to the * file descriptor or to stdout. */ int fd; char xname[64]; fd = check_special_filename (fname, 1, 0); if (fd == -1) { /* Not a special filename, thus we want stdout. */ fp = es_stdout; es_set_binary (fp); } else if (!(fp = es_fdopen_nc (fd, "wb"))) { err = gpg_error_from_syserror (); snprintf (xname, sizeof xname, "[fd %d]", fd); log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); goto leave; } } else { while (!overwrite_filep (fname)) { char *tmp = ask_outfile_name (NULL, 0); if (!tmp || !*tmp) { xfree (tmp); /* FIXME: Below used to be GPG_ERR_CREATE_FILE */ err = gpg_error (GPG_ERR_GENERAL); goto leave; } xfree (fname); fname = tmp; } } -#ifndef __riscos__ if (opt.outfp && is_secured_file (es_fileno (opt.outfp))) { err = gpg_error (GPG_ERR_EPERM); log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } else if (fp || nooutput) ; else if (is_secured_filename (fname)) { gpg_err_set_errno (EPERM); err = gpg_error_from_syserror (); log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } else if (!(fp = es_fopen (fname, "wb"))) { err = gpg_error_from_syserror (); log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } -#else /* __riscos__ */ - /* If no output filename was given, i.e. we constructed it, convert - all '.' in fname to '/' but not vice versa as we don't create - directories! */ - if (!opt.outfile) - for (c = 0; fname[c]; ++c) - if (fname[c] == '.') - fname[c] = '/'; - - if (fp || nooutput) - ; - else - { - /* Note: riscos stuff is not expected to work anymore. If we - want to port it again to riscos we should do most of the suff - in estream. FIXME: Consider to remove all riscos special - cases. */ - fp = fopen (fname, "wb"); - if (!fp) - { - log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); - err = GPG_ERR_CREATE_FILE; - if (errno == 106) - log_info ("Do output file and input file have the same name?\n"); - goto leave; - } - - /* If there's a ,xxx extension in the embedded filename, - use that, else check whether the user input (in fname) - has a ,xxx appended, then use that in preference */ - if ((c = riscos_get_filetype_from_string (embedded_name, - embedded_namelen)) != -1) - filetype = c; - if ((c = riscos_get_filetype_from_string (fname, strlen (fname))) != -1) - filetype = c; - riscos_set_filetype_by_number (fname, filetype); - } -#endif /* __riscos__ */ leave: if (err) { if (fp && fp != es_stdout && fp != opt.outfp) es_fclose (fp); xfree (fname); return err; } *fnamep = fname; *fpp = fp; return 0; } /* Handle a plaintext packet. If MFX is not NULL, update the MDs * Note: We should have used the filter stuff here, but we have to add * some easy mimic to set a read limit, so we calculate only the bytes * from the plaintext. */ int handle_plaintext (PKT_plaintext * pt, md_filter_context_t * mfx, int nooutput, int clearsig) { char *fname = NULL; estream_t fp = NULL; static off_t count = 0; int err = 0; int c; int convert; #ifdef __riscos__ int filetype = 0xfff; #endif if (pt->mode == 't' || pt->mode == 'u' || pt->mode == 'm') convert = pt->mode; else convert = 0; /* Let people know what the plaintext info is. This allows the receiving program to try and do something different based on the format code (say, recode UTF-8 to local). */ if (!nooutput && is_status_enabled ()) { char status[50]; /* Better make sure that stdout has been flushed in case the output will be written to it. This is to make sure that no not-yet-flushed stuff will be written after the plaintext status message. */ es_fflush (es_stdout); snprintf (status, sizeof status, "%X %lu ", (byte) pt->mode, (ulong) pt->timestamp); write_status_text_and_buffer (STATUS_PLAINTEXT, status, pt->name, pt->namelen, 0); if (!pt->is_partial) { snprintf (status, sizeof status, "%lu", (ulong) pt->len); write_status_text (STATUS_PLAINTEXT_LENGTH, status); } } if (! nooutput) { err = get_output_file (pt->name, pt->namelen, pt->buf, &fname, &fp); if (err) goto leave; } if (!pt->is_partial) { /* We have an actual length (which might be zero). */ if (clearsig) { log_error ("clearsig encountered while not expected\n"); err = gpg_error (GPG_ERR_UNEXPECTED); goto leave; } if (convert) /* Text mode. */ { for (; pt->len; pt->len--) { if ((c = iobuf_get (pt->buf)) == -1) { err = gpg_error_from_syserror (); log_error ("problem reading source (%u bytes remaining)\n", (unsigned) pt->len); goto leave; } if (mfx->md) gcry_md_putc (mfx->md, c); #ifndef HAVE_DOSISH_SYSTEM /* Convert to native line ending. */ /* fixme: this hack might be too simple */ if (c == '\r' && convert != 'm') continue; #endif if (fp) { if (opt.max_output && (++count) > opt.max_output) { log_error ("error writing to '%s': %s\n", fname, "exceeded --max-output limit\n"); err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } else if (es_putc (c, fp) == EOF) { if (es_ferror (fp)) err = gpg_error_from_syserror (); else err = gpg_error (GPG_ERR_EOF); log_error ("error writing to '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } } } else /* Binary mode. */ { byte *buffer = xmalloc (32768); while (pt->len) { int len = pt->len > 32768 ? 32768 : pt->len; len = iobuf_read (pt->buf, buffer, len); if (len == -1) { err = gpg_error_from_syserror (); log_error ("problem reading source (%u bytes remaining)\n", (unsigned) pt->len); xfree (buffer); goto leave; } if (mfx->md) gcry_md_write (mfx->md, buffer, len); if (fp) { if (opt.max_output && (count += len) > opt.max_output) { log_error ("error writing to '%s': %s\n", fname, "exceeded --max-output limit\n"); err = gpg_error (GPG_ERR_TOO_LARGE); xfree (buffer); goto leave; } else if (es_fwrite (buffer, 1, len, fp) != len) { err = gpg_error_from_syserror (); log_error ("error writing to '%s': %s\n", fname, gpg_strerror (err)); xfree (buffer); goto leave; } } pt->len -= len; } xfree (buffer); } } else if (!clearsig) { if (convert) { /* text mode */ while ((c = iobuf_get (pt->buf)) != -1) { if (mfx->md) gcry_md_putc (mfx->md, c); #ifndef HAVE_DOSISH_SYSTEM if (c == '\r' && convert != 'm') continue; /* fixme: this hack might be too simple */ #endif if (fp) { if (opt.max_output && (++count) > opt.max_output) { log_error ("Error writing to '%s': %s\n", fname, "exceeded --max-output limit\n"); err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } else if (es_putc (c, fp) == EOF) { if (es_ferror (fp)) err = gpg_error_from_syserror (); else err = gpg_error (GPG_ERR_EOF); log_error ("error writing to '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } } } else { /* binary mode */ byte *buffer; int eof_seen = 0; buffer = xtrymalloc (32768); if (!buffer) { err = gpg_error_from_syserror (); goto leave; } while (!eof_seen) { /* Why do we check for len < 32768: * If we won't, we would practically read 2 EOFs but * the first one has already popped the block_filter * off and therefore we don't catch the boundary. * So, always assume EOF if iobuf_read returns less bytes * then requested */ int len = iobuf_read (pt->buf, buffer, 32768); if (len == -1) break; if (len < 32768) eof_seen = 1; if (mfx->md) gcry_md_write (mfx->md, buffer, len); if (fp) { if (opt.max_output && (count += len) > opt.max_output) { log_error ("error writing to '%s': %s\n", fname, "exceeded --max-output limit\n"); err = gpg_error (GPG_ERR_TOO_LARGE); xfree (buffer); goto leave; } else if (es_fwrite (buffer, 1, len, fp) != len) { err = gpg_error_from_syserror (); log_error ("error writing to '%s': %s\n", fname, gpg_strerror (err)); xfree (buffer); goto leave; } } } xfree (buffer); } pt->buf = NULL; } else /* Clear text signature - don't hash the last CR,LF. */ { int state = 0; while ((c = iobuf_get (pt->buf)) != -1) { if (fp) { if (opt.max_output && (++count) > opt.max_output) { log_error ("error writing to '%s': %s\n", fname, "exceeded --max-output limit\n"); err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } else if (es_putc (c, fp) == EOF) { err = gpg_error_from_syserror (); log_error ("error writing to '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } if (!mfx->md) continue; if (state == 2) { gcry_md_putc (mfx->md, '\r'); gcry_md_putc (mfx->md, '\n'); state = 0; } if (!state) { if (c == '\r') state = 1; else if (c == '\n') state = 2; else gcry_md_putc (mfx->md, c); } else if (state == 1) { if (c == '\n') state = 2; else { gcry_md_putc (mfx->md, '\r'); if (c == '\r') state = 1; else { state = 0; gcry_md_putc (mfx->md, c); } } } } pt->buf = NULL; } if (fp && fp != es_stdout && fp != opt.outfp && es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); fp = NULL; goto leave; } fp = NULL; leave: /* Make sure that stdout gets flushed after the plaintext has been handled. This is for extra security as we do a flush anyway before checking the signature. */ if (es_fflush (es_stdout)) { /* We need to check the return code to detect errors like disk full for short plaintexts. See bug#1207. Checking return values is a good idea in any case. */ if (!err) err = gpg_error_from_syserror (); log_error ("error flushing '%s': %s\n", "[stdout]", gpg_strerror (err)); } if (fp && fp != es_stdout && fp != opt.outfp) es_fclose (fp); xfree (fname); return err; } static void do_hash (gcry_md_hd_t md, gcry_md_hd_t md2, IOBUF fp, int textmode) { text_filter_context_t tfx; int c; if (textmode) { memset (&tfx, 0, sizeof tfx); iobuf_push_filter (fp, text_filter, &tfx); } if (md2) { /* work around a strange behaviour in pgp2 */ /* It seems that at least PGP5 converts a single CR to a CR,LF too */ int lc = -1; while ((c = iobuf_get (fp)) != -1) { if (c == '\n' && lc == '\r') gcry_md_putc (md2, c); else if (c == '\n') { gcry_md_putc (md2, '\r'); gcry_md_putc (md2, c); } else if (c != '\n' && lc == '\r') { gcry_md_putc (md2, '\n'); gcry_md_putc (md2, c); } else gcry_md_putc (md2, c); if (md) gcry_md_putc (md, c); lc = c; } } else { while ((c = iobuf_get (fp)) != -1) { if (md) gcry_md_putc (md, c); } } } /**************** * Ask for the detached datafile and calculate the digest from it. * INFILE is the name of the input file. */ int ask_for_detached_datafile (gcry_md_hd_t md, gcry_md_hd_t md2, const char *inname, int textmode) { progress_filter_context_t *pfx; char *answer = NULL; IOBUF fp; int rc = 0; pfx = new_progress_context (); fp = open_sigfile (inname, pfx); /* Open default file. */ if (!fp && !opt.batch) { int any = 0; tty_printf (_("Detached signature.\n")); do { char *name; xfree (answer); tty_enable_completion (NULL); name = cpr_get ("detached_signature.filename", _("Please enter name of data file: ")); tty_disable_completion (); cpr_kill_prompt (); answer = make_filename (name, (void *) NULL); xfree (name); if (any && !*answer) { rc = gpg_error (GPG_ERR_GENERAL); /*G10ERR_READ_FILE */ goto leave; } fp = iobuf_open (answer); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if (!fp && errno == ENOENT) { tty_printf ("No such file, try again or hit enter to quit.\n"); any++; } else if (!fp) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), answer, strerror (errno)); goto leave; } } while (!fp); } if (!fp) { if (opt.verbose) log_info (_("reading stdin ...\n")); fp = iobuf_open (NULL); log_assert (fp); } do_hash (md, md2, fp, textmode); iobuf_close (fp); leave: xfree (answer); release_progress_context (pfx); return rc; } /* Hash the given files and append the hash to hash contexts MD and * MD2. If FILES is NULL, stdin is hashed. */ int hash_datafiles (gcry_md_hd_t md, gcry_md_hd_t md2, strlist_t files, const char *sigfilename, int textmode) { progress_filter_context_t *pfx; IOBUF fp; strlist_t sl; pfx = new_progress_context (); if (!files) { /* Check whether we can open the signed material. We avoid trying to open a file if run in batch mode. This assumed data file for a sig file feature is just a convenience thing for the command line and the user needs to read possible warning messages. */ if (!opt.batch) { fp = open_sigfile (sigfilename, pfx); if (fp) { do_hash (md, md2, fp, textmode); iobuf_close (fp); release_progress_context (pfx); return 0; } } log_error (_("no signed data\n")); release_progress_context (pfx); return gpg_error (GPG_ERR_NO_DATA); } for (sl = files; sl; sl = sl->next) { fp = iobuf_open (sl->d); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if (!fp) { int rc = gpg_error_from_syserror (); log_error (_("can't open signed data '%s'\n"), print_fname_stdin (sl->d)); release_progress_context (pfx); return rc; } handle_progress (pfx, fp, sl->d); do_hash (md, md2, fp, textmode); iobuf_close (fp); } release_progress_context (pfx); return 0; } /* Hash the data from file descriptor DATA_FD and append the hash to hash contexts MD and MD2. */ int hash_datafile_by_fd (gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd, int textmode) { progress_filter_context_t *pfx = new_progress_context (); iobuf_t fp; if (is_secured_file (data_fd)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = iobuf_fdopen_nc (data_fd, "rb"); if (!fp) { int rc = gpg_error_from_syserror (); log_error (_("can't open signed data fd=%d: %s\n"), data_fd, strerror (errno)); release_progress_context (pfx); return rc; } handle_progress (pfx, fp, NULL); do_hash (md, md2, fp, textmode); iobuf_close (fp); release_progress_context (pfx); return 0; } /* Set up a plaintext packet with the appropriate filename. If there is a --set-filename, use it (it's already UTF8). If there is a regular filename, UTF8-ize it if necessary. If there is no filenames at all, set the field empty. */ PKT_plaintext * setup_plaintext_name (const char *filename, IOBUF iobuf) { PKT_plaintext *pt; if ((filename && !iobuf_is_pipe_filename (filename)) || (opt.set_filename && !iobuf_is_pipe_filename (opt.set_filename))) { char *s; if (opt.set_filename) s = make_basename (opt.set_filename, iobuf_get_real_fname (iobuf)); else if (filename && !opt.flags.utf8_filename) { char *tmp = native_to_utf8 (filename); s = make_basename (tmp, iobuf_get_real_fname (iobuf)); xfree (tmp); } else s = make_basename (filename, iobuf_get_real_fname (iobuf)); pt = xmalloc (sizeof *pt + strlen (s) - 1); pt->namelen = strlen (s); memcpy (pt->name, s, pt->namelen); xfree (s); } else { /* no filename */ pt = xmalloc (sizeof *pt - 1); pt->namelen = 0; } return pt; } diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index c76809bc4..911514406 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -1,647 +1,647 @@ /* kbxutil.c - The Keybox utility * Copyright (C) 2000, 2001, 2004, 2007, 2011 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include "../common/logging.h" #include "../common/stringhelp.h" #include "../common/utf8conv.h" #include "../common/i18n.h" #include "keybox-defs.h" #include "../common/init.h" #include enum cmd_and_opt_values { aNull = 0, oArmor = 'a', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', aNoSuchCmd = 500, /* force other values not to be a letter */ aFindByFpr, aFindByKid, aFindByUid, aStats, aImportOpenPGP, aFindDups, aCut, oDebug, oDebugAll, oNoArmor, oFrom, oTo, aTest }; static gpgrt_opt_t opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, /* { aFindByFpr, "find-by-fpr", 0, "|FPR| find key using it's fingerprnt" }, */ /* { aFindByKid, "find-by-kid", 0, "|KID| find key using it's keyid" }, */ /* { aFindByUid, "find-by-uid", 0, "|NAME| find key by user name" }, */ { aStats, "stats", 0, "show key statistics" }, { aImportOpenPGP, "import-openpgp", 0, "import OpenPGP keyblocks"}, { aFindDups, "find-dups", 0, "find duplicates" }, { aCut, "cut", 0, "export records" }, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oFrom, "from", 4, "|N|first record to export" }, { oTo, "to", 4, "|N|last record to export" }, /* { oArmor, "armor", 0, N_("create ascii armored output")}, */ /* { oArmor, "armour", 0, "@" }, */ /* { oOutput, "output", 2, N_("use as output file")}, */ { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oDebug, "debug" ,4|16, N_("set debugging flags")}, { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, ARGPARSE_end () /* end of list */ }; void myexit (int rc); int keybox_errors_seen = 0; static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "kbxutil (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: kbxutil [options] [files] (-h for help)"); break; case 41: p = _("Syntax: kbxutil [options] [files]\n" "List, export, import Keybox data\n"); break; default: p = NULL; } return p; } /* Used by gcry for logging */ static void my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) { (void)dummy; /* Map the log levels. */ switch (level) { case GCRY_LOG_CONT: level = GPGRT_LOGLVL_CONT; break; case GCRY_LOG_INFO: level = GPGRT_LOGLVL_INFO; break; case GCRY_LOG_WARN: level = GPGRT_LOGLVL_WARN; break; case GCRY_LOG_ERROR:level = GPGRT_LOGLVL_ERROR; break; case GCRY_LOG_FATAL:level = GPGRT_LOGLVL_FATAL; break; case GCRY_LOG_BUG: level = GPGRT_LOGLVL_BUG; break; case GCRY_LOG_DEBUG:level = GPGRT_LOGLVL_DEBUG; break; default: level = GPGRT_LOGLVL_ERROR; break; } log_logv (level, fmt, arg_ptr); } /* static void */ /* wrong_args( const char *text ) */ /* { */ /* log_error("usage: kbxutil %s\n", text); */ /* myexit ( 1 ); */ /* } */ #if 0 static int hextobyte( const byte *s ) { int c; if( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if( *s >= '0' && *s <= '9' ) c += *s - '0'; else if( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } #endif #if 0 static char * format_fingerprint ( const char *s ) { int i, c; byte fpr[20]; for (i=0; i < 20 && *s; ) { if ( *s == ' ' || *s == '\t' ) { s++; continue; } c = hextobyte(s); if (c == -1) { return NULL; } fpr[i++] = c; s += 2; } return gcry_xstrdup ( fpr ); } #endif #if 0 static int format_keyid ( const char *s, u32 *kid ) { char helpbuf[9]; switch ( strlen ( s ) ) { case 8: kid[0] = 0; kid[1] = strtoul( s, NULL, 16 ); return 10; case 16: mem2str( helpbuf, s, 9 ); kid[0] = strtoul( helpbuf, NULL, 16 ); kid[1] = strtoul( s+8, NULL, 16 ); return 11; } return 0; /* error */ } #endif static char * read_file (const char *fname, size_t *r_length) { - FILE *fp; + estream_t fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; - fp = stdin; + fp = es_stdin; buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xtrymalloc (bufsize); else buf = xtryrealloc (buf, bufsize); if (!buf) log_fatal ("can't allocate buffer: %s\n", strerror (errno)); - nread = fread (buf+buflen, 1, NCHUNK, fp); - if (nread < NCHUNK && ferror (fp)) + nread = es_fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && es_ferror (fp)) { log_error ("error reading '[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; - fp = fopen (fname, "rb"); + fp = es_fopen (fname, "rb"); if (!fp) { log_error ("can't open '%s': %s\n", fname, strerror (errno)); return NULL; } - if (fstat (fileno(fp), &st)) + if (fstat (es_fileno(fp), &st)) { log_error ("can't stat '%s': %s\n", fname, strerror (errno)); - fclose (fp); + es_fclose (fp); return NULL; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) log_fatal ("can't allocate buffer: %s\n", strerror (errno)); - if (fread (buf, buflen, 1, fp) != 1) + if (es_fread (buf, buflen, 1, fp) != 1) { log_error ("error reading '%s': %s\n", fname, strerror (errno)); - fclose (fp); + es_fclose (fp); xfree (buf); return NULL; } - fclose (fp); + es_fclose (fp); } *r_length = buflen; return buf; } static void dump_fpr (const unsigned char *buffer, size_t len) { int i; for (i=0; i < len; i++, buffer++) { if (len == 20) { if (i == 10) putchar (' '); printf (" %02X%02X", buffer[0], buffer[1]); i++; buffer++; } else { if (i && !(i % 8)) putchar (' '); printf (" %02X", buffer[0]); } } } static void dump_grip (const unsigned char *buffer, size_t len) { int i; for (i=0; i < len; i++, buffer++) { printf ("%02X", buffer[0]); } } static void dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) { printf ("pub %2d %02X%02X%02X%02X", info->primary.algo, info->primary.keyid[4], info->primary.keyid[5], info->primary.keyid[6], info->primary.keyid[7] ); dump_fpr (info->primary.fpr, info->primary.fprlen); putchar ('\n'); fputs ("grp ", stdout); dump_grip (info->primary.grip, 20); putchar ('\n'); if (info->nsubkeys) { struct _keybox_openpgp_key_info *k; k = &info->subkeys; do { printf ("sub %2d %02X%02X%02X%02X", k->algo, k->keyid[4], k->keyid[5], k->keyid[6], k->keyid[7] ); dump_fpr (k->fpr, k->fprlen); putchar ('\n'); fputs ("grp ", stdout); dump_grip (k->grip, 20); putchar ('\n'); k = k->next; } while (k); } if (info->nuids) { struct _keybox_openpgp_uid_info *u; u = &info->uids; do { printf ("uid\t\t%.*s\n", (int)u->len, image + u->off); u = u->next; } while (u); } } static void import_openpgp (const char *filename, int dryrun) { gpg_error_t err; char *buffer; size_t buflen, nparsed; unsigned char *p; struct _keybox_openpgp_info info; KEYBOXBLOB blob; buffer = read_file (filename, &buflen); if (!buffer) return; p = (unsigned char *)buffer; for (;;) { err = _keybox_parse_openpgp (p, buflen, &nparsed, &info); assert (nparsed <= buflen); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) break; if (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM) { /* This is likely a v3 key packet with a non-RSA algorithm. These are keys from very early versions of GnuPG (pre-OpenPGP). */ } else { es_fflush (es_stdout); log_info ("%s: failed to parse OpenPGP keyblock: %s\n", filename, gpg_strerror (err)); } } else { if (dryrun) dump_openpgp_key (&info, p); else { err = _keybox_create_openpgp_blob (&blob, &info, p, nparsed, 0); if (err) { es_fflush (es_stdout); log_error ("%s: failed to create OpenPGP keyblock: %s\n", filename, gpg_strerror (err)); } else { err = _keybox_write_blob (blob, es_stdout, NULL); _keybox_release_blob (blob); if (err) { es_fflush (es_stdout); log_error ("%s: failed to write OpenPGP keyblock: %s\n", filename, gpg_strerror (err)); } } } _keybox_destroy_openpgp_info (&info); } p += nparsed; buflen -= nparsed; } xfree (buffer); } int main (int argc, char **argv) { gpgrt_argparse_t pargs; enum cmd_and_opt_values cmd = 0; unsigned long from = 0, to = ULONG_MAX; int dry_run = 0; early_system_init (); gpgrt_set_strusage( my_strusage ); gcry_control (GCRYCTL_DISABLE_SECMEM); log_set_prefix ("kbxutil", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); gcry_set_log_handler (my_gcry_logger, NULL); /*create_dotlock(NULL); register locking cleanup */ /* We need to use the gcry malloc function because jnlib uses them. */ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparse (NULL,&pargs, opts)) { switch (pargs.r_opt) { case oVerbose: /*opt.verbose++;*/ /*gcry_control( GCRYCTL_SET_VERBOSITY, (int)opt.verbose );*/ break; case oDebug: /*opt.debug |= pargs.r.ret_ulong; */ break; case oDebugAll: /*opt.debug = ~0;*/ break; case aFindByFpr: case aFindByKid: case aFindByUid: case aStats: case aImportOpenPGP: case aFindDups: case aCut: cmd = pargs.r_opt; break; case oFrom: from = pargs.r.ret_ulong; break; case oTo: to = pargs.r.ret_ulong; break; case oDryRun: dry_run = 1; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); if (to < from) log_error ("record number of \"--to\" is lower than \"--from\" one\n"); if (log_get_errorcount(0) ) myexit(2); if (!cmd) { /* Default is to list a KBX file */ if (!argc) _keybox_dump_file (NULL, 0, stdout); else { for (; argc; argc--, argv++) _keybox_dump_file (*argv, 0, stdout); } } else if (cmd == aStats ) { if (!argc) _keybox_dump_file (NULL, 1, stdout); else { for (; argc; argc--, argv++) _keybox_dump_file (*argv, 1, stdout); } } else if (cmd == aFindDups ) { if (!argc) _keybox_dump_find_dups (NULL, 0, stdout); else { for (; argc; argc--, argv++) _keybox_dump_find_dups (*argv, 0, stdout); } } else if (cmd == aCut ) { if (!argc) _keybox_dump_cut_records (NULL, from, to, stdout); else { for (; argc; argc--, argv++) _keybox_dump_cut_records (*argv, from, to, stdout); } } else if (cmd == aImportOpenPGP) { if (!argc) import_openpgp ("-", dry_run); else { for (; argc; argc--, argv++) import_openpgp (*argv, dry_run); } } #if 0 else if ( cmd == aFindByFpr ) { char *fpr; if ( argc != 2 ) wrong_args ("kbxfile foingerprint"); fpr = format_fingerprint ( argv[1] ); if ( !fpr ) log_error ("invalid formatted fingerprint\n"); else { kbxfile_search_by_fpr ( argv[0], fpr ); gcry_free ( fpr ); } } else if ( cmd == aFindByKid ) { u32 kid[2]; int mode; if ( argc != 2 ) wrong_args ("kbxfile short-or-long-keyid"); mode = format_keyid ( argv[1], kid ); if ( !mode ) log_error ("invalid formatted keyID\n"); else { kbxfile_search_by_kid ( argv[0], kid, mode ); } } else if ( cmd == aFindByUid ) { if ( argc != 2 ) wrong_args ("kbxfile userID"); kbxfile_search_by_uid ( argv[0], argv[1] ); } #endif else log_error ("unsupported action\n"); myexit(0); return 8; /*NEVER REACHED*/ } void myexit( int rc ) { /* if( opt.debug & DBG_MEMSTAT_VALUE ) {*/ /* gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); */ /* gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); */ /* }*/ /* if( opt.debug ) */ /* gcry_control( GCRYCTL_DUMP_SECMEM_STATS ); */ rc = rc? rc : log_get_errorcount(0)? 2 : keybox_errors_seen? 1 : 0; exit(rc ); }