--- /dev/null
+/*
+ * Copyright (C) 1997-2001 Thomas Roessler <roessler@does-not-exist.org>
+ *
+ * This program 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.
+ *
+ * This program 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, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111, USA.
+ */
+
+/*
+ * This is a "simple" PGP key ring dumper.
+ *
+ * The output format is supposed to be compatible to the one GnuPG
+ * emits and Mutt expects.
+ *
+ * Note that the code of this program could be considerably less
+ * complex, but most of it was taken from mutt's second generation
+ * key ring parser.
+ *
+ * You can actually use this to put together some fairly general
+ * PGP key management applications.
+ *
+ */
+
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+extern char *optarg;
+extern int optind;
+
+#include "sha1.h"
+#include "lib.h"
+#include "pgplib.h"
+#include "pgppacket.h"
+
+
+#ifdef HAVE_FGETPOS
+#define FGETPOS(fp,pos) fgetpos((fp),&(pos))
+#define FSETPOS(fp,pos) fsetpos((fp),&(pos))
+#else
+#define FGETPOS(fp,pos) pos=ftell((fp));
+#define FSETPOS(fp,pos) fseek((fp),(pos),SEEK_SET)
+#endif
+
+
+static short dump_signatures = 0;
+
+
+static void pgpring_find_candidates (char *ringfile, const char *hints[], int nhints);
+static void pgpring_dump_keyblock (pgp_key_t *p);
+
+int main (int argc, char * const argv[])
+{
+ int c;
+
+ short version = 2;
+ short secring = 0;
+
+ const char *_kring = NULL;
+ char *env_pgppath, *env_home;
+
+ char pgppath[_POSIX_PATH_MAX];
+ char kring[_POSIX_PATH_MAX];
+
+ while ((c = getopt (argc, argv, "25sk:S")) != EOF)
+ {
+ switch (c)
+ {
+ case 'S':
+ {
+ dump_signatures = 1;
+ break;
+ }
+
+ case 'k':
+ {
+ _kring = optarg;
+ break;
+ }
+
+ case '2': case '5':
+ {
+ version = c - '0';
+ break;
+ }
+
+ case 's':
+ {
+ secring = 1;
+ break;
+ }
+
+ default:
+ {
+ fprintf (stderr, "usage: %s [-k <key ring> | [-2 | -5] [ -s]] [hints]\n",
+ argv[0]);
+ exit (1);
+ }
+ }
+ }
+
+ if (_kring)
+ strfcpy (kring, _kring, sizeof (kring));
+ else
+ {
+ if ((env_pgppath = getenv ("PGPPATH")))
+ strfcpy (pgppath, env_pgppath, sizeof (pgppath));
+ else if ((env_home = getenv ("HOME")))
+ snprintf (pgppath, sizeof (pgppath), "%s/.pgp", env_home);
+ else
+ {
+ fprintf (stderr, "%s: Can't determine your PGPPATH.\n", argv[0]);
+ exit (1);
+ }
+
+ if (secring)
+ snprintf (kring, sizeof (kring), "%s/secring.%s", pgppath, version == 2 ? "pgp" : "skr");
+ else
+ snprintf (kring, sizeof (kring), "%s/pubring.%s", pgppath, version == 2 ? "pgp" : "pkr");
+ }
+
+ pgpring_find_candidates (kring, (const char**) argv + optind, argc - optind);
+
+ return 0;
+}
+
+
+/* The actual key ring parser */
+
+static pgp_key_t *pgp_parse_pgp2_key (unsigned char *buff, size_t l)
+{
+ pgp_key_t *p;
+ unsigned char alg;
+ size_t expl;
+ unsigned long id;
+ time_t gen_time = 0;
+ unsigned short exp_days = 0;
+ size_t j;
+ int i, k;
+ unsigned char scratch[LONG_STRING];
+
+ if (l < 12)
+ return NULL;
+
+ p = pgp_new_keyinfo();
+
+ for (i = 0, j = 2; i < 4; i++)
+ gen_time = (gen_time << 8) + buff[j++];
+
+ p->gen_time = gen_time;
+
+ for (i = 0; i < 2; i++)
+ exp_days = (exp_days << 8) + buff[j++];
+
+ if (exp_days && time (NULL) > gen_time + exp_days * 24 * 3600)
+ p->flags |= KEYFLAG_EXPIRED;
+
+ alg = buff[j++];
+
+ p->numalg = alg;
+ p->algorithm = pgp_pkalgbytype (alg);
+ p->flags |= pgp_get_abilities (alg);
+
+ expl = 0;
+ for (i = 0; i < 2; i++)
+ expl = (expl << 8) + buff[j++];
+
+ p->keylen = expl;
+
+ expl = (expl + 7) / 8;
+ if (expl < 4)
+ goto bailout;
+
+
+ j += expl - 8;
+
+ for (k = 0; k < 2; k++)
+ {
+ for (id = 0, i = 0; i < 4; i++)
+ id = (id << 8) + buff[j++];
+
+ snprintf ((char *) scratch + k * 8, sizeof (scratch) - k * 8,
+ "%08lX", id);
+ }
+
+ p->keyid = safe_strdup ((char *) scratch);
+
+ return p;
+
+bailout:
+
+ safe_free ((void *)&p);
+ return NULL;
+}
+
+static void pgp_make_pgp3_fingerprint (unsigned char *buff, size_t l,
+ unsigned char *digest)
+{
+ unsigned char dummy;
+ SHA1_CTX context;
+
+ SHA1_Init (&context);
+
+ dummy = buff[0] & 0x3f;
+
+ if (dummy == PT_SUBSECKEY || dummy == PT_SUBKEY || dummy == PT_SECKEY)
+ dummy = PT_PUBKEY;
+
+ dummy = (dummy << 2) | 0x81;
+ SHA1_Update (&context, &dummy, 1);
+ dummy = ((l - 1) >> 8) & 0xff;
+ SHA1_Update (&context, &dummy, 1);
+ dummy = (l - 1) & 0xff;
+ SHA1_Update (&context, &dummy, 1);
+ SHA1_Update (&context, buff + 1, l - 1);
+ SHA1_Final (digest, &context);
+
+}
+
+static void skip_bignum (unsigned char *buff, size_t l, size_t j,
+ size_t * toff, size_t n)
+{
+ size_t len;
+
+ do
+ {
+ len = (buff[j] << 8) + buff[j + 1];
+ j += (len + 7) / 8 + 2;
+ }
+ while (j <= l && --n > 0);
+
+ if (toff)
+ *toff = j;
+}
+
+
+static pgp_key_t *pgp_parse_pgp3_key (unsigned char *buff, size_t l)
+{
+ pgp_key_t *p;
+ unsigned char alg;
+ unsigned char digest[SHA_DIGEST_LENGTH];
+ unsigned char scratch[LONG_STRING];
+ time_t gen_time = 0;
+ unsigned long id;
+ int i, k;
+ short len;
+ size_t j;
+
+ p = pgp_new_keyinfo ();
+ j = 2;
+
+ for (i = 0; i < 4; i++)
+ gen_time = (gen_time << 8) + buff[j++];
+
+ p->gen_time = gen_time;
+
+ alg = buff[j++];
+
+ p->numalg = alg;
+ p->algorithm = pgp_pkalgbytype (alg);
+ p->flags |= pgp_get_abilities (alg);
+
+ if (alg == 17)
+ skip_bignum (buff, l, j, &j, 3);
+ else if (alg == 16 || alg == 20)
+ skip_bignum (buff, l, j, &j, 2);
+
+ len = (buff[j] << 8) + buff[j + 1];
+ p->keylen = len;
+
+ if (alg >= 1 && alg <= 3)
+ skip_bignum (buff, l, j, &j, 2);
+ else if (alg == 17 || alg == 16 || alg == 20)
+ skip_bignum (buff, l, j, &j, 1);
+
+ pgp_make_pgp3_fingerprint (buff, j, digest);
+
+ for (k = 0; k < 2; k++)
+ {
+ for (id = 0, i = SHA_DIGEST_LENGTH - 8 + k * 4;
+ i < SHA_DIGEST_LENGTH + (k - 1) * 4; i++)
+ id = (id << 8) + digest[i];
+
+ snprintf ((char *) scratch + k * 8, sizeof (scratch) - k * 8, "%08lX", id);
+ }
+
+ p->keyid = safe_strdup ((char *) scratch);
+
+ return p;
+}
+
+static pgp_key_t *pgp_parse_keyinfo (unsigned char *buff, size_t l)
+{
+ if (!buff || l < 2)
+ return NULL;
+
+ switch (buff[1])
+ {
+ case 2:
+ case 3:
+ return pgp_parse_pgp2_key (buff, l);
+ case 4:
+ return pgp_parse_pgp3_key (buff, l);
+ default:
+ return NULL;
+ }
+}
+
+static int pgp_parse_pgp2_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *s)
+{
+ unsigned char sigtype;
+ time_t sig_gen_time;
+ unsigned long signerid1;
+ unsigned long signerid2;
+ size_t j;
+ int i;
+
+ if (l < 22)
+ return -1;
+
+ j = 3;
+ sigtype = buff[j++];
+
+ sig_gen_time = 0;
+ for (i = 0; i < 4; i++)
+ sig_gen_time = (sig_gen_time << 8) + buff[j++];
+
+ signerid1 = signerid2 = 0;
+ for (i = 0; i < 4; i++)
+ signerid1 = (signerid1 << 8) + buff[j++];
+
+ for (i = 0; i < 4; i++)
+ signerid2 = (signerid2 << 8) + buff[j++];
+
+
+ if (sigtype == 0x20 || sigtype == 0x28)
+ p->flags |= KEYFLAG_REVOKED;
+
+ if (s)
+ {
+ s->sigtype = sigtype;
+ s->sid1 = signerid1;
+ s->sid2 = signerid2;
+ }
+
+ return 0;
+}
+
+static int pgp_parse_pgp3_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *s)
+{
+ unsigned char sigtype;
+ unsigned char pkalg;
+ unsigned char hashalg;
+ unsigned char skt;
+ time_t sig_gen_time = -1;
+ long validity = -1;
+ long key_validity = -1;
+ unsigned long signerid1 = 0;
+ unsigned long signerid2 = 0;
+ size_t ml;
+ size_t j;
+ int i;
+ short ii;
+ short have_critical_spks = 0;
+
+ if (l < 7)
+ return -1;
+
+ j = 2;
+
+ sigtype = buff[j++];
+ pkalg = buff[j++];
+ hashalg = buff[j++];
+
+ for (ii = 0; ii < 2; ii++)
+ {
+ size_t skl;
+ size_t nextone;
+
+ ml = (buff[j] << 8) + buff[j + 1];
+ j += 2;
+
+ if (j + ml > l)
+ break;
+
+ nextone = j;
+ while (ml)
+ {
+ j = nextone;
+ skl = buff[j++];
+ if (!--ml)
+ break;
+
+ if (skl >= 192)
+ {
+ skl = (skl - 192) * 256 + buff[j++] + 192;
+ if (!--ml)
+ break;
+ }
+
+ if ((int) ml - (int) skl < 0)
+ break;
+ ml -= skl;
+
+ nextone = j + skl;
+ skt = buff[j++];
+
+ switch (skt & 0x7f)
+ {
+ case 2: /* creation time */
+ {
+ if (skl < 4)
+ break;
+ sig_gen_time = 0;
+ for (i = 0; i < 4; i++)
+ sig_gen_time = (sig_gen_time << 8) + buff[j++];
+
+ break;
+ }
+ case 3: /* expiration time */
+ {
+ if (skl < 4)
+ break;
+ validity = 0;
+ for (i = 0; i < 4; i++)
+ validity = (validity << 8) + buff[j++];
+ break;
+ }
+ case 9: /* key expiration time */
+ {
+ if (skl < 4)
+ break;
+ key_validity = 0;
+ for (i = 0; i < 4; i++)
+ key_validity = (key_validity << 8) + buff[j++];
+ break;
+ }
+ case 16: /* issuer key ID */
+ {
+ if (skl < 8)
+ break;
+ signerid2 = signerid1 = 0;
+ for (i = 0; i < 4; i++)
+ signerid1 = (signerid1 << 8) + buff[j++];
+ for (i = 0; i < 4; i++)
+ signerid2 = (signerid2 << 8) + buff[j++];
+
+ break;
+ }
+ case 10: /* CMR key */
+ break;
+ case 4: /* exportable */
+ case 5: /* trust */
+ case 6: /* regexp */
+ case 7: /* revocable */
+ case 11: /* Pref. symm. alg. */
+ case 12: /* revocation key */
+ case 20: /* notation data */
+ case 21: /* pref. hash */
+ case 22: /* pref. comp.alg. */
+ case 23: /* key server prefs. */
+ case 24: /* pref. key server */
+ default:
+ {
+ if (skt & 0x80)
+ have_critical_spks = 1;
+ }
+ }
+ }
+ j = nextone;
+ }
+
+ if (sigtype == 0x20 || sigtype == 0x28)
+ p->flags |= KEYFLAG_REVOKED;
+ if (key_validity != -1 && time (NULL) > p->gen_time + key_validity)
+ p->flags |= KEYFLAG_EXPIRED;
+ if (have_critical_spks)
+ p->flags |= KEYFLAG_CRITICAL;
+
+ if (s)
+ {
+ s->sigtype = sigtype;
+ s->sid1 = signerid1;
+ s->sid2 = signerid2;
+ }
+
+
+ return 0;
+
+}
+
+
+static int pgp_parse_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *sig)
+{
+ if (!buff || l < 2 || !p)
+ return -1;
+
+ switch (buff[1])
+ {
+ case 2:
+ case 3:
+ return pgp_parse_pgp2_sig (buff, l, p, sig);
+ case 4:
+ return pgp_parse_pgp3_sig (buff, l, p, sig);
+ default:
+ return -1;
+ }
+}
+
+/* parse one key block, including all subkeys. */
+
+static pgp_key_t *pgp_parse_keyblock (FILE * fp)
+{
+ unsigned char *buff;
+ unsigned char pt = 0;
+ unsigned char last_pt;
+ size_t l;
+ short err = 0;
+
+#ifdef HAVE_FGETPOS
+ fpos_t pos;
+#else
+ long pos;
+#endif
+
+ pgp_key_t *root = NULL;
+ pgp_key_t **last = &root;
+ pgp_key_t *p = NULL;
+ pgp_uid_t *uid = NULL;
+ pgp_uid_t **addr = NULL;
+ pgp_sig_t **lsig = NULL;
+
+ FGETPOS(fp,pos);
+
+ while (!err && (buff = pgp_read_packet (fp, &l)) != NULL)
+ {
+ last_pt = pt;
+ pt = buff[0] & 0x3f;
+
+ /* check if we have read the complete key block. */
+
+ if ((pt == PT_SECKEY || pt == PT_PUBKEY) && root)
+ {
+ FSETPOS(fp, pos);
+ return root;
+ }
+
+ switch (pt)
+ {
+ case PT_SECKEY:
+ case PT_PUBKEY:
+ case PT_SUBKEY:
+ case PT_SUBSECKEY:
+ {
+ if (!(*last = p = pgp_parse_keyinfo (buff, l)))
+ {
+ err = 1;
+ break;
+ }
+
+ last = &p->next;
+ addr = &p->address;
+ lsig = &p->sigs;
+
+ if (pt == PT_SUBKEY || pt == PT_SUBSECKEY)
+ {
+ p->flags |= KEYFLAG_SUBKEY;
+ if (p != root)
+ {
+ p->parent = root;
+ p->address = pgp_copy_uids (root->address, p);
+ while (*addr) addr = &(*addr)->next;
+ }
+ }
+
+ if (pt == PT_SECKEY || pt == PT_SUBSECKEY)
+ p->flags |= KEYFLAG_SECRET;
+
+ break;
+ }
+
+ case PT_SIG:
+ {
+ if (lsig)
+ {
+ pgp_sig_t *signature = safe_calloc (sizeof (pgp_sig_t), 1);
+ *lsig = signature;
+ lsig = &signature->next;
+
+ pgp_parse_sig (buff, l, p, signature);
+ }
+ break;
+ }
+
+ case PT_TRUST:
+ {
+ if (p && (last_pt == PT_SECKEY || last_pt == PT_PUBKEY ||
+ last_pt == PT_SUBKEY || last_pt == PT_SUBSECKEY))
+ {
+ if (buff[1] & 0x20)
+ {
+ p->flags |= KEYFLAG_DISABLED;
+ }
+ }
+ else if (last_pt == PT_NAME && uid)
+ {
+ uid->trust = buff[1];
+ }
+ break;
+ }
+ case PT_NAME:
+ {
+ char *chr;
+
+
+ if (!addr)
+ break;
+
+ chr = safe_malloc (l);
+ memcpy (chr, buff + 1, l - 1);
+ chr[l - 1] = '\0';
+
+
+ *addr = uid = safe_calloc (1, sizeof (pgp_uid_t)); /* XXX */
+ uid->addr = chr;
+ uid->parent = p;
+ uid->trust = 0;
+ addr = &uid->next;
+ lsig = &uid->sigs;
+
+ /* the following tags are generated by
+ * pgp 2.6.3in.
+ */
+
+ if (strstr (chr, "ENCR"))
+ p->flags |= KEYFLAG_PREFER_ENCRYPTION;
+ if (strstr (chr, "SIGN"))
+ p->flags |= KEYFLAG_PREFER_SIGNING;
+
+ break;
+ }
+ }
+
+ FGETPOS(fp,pos);
+ }
+
+ if (err)
+ pgp_free_key (&root);
+
+ return root;
+}
+
+static int pgpring_string_matches_hint (const char *s, const char *hints[], int nhints)
+{
+ int i;
+
+ if (!hints || !nhints)
+ return 1;
+
+ for (i = 0; i < nhints; i++)
+ {
+ if (mutt_stristr (s, hints[i]) != NULL)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Go through the key ring file and look for keys with
+ * matching IDs.
+ */
+
+static void pgpring_find_candidates (char *ringfile, const char *hints[], int nhints)
+{
+ FILE *rfp;
+#ifdef HAVE_FGETPOS
+ fpos_t pos, keypos;
+#else
+ long pos, keypos;
+#endif
+
+ unsigned char *buff = NULL;
+ unsigned char pt = 0;
+ size_t l = 0;
+
+ short err = 0;
+
+ if ((rfp = fopen (ringfile, "r")) == NULL)
+ {
+ perror ("fopen");
+ return;
+ }
+
+ FGETPOS(rfp,pos);
+ FGETPOS(rfp,keypos);
+
+ while (!err && (buff = pgp_read_packet (rfp, &l)) != NULL)
+ {
+ pt = buff[0] & 0x3f;
+
+ if (l < 1)
+ continue;
+
+ if ((pt == PT_SECKEY) || (pt == PT_PUBKEY))
+ {
+ keypos = pos;
+ }
+ else if (pt == PT_NAME)
+ {
+ char *tmp = safe_malloc (l);
+
+ memcpy (tmp, buff + 1, l - 1);
+ tmp[l - 1] = '\0';
+
+ /* mutt_decode_utf8_string (tmp, chs); */
+
+ if (pgpring_string_matches_hint (tmp, hints, nhints))
+ {
+ pgp_key_t *p;
+
+ FSETPOS(rfp, keypos);
+
+ /* Not bailing out here would lead us into an endless loop. */
+
+ if ((p = pgp_parse_keyblock (rfp)) == NULL)
+ err = 1;
+
+ pgpring_dump_keyblock (p);
+ pgp_free_key (&p);
+ }
+
+ safe_free (&tmp);
+ }
+
+ FGETPOS(rfp,pos);
+ }
+
+ fclose (rfp);
+
+}
+
+static void print_userid (const char *id)
+{
+ for (; id && *id; id++)
+ {
+ if (*id >= ' ' && *id <= 'z' && *id != ':')
+ putchar (*id);
+ else
+ printf ("\\x%02x", *id);
+ }
+}
+
+static void pgpring_dump_signatures (pgp_sig_t *sig)
+{
+ for (; sig; sig = sig->next)
+ {
+ if (sig->sigtype == 0x10 || sig->sigtype == 0x11 ||
+ sig->sigtype == 0x12 || sig->sigtype == 0x13)
+ printf ("sig::::%08lX%08lX::::::%X:\n",
+ sig->sid1, sig->sid2, sig->sigtype);
+ else if (sig->sigtype == 0x20)
+ printf ("rev::::%08lX%08lX::::::%X:\n",
+ sig->sid1, sig->sid2, sig->sigtype);
+ }
+}
+
+
+static char gnupg_trustletter (int t)
+{
+ switch (t)
+ {
+ case 1: return 'n';
+ case 2: return 'm';
+ case 3: return 'f';
+ }
+ return 'q';
+}
+
+static void pgpring_dump_keyblock (pgp_key_t *p)
+{
+ pgp_uid_t *uid;
+ short first;
+ struct tm *tp;
+ time_t t;
+
+ for (; p; p = p->next)
+ {
+ first = 1;
+
+ if (p->flags & KEYFLAG_SECRET)
+ {
+ if (p->flags & KEYFLAG_SUBKEY)
+ printf ("ssb:");
+ else
+ printf ("sec:");
+ }
+ else
+ {
+ if (p->flags & KEYFLAG_SUBKEY)
+ printf ("sub:");
+ else
+ printf ("pub:");
+ }
+
+ if (p->flags & KEYFLAG_REVOKED)
+ putchar ('r');
+ if (p->flags & KEYFLAG_EXPIRED)
+ putchar ('e');
+ if (p->flags & KEYFLAG_DISABLED)
+ putchar ('d');
+
+ for (uid = p->address; uid; uid = uid->next, first = 0)
+ {
+ if (!first)
+ {
+ printf ("uid:%c::::::::", gnupg_trustletter (uid->trust));
+ print_userid (uid->addr);
+ printf (":\n");
+ }
+ else
+ {
+ if (p->flags & KEYFLAG_SECRET)
+ putchar ('u');
+ else
+ putchar (gnupg_trustletter (uid->trust));
+
+ t = p->gen_time;
+ tp = gmtime (&t);
+
+ printf (":%d:%d:%s:%04d-%02d-%02d::::", p->keylen, p->numalg, p->keyid,
+ 1900 + tp->tm_year, tp->tm_mon + 1, tp->tm_mday);
+
+ print_userid (uid->addr);
+ printf (":\n");
+ }
+
+ if (dump_signatures)
+ {
+ if (first) pgpring_dump_signatures (p->sigs);
+ pgpring_dump_signatures (uid->sigs);
+ }
+ }
+ }
+}
+
+/*
+ * The mutt_gettext () defined in gettext.c requires iconv,
+ * so we do without charset conversion here.
+ */
+
+char *mutt_gettext (const char *message)
+{
+ return (char *)message;
+}