gnupg/kbx/kbxutil.c

647 lines
15 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include "keybox-defs.h"
#include "../common/logging.h"
#include "../common/stringhelp.h"
#include "../common/utf8conv.h"
#include "../common/i18n.h"
#include "../common/init.h"
#include <gcrypt.h>
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)
{
estream_t fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
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 = 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 = es_fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (es_fileno(fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
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 (es_fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
es_fclose (fp);
xfree (buf);
return NULL;
}
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 );
}