tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

pgp.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : pgp.c
4 * Author : Steven J. Madsen
5 * Created : 1995-05-12
6 * Updated : 2021-02-27
7 * Notes : PGP support
8 *
9 * Copyright (c) 1995-2022 Steven J. Madsen <steve@erinet.com>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41#ifndef TIN_H
42# include "tin.h"
43#endif /* !TIN_H */
44
45#ifdef HAVE_PGP_GPG
46# ifndef TCURSES_H
47# include "tcurses.h"
48# endif /* !TCURSES_H */
49
50
51/*
52 * The first two args are typically the PGP command name and then $PGPOPTS
53 * NB: The '1' variations on DO_{SIGN,BOTH} are used when local-user name is
54 * used and are valid only when signing
55 */
56# if defined(HAVE_PGP) /* pgp-2 */
57# define PGPNAME PATH_PGP
58# define PGPDIR ".pgp"
59# define PGP_PUBRING "pubring.pgp"
60# define CHECK_SIGN "%s %s -f <%s %s"
61# define ADD_KEY "%s %s -ka %s"
62# define APPEND_KEY "%s %s -kxa %s %s", PGPNAME, pgpopts, buf, keyfile
63# define DO_ENCRYPT "%s %s -ate %s %s", PGPNAME, pgpopts, pt, mailto
64# define DO_SIGN "%s %s -ats %s %s", PGPNAME, pgpopts, pt, mailto
65# define DO_SIGN1 "%s %s -ats %s %s -u %s", PGPNAME, pgpopts, pt, mailto, mailfrom
66# define DO_BOTH "%s %s -ates %s %s", PGPNAME, pgpopts, pt, mailto
67# define DO_BOTH1 "%s %s -ates %s %s -u %s", PGPNAME, pgpopts, pt, mailto, mailfrom
68# endif /* HAVE_PGP */
69
70# if defined(HAVE_PGPK) /* pgp-5 */
71# define PGPNAME "pgp" /* FIXME: this is AFAIK not PATH_PGPK */
72# define PGPDIR ".pgp"
73# define PGP_PUBRING "pubring.pkr"
74# define CHECK_SIGN "%sv %s -f <%s %s"
75# define ADD_KEY "%sk %s -a %s"
76# define APPEND_KEY "%sk %s -xa %s -o %s", PGPNAME, pgpopts, keyfile, buf
77# define DO_ENCRYPT "%se %s -at %s %s", PGPNAME, pgpopts, pt, mailto
78# define DO_SIGN "%ss %s -at %s %s", PGPNAME, pgpopts, pt, mailto
79# define DO_SIGN1 "%ss %s -at %s %s -u %s", PGPNAME, pgpopts, pt, mailto, mailfrom
80# define DO_BOTH "%se %s -ats %s %s", PGPNAME, pgpopts, pt, mailto
81# define DO_BOTH1 "%se %s -ats %s %s -u %s", PGPNAME, pgpopts, pt, mailto, mailfrom
82# endif /* HAVE_PGPK */
83
84# if defined(HAVE_GPG) /* gpg */
85# define PGPNAME PATH_GPG
86# define PGPDIR ".gnupg"
87# define PGP_PUBRING "pubring.gpg"
88# if 0 /* gpg 1.4.11 doesn't like this */
89# define CHECK_SIGN "%s %s --no-batch --decrypt <%s %s"
90# else
91# define CHECK_SIGN "%s %s < %s %s"
92# endif /* 0 */
93# define ADD_KEY "%s %s --no-batch --import %s"
94# define APPEND_KEY "%s %s --no-batch --armor --output %s --export %s", PGPNAME, pgpopts, keyfile, buf
95# define DO_ENCRYPT \
96"%s %s --textmode --armor --no-batch --output %s.asc --recipient %s --encrypt %s", \
97PGPNAME, pgpopts, pt, mailto, pt
98# define DO_SIGN \
99"%s %s --textmode --armor --no-batch --output %s.asc --escape-from --clearsign %s", \
100PGPNAME, pgpopts, pt, pt
101# define DO_SIGN1 \
102"%s %s --textmode --armor --no-batch --local-user %s --output %s.asc --escape-from --clearsign %s", \
103PGPNAME, pgpopts, mailfrom, pt, pt
104# define DO_BOTH \
105"%s %s --textmode --armor --no-batch --output %s.asc --recipient %s --sign --encrypt %s", \
106PGPNAME, pgpopts, pt, mailto, pt
107# define DO_BOTH1 \
108"%s %s --textmode --armor --no-batch --output %s.asc --recipient %s --local-user %s --sign --encrypt %s", \
109PGPNAME, pgpopts, pt, mailto, mailfrom, pt
110# endif /* HAVE_GPG */
111
112/* RFC 4880 6.2 */
113# define PGP_SIG_TAG "-----BEGIN PGP SIGNED MESSAGE-----\n"
114# define PGP_KEY_TAG "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
115
116# define HEADERS "tin-%ld.h"
117# ifdef HAVE_LONG_FILE_NAMES
118# define PLAINTEXT "tin-%ld.pt"
119# define CIPHERTEXT "tin-%ld.pt.asc"
120# define KEYFILE "tin-%ld.k.asc"
121# else
122# define PLAINTEXT "tn-%ld.p"
123# define CIPHERTEXT "tn-%ld.p.asc"
124# define KEYFILE "tn-%ld.k.asc"
125# endif /* HAVE_LONG_FILE_NAMES */
126
127
128/*
129 * local prototypes
130 */
131static t_bool pgp_available(void);
132static void do_pgp(t_function what, const char *file, const char *mail_to);
133static void join_files(const char *file);
134static void pgp_append_public_key(char *file);
135static void split_file(const char *file);
136
137static char pgp_data[PATH_LEN];
138static char hdr[PATH_LEN], pt[PATH_LEN], ct[PATH_LEN];
139static const char *pgpopts = "";
140
141
142void
143init_pgp(
144 void)
145{
146 char *ptr;
147
148 pgpopts = get_val("PGPOPTS", "");
149
150# ifdef HAVE_GPG
151 if ((ptr = getenv("GNUPGHOME")) != NULL)
152 my_strncpy(pgp_data, ptr, sizeof(pgp_data) - 1);
153 else
154# endif /* HAVE_GPG */
155 {
156 if ((ptr = getenv("PGPPATH")) != NULL)
157 my_strncpy(pgp_data, ptr, sizeof(pgp_data) - 1);
158 else
159 joinpath(pgp_data, sizeof(pgp_data), homedir, PGPDIR);
160 }
161}
162
163
164/*
165 * Write the header file then the ciphertext file to the art file
166 * This function is void, no way to return errs
167 */
168static void
169join_files(
170 const char *file)
171{
172 FILE *art, *header, *text;
173
174 if ((header = fopen(hdr, "r")) != NULL) {
175 if ((text = fopen(ct, "r")) != NULL) {
176 if ((art = fopen(file, "w")) != NULL) {
177 if (copy_fp(header, art))
178 copy_fp(text, art);
179 fclose(art);
180 }
181 fclose(text);
182 }
183 fclose(header);
184 }
185
186 unlink(hdr);
187 unlink(pt);
188 unlink(ct);
189}
190
191
192/*
193 * Split the file parameter into a header file 'hdr' and a plaintext
194 * file 'pt'
195 */
196static void
197split_file(
198 const char *file)
199{
200 FILE *art, *header, *plaintext;
201 char buf[LEN];
202 char tmp[PATH_LEN];
203 mode_t mask;
204
205 snprintf(tmp, sizeof(tmp), HEADERS, (long) process_id);
206 joinpath(hdr, sizeof(hdr), TMPDIR, tmp);
207 snprintf(tmp, sizeof(tmp), PLAINTEXT, (long) process_id);
208 joinpath(pt, sizeof(pt), TMPDIR, tmp);
209 snprintf(tmp, sizeof(tmp), CIPHERTEXT, (long) process_id);
210 joinpath(ct, sizeof(ct), TMPDIR, tmp);
211
212 if ((art = fopen(file, "r")) == NULL)
213 return;
214
215 mask = umask((mode_t) (S_IRWXO|S_IRWXG));
216
217 if ((header = fopen(hdr, "w")) == NULL)
218 goto err_art;
219
220 if ((plaintext = fopen(pt, "w")) == NULL)
221 goto err_hdr;
222
223 if (fgets(buf, LEN, art) != NULL) { /* Copy the hdr up to and including the \n */
224 while (strcmp(buf, "\n")) {
225 fputs(buf, header);
226 fgets(buf, LEN, art);
227 }
228 fputs(buf, header);
229 copy_fp(art, plaintext);
230 }
231
232 fclose(plaintext);
233err_hdr:
234 fclose(header);
235err_art:
236 fclose(art);
237 umask(mask);
238}
239
240
241static void
242do_pgp(
243 t_function what,
244 const char *file,
245 const char *mail_to)
246{
247 char cmd[LEN];
248 char mailfrom[LEN];
249 const char *mailto = BlankIfNull(mail_to);
250
251 mailfrom[0] = '\0';
252
253 split_file(file);
254
255 /*
256 * <mailfrom> is valid only when signing and a local address exists
257 */
258 if ((CURR_GROUP.attribute->from) != NULL)
259 strip_name(CURR_GROUP.attribute->from, mailfrom);
260
261 switch (what) {
262 case PGP_KEY_SIGN:
263 if (strlen(mailfrom))
264 sh_format(cmd, sizeof(cmd), DO_SIGN1);
265 else
266 sh_format(cmd, sizeof(cmd), DO_SIGN);
267 invoke_cmd(cmd);
268 break;
269
270 case PGP_KEY_ENCRYPT_SIGN:
271 if (strlen(mailfrom))
272 sh_format(cmd, sizeof(cmd), DO_BOTH1);
273 else
274 sh_format(cmd, sizeof(cmd), DO_BOTH);
275 invoke_cmd(cmd);
276 break;
277
278 case PGP_KEY_ENCRYPT:
279 sh_format(cmd, sizeof(cmd), DO_ENCRYPT);
280 invoke_cmd(cmd);
281 break;
282
283 default:
284 break;
285 }
286
287 join_files(file);
288}
289
290
291static void
292pgp_append_public_key(
293 char *file)
294{
295 FILE *fp, *key;
296 char cmd[LEN], buf[LEN];
297 char keyfile[PATH_LEN], tmp[PATH_LEN];
298
299 if ((CURR_GROUP.attribute->from) != NULL && strlen(CURR_GROUP.attribute->from))
300 strip_name(CURR_GROUP.attribute->from, buf);
301 else
302 snprintf(buf, sizeof(buf), "%s@%s", userid, BlankIfNull(get_host_name()));
303
304 snprintf(tmp, sizeof(tmp), KEYFILE, (long) process_id);
305 joinpath(keyfile, sizeof(keyfile), TMPDIR, tmp);
306
307/*
308 * TODO: I'm guessing the pgp append key command creates 'keyfile' and that
309 * we should remove it
310 */
311 sh_format(cmd, sizeof(cmd), APPEND_KEY);
312 if (invoke_cmd(cmd)) {
313 if ((fp = fopen(file, "a")) != NULL) {
314 if ((key = fopen(keyfile, "r")) != NULL) {
315 fputc('\n', fp); /* Add a blank line */
316 copy_fp(key, fp); /* and copy in the key */
317 fclose(key);
318 }
319 fclose(fp);
320 }
321 unlink(keyfile);
322 }
323}
324
325
326/*
327 * Simply check for existence of keyring file
328 */
329static t_bool
330pgp_available(
331 void)
332{
333 FILE *fp;
334 char keyring[PATH_LEN];
335
336 joinpath(keyring, sizeof(keyring), pgp_data, PGP_PUBRING);
337 if ((fp = fopen(keyring, "r")) == NULL) {
338 wait_message(2, _(txt_pgp_not_avail), keyring);
339 return FALSE;
340 }
341
342 fclose(fp);
343 return TRUE;
344}
345
346
347void
348invoke_pgp_mail(
349 const char *nam,
350 char *mail_to)
351{
352 char keyboth[MAXKEYLEN], keyencrypt[MAXKEYLEN], keyquit[MAXKEYLEN];
353 char keysign[MAXKEYLEN];
354 t_function func, default_func = PGP_KEY_SIGN;
355
356 if (!pgp_available())
357 return;
358
359 func = prompt_slk_response(default_func, pgp_mail_keys, _(txt_pgp_mail),
360 PrintFuncKey(keyencrypt, PGP_KEY_ENCRYPT, pgp_mail_keys),
361 PrintFuncKey(keysign, PGP_KEY_SIGN, pgp_mail_keys),
362 PrintFuncKey(keyboth, PGP_KEY_ENCRYPT_SIGN, pgp_mail_keys),
363 PrintFuncKey(keyquit, GLOBAL_QUIT, pgp_mail_keys));
364 switch (func) {
365 case PGP_KEY_SIGN:
366# ifdef HAVE_PGPK
367 ClearScreen();
368 MoveCursor(cLINES - 7, 0);
369# endif /* HAVE_PGPK */
370 do_pgp(func, nam, NULL);
371 break;
372
373 case PGP_KEY_ENCRYPT_SIGN:
374# ifdef HAVE_PGPK
375 ClearScreen();
376 MoveCursor(cLINES - 7, 0);
377# endif /* HAVE_PGPK */
378 do_pgp(func, nam, mail_to);
379 break;
380
381 case PGP_KEY_ENCRYPT:
382 do_pgp(func, nam, mail_to);
383 break;
384
385 case GLOBAL_ABORT:
386 case GLOBAL_QUIT:
387 default:
388 break;
389 }
390}
391
392
393void
394invoke_pgp_news(
395 char *artfile)
396{
397 char keyinclude[MAXKEYLEN], keyquit[MAXKEYLEN], keysign[MAXKEYLEN];
398 t_function func, default_func = PGP_KEY_SIGN;
399
400 if (!pgp_available())
401 return;
402
403 func = prompt_slk_response(default_func, pgp_news_keys, _(txt_pgp_news),
404 PrintFuncKey(keysign, PGP_KEY_SIGN, pgp_news_keys),
405 PrintFuncKey(keyinclude, PGP_INCLUDE_KEY, pgp_news_keys),
406 PrintFuncKey(keyquit, GLOBAL_QUIT, pgp_news_keys));
407 switch (func) {
408 case GLOBAL_ABORT:
409 case GLOBAL_QUIT:
410 break;
411
412 case PGP_KEY_SIGN:
413# ifdef HAVE_PGPK
414 info_message(" ");
415 MoveCursor(cLINES - 7, 0);
416 my_printf("\n");
417# endif /* HAVE_PGPK */
418 do_pgp(func, artfile, NULL);
419 break;
420
421 case PGP_INCLUDE_KEY:
422# ifdef HAVE_PGPK
423 info_message(" ");
424 MoveCursor(cLINES - 7, 0);
425 my_printf("\n");
426# endif /* HAVE_PGPK */
427 do_pgp(PGP_KEY_SIGN, artfile, NULL);
428 pgp_append_public_key(artfile);
429 break;
430
431 default:
432 break;
433 }
434}
435
436
437t_bool
438pgp_check_article(
439 t_openartinfo *artinfo)
440{
441 FILE *art;
442 char artfile[PATH_LEN], buf[LEN], cmd[LEN];
443 t_bool pgp_signed = FALSE;
444 t_bool pgp_key = FALSE;
445
446 if (!pgp_available())
447 return FALSE;
448
449 joinpath(artfile, sizeof(artfile), homedir, TIN_ARTICLE_NAME);
450# ifdef APPEND_PID
451 snprintf(artfile + strlen(artfile), sizeof(artfile) - strlen(artfile), ".%ld", (long) process_id);
452# endif /* APPEND_PID */
453 if ((art = fopen(artfile, "w")) == NULL) {
454 info_message(_(txt_cannot_open), artfile);
455 return FALSE;
456 }
457 /* -> start of body */
458 if (fseek(artinfo->raw, artinfo->hdr.ext->offset, SEEK_SET) != 0) {
459 fclose(art);
460 return FALSE;
461 }
462
463 if (fgets(buf, LEN, artinfo->raw) != NULL) { /* Copy the body whilst looking for SIG/KEY tags */
464 while (!feof(artinfo->raw)) {
465 if (!pgp_signed && !strcmp(buf, PGP_SIG_TAG))
466 pgp_signed = TRUE;
467 if (!pgp_key && !strcmp(buf, PGP_KEY_TAG))
468 pgp_key = TRUE;
469 fputs(buf, art);
470 fgets(buf, LEN, artinfo->raw);
471 }
472 }
473 fclose(art);
474
475 if (!(pgp_signed || pgp_key)) {
476 info_message(_(txt_pgp_nothing));
477 return FALSE;
478 }
479 ClearScreen();
480
481 if (pgp_signed) {
482 Raw(FALSE);
483
484 /*
485 * We don't use sh_format here else the redirection get misquoted
486 */
487 snprintf(cmd, sizeof(cmd), CHECK_SIGN, PGPNAME, pgpopts, artfile, REDIRECT_PGP_OUTPUT);
488 invoke_cmd(cmd);
489 my_printf("\n");
490 Raw(TRUE);
491 }
492# ifndef USE_CURSES
493 EndWin();
494 Raw(FALSE);
495# endif /* !USE_CURSES */
497# ifndef USE_CURSES
498 Raw(TRUE);
499 InitWin();
500# endif /* !USE_CURSES */
501 if (pgp_key) {
502 if (prompt_yn(_(txt_pgp_add), FALSE) == 1) {
503 Raw(FALSE);
504
505 sh_format(cmd, sizeof(cmd), ADD_KEY, PGPNAME, pgpopts, artfile);
506 invoke_cmd(cmd);
507 my_printf("\n");
508 Raw(TRUE);
509 }
510 }
511
512 unlink(artfile);
513 return TRUE;
514}
515#endif /* HAVE_PGP_GPG */
unsigned t_bool
Definition: bool.h:77
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
static t_openartinfo * art
Definition: cook.c:78
char homedir[PATH_LEN]
Definition: init.c:78
pid_t process_id
Definition: init.c:125
constext txt_cannot_open[]
Definition: lang.c:128
char userid[PATH_LEN]
Definition: init.c:107
int cLINES
Definition: curses.c:52
#define MAXKEYLEN
Definition: keymap.h:136
t_function prompt_slk_response(t_function default_func, const struct keylist keys, const char *fmt,...)
Definition: prompt.c:699
@ GLOBAL_ABORT
Definition: keymap.h:186
@ GLOBAL_QUIT
Definition: keymap.h:210
enum defined_functions t_function
Definition: keymap.h:375
#define PrintFuncKey(buf, func, keys)
Definition: keymap.h:445
static char buf[16]
Definition: langinfo.c:50
void prompt_continue(void)
Definition: prompt.c:803
const char * get_host_name(void)
Definition: header.c:52
const char * get_val(const char *env, const char *def)
Definition: misc.c:361
int sh_format(char *dst, size_t len, const char *fmt,...)
Definition: string.c:677
void ClearScreen(void)
Definition: curses.c:410
void info_message(const char *fmt,...)
Definition: screen.c:102
t_bool invoke_cmd(const char *nam)
Definition: misc.c:813
t_bool copy_fp(FILE *fp_ip, FILE *fp_op)
Definition: misc.c:179
void EndWin(void)
Definition: curses.c:368
void joinpath(char *result, size_t result_size, const char *dir, const char *file)
Definition: joinpath.c:50
void MoveCursor(int row, int col)
Definition: curses.c:441
void Raw(int state)
Definition: curses.c:624
void InitWin(void)
Definition: curses.c:355
void wait_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:133
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:196
void strip_name(const char *from, char *address)
Definition: misc.c:2390
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
void(* func)(SIG_ARGS)
Definition: signal.c:176
struct t_header hdr
Definition: rfc2046.h:185
FILE * raw
Definition: rfc2046.h:188
long offset
Definition: rfc2046.h:103
t_part * ext
Definition: rfc2046.h:146
#define my_printf
Definition: tcurses.h:175
#define LEN
Definition: tin.h:860
#define SEEK_SET
Definition: tin.h:2512
#define TMPDIR
Definition: tin.h:2170
#define S_IRWXG
Definition: tin.h:2189
#define unlink(file)
Definition: tin.h:387
#define S_IRWXO
Definition: tin.h:2194
#define REDIRECT_PGP_OUTPUT
Definition: tin.h:2165
#define CURR_GROUP
Definition: tin.h:1054
#define _(Text)
Definition: tin.h:94
#define PATH_LEN
Definition: tin.h:843
#define snprintf
Definition: tin.h:2464
#define TIN_ARTICLE_NAME
Definition: tin.h:2520
#define BlankIfNull(p)
Definition: tin.h:2255