Coverage Report

Created: 2025-03-11 06:49

/src/neomutt/ncrypt/gnupgparse.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Parse the output of CLI PGP program
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2021 Pietro Cerutti <gahr@gahr.ch>
7
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
/**
26
 * @page crypt_gnupg Parse the output of CLI PGP program
27
 *
28
 * Parse the output of CLI PGP program
29
 *
30
 * @note This code used to be the parser for GnuPG's output.
31
 *
32
 * Nowadays, we are using an external pubring lister with PGP which mimics
33
 * gpg's output format.
34
 */
35
36
#include "config.h"
37
#include <ctype.h>
38
#include <fcntl.h>
39
#include <iconv.h>
40
#include <stdbool.h>
41
#include <stdio.h>
42
#include <string.h>
43
#include <sys/types.h>
44
#include <time.h>
45
#include <unistd.h>
46
#include "mutt/lib.h"
47
#include "config/lib.h"
48
#include "email/lib.h"
49
#include "core/lib.h"
50
#include "gnupgparse.h"
51
#include "lib.h"
52
#include "pgpinvoke.h"
53
#include "pgpkey.h"
54
#ifdef CRYPT_BACKEND_CLASSIC_PGP
55
#include "pgplib.h"
56
#endif
57
58
/****************
59
 * Read the GNUPG keys.  For now we read the complete keyring by
60
 * calling gnupg in a special mode.
61
 *
62
 * The output format of gpgm is colon delimited with these fields:
63
 *   - record type ("pub","uid","sig","rev" etc.)
64
 *   - trust info
65
 *   - key length
66
 *   - pubkey algo
67
 *   - 16 hex digits with the long keyid
68
 *   - timestamp (1998-02-28)
69
 *   - Local id
70
 *   - ownertrust
71
 *   - name
72
 *   - signature class
73
 */
74
75
/// Cached copy of $charset
76
static char *Charset = NULL;
77
78
/**
79
 * fix_uid - Decode backslash-escaped user ids (in place)
80
 * @param uid String to decode
81
 */
82
static void fix_uid(char *uid)
83
0
{
84
0
  char *s = NULL, *d = NULL;
85
0
  iconv_t cd = ICONV_T_INVALID;
86
87
0
  for (s = uid, d = uid; *s;)
88
0
  {
89
0
    if ((s[0] == '\\') && (s[1] == 'x') && isxdigit((unsigned char) s[2]) &&
90
0
        isxdigit((unsigned char) s[3]))
91
0
    {
92
0
      *d++ = (hexval(s[2]) << 4) | hexval(s[3]);
93
0
      s += 4;
94
0
    }
95
0
    else
96
0
    {
97
0
      *d++ = *s++;
98
0
    }
99
0
  }
100
0
  *d = '\0';
101
102
0
  if (Charset && iconv_t_valid(cd = mutt_ch_iconv_open(Charset, "utf-8", MUTT_ICONV_NO_FLAGS)))
103
0
  {
104
0
    int n = s - uid + 1; /* chars available in original buffer */
105
106
0
    char *buf = MUTT_MEM_MALLOC(n + 1, char);
107
0
    const char *ib = uid;
108
0
    size_t ibl = d - uid + 1;
109
0
    char *ob = buf;
110
0
    size_t obl = n;
111
0
    iconv(cd, (ICONV_CONST char **) &ib, &ibl, &ob, &obl);
112
0
    if (ibl == 0)
113
0
    {
114
0
      if (ob - buf < n)
115
0
      {
116
0
        memcpy(uid, buf, ob - buf);
117
0
        uid[ob - buf] = '\0';
118
0
      }
119
0
      else if ((n >= 0) && ((ob - buf) == n) && (buf[n] = 0, (strlen(buf) < (size_t) n)))
120
0
      {
121
0
        memcpy(uid, buf, n);
122
0
      }
123
0
    }
124
0
    FREE(&buf);
125
0
  }
126
0
}
127
128
/**
129
 * parse_pub_line - Parse the 'pub' line from the pgp output
130
 * @param[in]  buf       Buffer containing string to parse
131
 * @param[out] is_subkey Is this a subkey of another key?
132
 * @param[in]  k         Key to from which to merge info (optional)
133
 * @retval ptr  PgpKeyInfo containing the (merged) results
134
 * @retval NULL Error
135
 */
136
static struct PgpKeyInfo *parse_pub_line(char *buf, bool *is_subkey, struct PgpKeyInfo *k)
137
0
{
138
0
  struct PgpUid *uid = NULL;
139
0
  int field = 0;
140
0
  bool is_uid = false;
141
0
  bool is_pub = false;
142
0
  bool is_fpr = false;
143
0
  char *pend = NULL, *p = NULL;
144
0
  int trust = 0;
145
0
  KeyFlags flags = KEYFLAG_NO_FLAGS;
146
0
  char tstr[11] = { 0 };
147
148
0
  *is_subkey = false;
149
0
  if (*buf == '\0')
150
0
    return NULL;
151
152
  /* if we're given a key, merge our parsing results, else
153
   * start with a fresh one to work with so that we don't
154
   * mess up the real key in case we find parsing errors. */
155
0
  struct PgpKeyInfo tmp = { 0 };
156
0
  if (k)
157
0
    memcpy(&tmp, k, sizeof(tmp));
158
159
0
  mutt_debug(LL_DEBUG2, "buf = '%s'\n", buf);
160
161
0
  const bool c_pgp_ignore_subkeys = cs_subset_bool(NeoMutt->sub, "pgp_ignore_subkeys");
162
0
  for (p = buf; p; p = pend)
163
0
  {
164
0
    pend = strchr(p, ':');
165
0
    if (pend)
166
0
      *pend++ = 0;
167
0
    field++;
168
0
    if ((*p == '\0') && (field != 1) && (field != 10))
169
0
      continue;
170
171
0
    if (is_fpr && (field != 10))
172
0
      continue;
173
174
0
    switch (field)
175
0
    {
176
0
      case 1: /* record type */
177
0
      {
178
0
        mutt_debug(LL_DEBUG2, "record type: %s\n", p);
179
180
0
        if (mutt_str_equal(p, "pub"))
181
0
          is_pub = true;
182
0
        else if (mutt_str_equal(p, "sub"))
183
0
          *is_subkey = true;
184
0
        else if (mutt_str_equal(p, "sec"))
185
0
          ; // do nothing
186
0
        else if (mutt_str_equal(p, "ssb"))
187
0
          *is_subkey = true;
188
0
        else if (mutt_str_equal(p, "uid"))
189
0
          is_uid = true;
190
0
        else if (mutt_str_equal(p, "fpr"))
191
0
          is_fpr = true;
192
0
        else
193
0
          return NULL;
194
195
0
        if (!(is_uid || is_fpr || (*is_subkey && c_pgp_ignore_subkeys)))
196
0
          memset(&tmp, 0, sizeof(tmp));
197
198
0
        break;
199
0
      }
200
0
      case 2: /* trust info */
201
0
      {
202
0
        mutt_debug(LL_DEBUG2, "trust info: %s\n", p);
203
204
0
        switch (*p)
205
0
        { /* look only at the first letter */
206
0
          case 'd':
207
0
            flags |= KEYFLAG_DISABLED;
208
0
            break;
209
0
          case 'e':
210
0
            flags |= KEYFLAG_EXPIRED;
211
0
            break;
212
0
          case 'f':
213
0
            trust = 3;
214
0
            break;
215
0
          case 'm':
216
0
            trust = 2;
217
0
            break;
218
0
          case 'n':
219
0
            trust = 1;
220
0
            break;
221
0
          case 'r':
222
0
            flags |= KEYFLAG_REVOKED;
223
0
            break;
224
0
          case 'u':
225
0
            trust = 3;
226
0
            break;
227
0
        }
228
229
0
        if (!is_uid && !(*is_subkey && c_pgp_ignore_subkeys))
230
0
          tmp.flags |= flags;
231
232
0
        break;
233
0
      }
234
0
      case 3: /* key length  */
235
0
      {
236
0
        mutt_debug(LL_DEBUG2, "key len: %s\n", p);
237
238
0
        if (!(*is_subkey && c_pgp_ignore_subkeys) && !mutt_str_atos_full(p, &tmp.keylen))
239
0
        {
240
0
          goto bail;
241
0
        }
242
0
        break;
243
0
      }
244
0
      case 4: /* pubkey algo */
245
0
      {
246
0
        mutt_debug(LL_DEBUG2, "pubkey algorithm: %s\n", p);
247
248
0
        if (!(*is_subkey && c_pgp_ignore_subkeys))
249
0
        {
250
0
          int x = 0;
251
0
          if (!mutt_str_atoi_full(p, &x))
252
0
            goto bail;
253
0
          tmp.numalg = x;
254
0
          tmp.algorithm = pgp_pkalgbytype(x);
255
0
        }
256
0
        break;
257
0
      }
258
0
      case 5: /* 16 hex digits with the long keyid. */
259
0
      {
260
0
        mutt_debug(LL_DEBUG2, "key id: %s\n", p);
261
262
0
        if (!(*is_subkey && c_pgp_ignore_subkeys))
263
0
          mutt_str_replace(&tmp.keyid, p);
264
0
        break;
265
0
      }
266
0
      case 6: /* timestamp (1998-02-28) */
267
0
      {
268
0
        mutt_debug(LL_DEBUG2, "time stamp: %s\n", p);
269
270
0
        if (strchr(p, '-')) /* gpg pre-2.0.10 used format (yyyy-mm-dd) */
271
0
        {
272
0
          struct tm time = { 0 };
273
274
0
          time.tm_sec = 0;
275
0
          time.tm_min = 0;
276
0
          time.tm_hour = 12;
277
0
          strncpy(tstr, p, 11);
278
0
          tstr[4] = '\0';
279
0
          tstr[7] = '\0';
280
0
          if (!mutt_str_atoi_full(tstr, &time.tm_year))
281
0
          {
282
0
            p = tstr;
283
0
            goto bail;
284
0
          }
285
0
          time.tm_year -= 1900;
286
0
          if (!mutt_str_atoi_full(tstr + 5, &time.tm_mon))
287
0
          {
288
0
            p = tstr + 5;
289
0
            goto bail;
290
0
          }
291
0
          time.tm_mon -= 1;
292
0
          if (!mutt_str_atoi_full(tstr + 8, &time.tm_mday))
293
0
          {
294
0
            p = tstr + 8;
295
0
            goto bail;
296
0
          }
297
0
          tmp.gen_time = mutt_date_make_time(&time, false);
298
0
        }
299
0
        else /* gpg 2.0.10+ uses seconds since 1970-01-01 */
300
0
        {
301
0
          unsigned long long secs;
302
303
0
          if (!mutt_str_atoull(p, &secs))
304
0
            goto bail;
305
0
          tmp.gen_time = (time_t) secs;
306
0
        }
307
0
        break;
308
0
      }
309
0
      case 7: /* valid for n days */
310
0
        break;
311
0
      case 8: /* Local id         */
312
0
        break;
313
0
      case 9: /* ownertrust       */
314
0
        break;
315
0
      case 10: /* name             */
316
0
      {
317
        /* Empty field or no trailing colon.
318
         * We allow an empty field for a pub record type because it is
319
         * possible for a primary uid record to have an empty User-ID
320
         * field.  Without any address records, it is not possible to
321
         * use the key in neomutt.  */
322
0
        if (!(pend && (*p || is_pub)))
323
0
          break;
324
325
0
        if (is_fpr)
326
0
        {
327
          /* don't let a subkey fpr overwrite an existing primary key fpr */
328
0
          if (!tmp.fingerprint)
329
0
            tmp.fingerprint = mutt_str_dup(p);
330
0
          break;
331
0
        }
332
333
        /* ignore user IDs on subkeys */
334
0
        if (!is_uid && (*is_subkey && c_pgp_ignore_subkeys))
335
0
          break;
336
337
0
        mutt_debug(LL_DEBUG2, "user ID: %s\n", NONULL(p));
338
339
0
        uid = MUTT_MEM_CALLOC(1, struct PgpUid);
340
0
        fix_uid(p);
341
0
        uid->addr = mutt_str_dup(p);
342
0
        uid->trust = trust;
343
0
        uid->flags |= flags;
344
0
        uid->next = tmp.address;
345
0
        tmp.address = uid;
346
347
0
        if (strstr(p, "ENCR"))
348
0
          tmp.flags |= KEYFLAG_PREFER_ENCRYPTION;
349
0
        if (strstr(p, "SIGN"))
350
0
          tmp.flags |= KEYFLAG_PREFER_SIGNING;
351
352
0
        break;
353
0
      }
354
0
      case 11: /* signature class  */
355
0
        break;
356
0
      case 12: /* key capabilities */
357
0
        mutt_debug(LL_DEBUG2, "capabilities info: %s\n", p);
358
359
0
        while (*p)
360
0
        {
361
0
          switch (*p++)
362
0
          {
363
0
            case 'D':
364
0
              flags |= KEYFLAG_DISABLED;
365
0
              break;
366
367
0
            case 'e':
368
0
              flags |= KEYFLAG_CANENCRYPT;
369
0
              break;
370
371
0
            case 's':
372
0
              flags |= KEYFLAG_CANSIGN;
373
0
              break;
374
0
          }
375
0
        }
376
377
0
        if (!is_uid && (!*is_subkey || !c_pgp_ignore_subkeys ||
378
0
                        !((flags & KEYFLAG_DISABLED) || (flags & KEYFLAG_REVOKED) ||
379
0
                          (flags & KEYFLAG_EXPIRED))))
380
0
        {
381
0
          tmp.flags |= flags;
382
0
        }
383
384
0
        break;
385
386
0
      default:
387
0
        break;
388
0
    }
389
0
  }
390
391
  /* merge temp key back into real key */
392
0
  if (!(is_uid || is_fpr || (*is_subkey && c_pgp_ignore_subkeys)))
393
0
    k = MUTT_MEM_MALLOC(1, struct PgpKeyInfo);
394
0
  if (!k)
395
0
    return NULL;
396
0
  memcpy(k, &tmp, sizeof(*k));
397
  /* fixup parentship of uids after merging the temp key into
398
   * the real key */
399
0
  if (tmp.address)
400
0
  {
401
0
    for (uid = k->address; uid; uid = uid->next)
402
0
      uid->parent = k;
403
0
  }
404
405
0
  return k;
406
407
0
bail:
408
0
  mutt_debug(LL_DEBUG1, "invalid number: '%s'\n", p);
409
0
  return NULL;
410
0
}
411
412
/**
413
 * pgp_get_candidates - Find PGP keys matching a list of hints
414
 * @param keyring PGP Keyring
415
 * @param hints   List of strings to match
416
 * @retval ptr  Key list
417
 * @retval NULL Error
418
 */
419
struct PgpKeyInfo *pgp_get_candidates(enum PgpRing keyring, struct ListHead *hints)
420
0
{
421
0
  FILE *fp = NULL;
422
0
  pid_t pid;
423
0
  char buf[1024] = { 0 };
424
0
  struct PgpKeyInfo *db = NULL, **kend = NULL, *k = NULL, *kk = NULL, *mainkey = NULL;
425
0
  bool is_sub = false;
426
427
0
  int fd_null = open("/dev/null", O_RDWR);
428
0
  if (fd_null == -1)
429
0
    return NULL;
430
431
0
  mutt_str_replace(&Charset, cc_charset());
432
433
0
  pid = pgp_invoke_list_keys(NULL, &fp, NULL, -1, -1, fd_null, keyring, hints);
434
0
  if (pid == -1)
435
0
  {
436
0
    close(fd_null);
437
0
    return NULL;
438
0
  }
439
440
0
  kend = &db;
441
0
  k = NULL;
442
0
  while (fgets(buf, sizeof(buf) - 1, fp))
443
0
  {
444
0
    kk = parse_pub_line(buf, &is_sub, k);
445
0
    if (!kk)
446
0
      continue;
447
448
    /* Only append kk to the list if it's new. */
449
0
    if (kk != k)
450
0
    {
451
0
      if (k)
452
0
        kend = &k->next;
453
0
      *kend = kk;
454
0
      k = kk;
455
456
0
      if (is_sub)
457
0
      {
458
0
        struct PgpUid **l = NULL;
459
460
0
        k->flags |= KEYFLAG_SUBKEY;
461
0
        k->parent = mainkey;
462
0
        for (l = &k->address; *l; l = &(*l)->next)
463
0
          ; // do nothing
464
465
0
        *l = pgp_copy_uids(mainkey->address, k);
466
0
      }
467
0
      else
468
0
      {
469
0
        mainkey = k;
470
0
      }
471
0
    }
472
0
  }
473
474
0
  if (ferror(fp))
475
0
    mutt_perror("fgets");
476
477
0
  mutt_file_fclose(&fp);
478
0
  filter_wait(pid);
479
480
0
  close(fd_null);
481
482
0
  return db;
483
0
}