Update estream.
[gnupg.git] / tools / gpgparsemail.c
blob6265efc930dd2d6872a2b821888d57632df2f019
1 /* gpgparsemail.c - Standalone crypto mail parser
2 * Copyright (C) 2004, 2007 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
6 * GnuPG is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * GnuPG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 /* This utility prints an RFC8222, possible MIME structured, message
22 in an annotated format with the first column having an indicator
23 for the content of the line. Several options are available to
24 scrutinize the message. S/MIME and OpenPGP support is included. */
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stddef.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <assert.h>
37 #include <time.h>
38 #include <signal.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <sys/wait.h>
43 #include "rfc822parse.h"
46 #define PGM "gpgparsemail"
48 /* Option flags. */
49 static int verbose;
50 static int debug;
51 static int opt_crypto; /* Decrypt or verify messages. */
52 static int opt_no_header; /* Don't output the header lines. */
54 /* Structure used to communicate with the parser callback. */
55 struct parse_info_s {
56 int show_header; /* Show the header lines. */
57 int show_data; /* Show the data lines. */
58 unsigned int skip_show; /* Temporary disable above for these
59 number of lines. */
60 int show_data_as_note; /* The next data line should be shown
61 as a note. */
62 int show_boundary;
63 int nesting_level;
65 int is_pkcs7; /* Old style S/MIME message. */
67 int moss_state; /* State of PGP/MIME or S/MIME parsing. */
68 int is_smime; /* This is S/MIME and not PGP/MIME. */
70 char *signing_protocol;
71 int hashing_level; /* The nesting level we are hashing. */
72 int hashing;
73 FILE *hash_file;
75 FILE *sig_file; /* Signature part with MIME or full
76 pkcs7 data if IS_PCKS7 is set. */
77 int verify_now; /* Flag set when all signature data is
78 available. */
82 /* Print diagnostic message and exit with failure. */
83 static void
84 die (const char *format, ...)
86 va_list arg_ptr;
88 fflush (stdout);
89 fprintf (stderr, "%s: ", PGM);
91 va_start (arg_ptr, format);
92 vfprintf (stderr, format, arg_ptr);
93 va_end (arg_ptr);
94 putc ('\n', stderr);
96 exit (1);
100 /* Print diagnostic message. */
101 static void
102 err (const char *format, ...)
104 va_list arg_ptr;
106 fflush (stdout);
107 fprintf (stderr, "%s: ", PGM);
109 va_start (arg_ptr, format);
110 vfprintf (stderr, format, arg_ptr);
111 va_end (arg_ptr);
112 putc ('\n', stderr);
115 static void *
116 xmalloc (size_t n)
118 void *p = malloc (n);
119 if (!p)
120 die ("out of core: %s", strerror (errno));
121 return p;
124 /* static void * */
125 /* xcalloc (size_t n, size_t m) */
126 /* { */
127 /* void *p = calloc (n, m); */
128 /* if (!p) */
129 /* die ("out of core: %s", strerror (errno)); */
130 /* return p; */
131 /* } */
133 /* static void * */
134 /* xrealloc (void *old, size_t n) */
135 /* { */
136 /* void *p = realloc (old, n); */
137 /* if (!p) */
138 /* die ("out of core: %s", strerror (errno)); */
139 /* return p; */
140 /* } */
142 static char *
143 xstrdup (const char *string)
145 void *p = malloc (strlen (string)+1);
146 if (!p)
147 die ("out of core: %s", strerror (errno));
148 strcpy (p, string);
149 return p;
152 #ifndef HAVE_STPCPY
153 static char *
154 stpcpy (char *a,const char *b)
156 while (*b)
157 *a++ = *b++;
158 *a = 0;
160 return (char*)a;
162 #endif
164 static int
165 run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
167 int rp[2];
168 pid_t pid;
169 int i, c, is_status;
170 unsigned int pos;
171 char status_buf[10];
172 FILE *fp;
174 if (pipe (rp) == -1)
175 die ("error creating a pipe: %s", strerror (errno));
177 pid = fork ();
178 if (pid == -1)
179 die ("error forking process: %s", strerror (errno));
181 if (!pid)
182 { /* Child. */
183 char data_fd_buf[50];
184 int fd;
186 /* Connect our signature fd to stdin. */
187 if (sig_fd != 0)
189 if (dup2 (sig_fd, 0) == -1)
190 die ("dup2 stdin failed: %s", strerror (errno));
193 /* Keep our data fd and format it for gpg/gpgsm use. */
194 if (data_fd == -1)
195 *data_fd_buf = 0;
196 else
197 sprintf (data_fd_buf, "-&%d", data_fd);
199 /* Send stdout to the bit bucket. */
200 fd = open ("/dev/null", O_WRONLY);
201 if (fd == -1)
202 die ("can't open `/dev/null': %s", strerror (errno));
203 if (fd != 1)
205 if (dup2 (fd, 1) == -1)
206 die ("dup2 stderr failed: %s", strerror (errno));
209 /* Connect stderr to our pipe. */
210 if (rp[1] != 2)
212 if (dup2 (rp[1], 2) == -1)
213 die ("dup2 stderr failed: %s", strerror (errno));
216 /* Close other files. */
217 for (i=0; (fd=close_list[i]) != -1; i++)
218 if (fd > 2 && fd != data_fd)
219 close (fd);
220 errno = 0;
222 if (smime)
223 execlp ("gpgsm", "gpgsm",
224 "--enable-special-filenames",
225 "--status-fd", "2",
226 "--assume-base64",
227 "--verify",
228 "--",
229 "-", data_fd == -1? NULL : data_fd_buf,
230 NULL);
231 else
232 execlp ("gpg", "gpg",
233 "--enable-special-filenames",
234 "--status-fd", "2",
235 "--verify",
236 "--debug=512",
237 "--",
238 "-", data_fd == -1? NULL : data_fd_buf,
239 NULL);
241 die ("failed to exec the crypto command: %s", strerror (errno));
244 /* Parent. */
245 close (rp[1]);
247 fp = fdopen (rp[0], "r");
248 if (!fp)
249 die ("can't fdopen pipe for reading: %s", strerror (errno));
251 pos = 0;
252 is_status = 0;
253 assert (sizeof status_buf > 9);
254 while ((c=getc (fp)) != EOF)
256 if (pos < 9)
257 status_buf[pos] = c;
258 else
260 if (pos == 9)
262 is_status = !memcmp (status_buf, "[GNUPG:] ", 9);
263 if (is_status)
264 fputs ( "c ", stdout);
265 else if (verbose)
266 fputs ( "# ", stdout);
267 fwrite (status_buf, 9, 1, stdout);
269 putchar (c);
271 if (c == '\n')
273 if (verbose && pos < 9)
275 fputs ( "# ", stdout);
276 fwrite (status_buf, pos+1, 1, stdout);
278 pos = 0;
280 else
281 pos++;
283 if (pos)
285 if (verbose && pos < 9)
287 fputs ( "# ", stdout);
288 fwrite (status_buf, pos+1, 1, stdout);
290 putchar ('\n');
292 fclose (fp);
294 while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
296 if (i == -1)
297 die ("waiting for child failed: %s", strerror (errno));
299 return 0;
305 /* Verify the signature in the current temp files. */
306 static void
307 verify_signature (struct parse_info_s *info)
309 int close_list[10];
311 if (info->is_pkcs7)
313 assert (!info->hash_file);
314 assert (info->sig_file);
315 rewind (info->sig_file);
317 else
319 assert (info->hash_file);
320 assert (info->sig_file);
321 rewind (info->hash_file);
322 rewind (info->sig_file);
325 /* printf ("# Begin hashed data\n"); */
326 /* while ( (c=getc (info->hash_file)) != EOF) */
327 /* putchar (c); */
328 /* printf ("# End hashed data signature\n"); */
329 /* printf ("# Begin signature\n"); */
330 /* while ( (c=getc (info->sig_file)) != EOF) */
331 /* putchar (c); */
332 /* printf ("# End signature\n"); */
333 /* rewind (info->hash_file); */
334 /* rewind (info->sig_file); */
336 close_list[0] = -1;
337 run_gnupg (info->is_smime, fileno (info->sig_file),
338 info->hash_file ? fileno (info->hash_file) : -1, close_list);
345 /* Prepare for a multipart/signed.
346 FIELD_CTX is the parsed context of the content-type header.*/
347 static void
348 mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg,
349 rfc822parse_field_t field_ctx)
351 const char *s;
353 (void)msg;
355 s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
356 if (s)
358 printf ("h signed.protocol: %s\n", s);
359 if (!strcmp (s, "application/pgp-signature"))
361 if (info->moss_state)
362 err ("note: ignoring nested PGP/MIME or S/MIME signature");
363 else
365 info->moss_state = 1;
366 info->is_smime = 0;
367 free (info->signing_protocol);
368 info->signing_protocol = xstrdup (s);
371 else if (!strcmp (s, "application/pkcs7-signature")
372 || !strcmp (s, "application/x-pkcs7-signature"))
374 if (info->moss_state)
375 err ("note: ignoring nested PGP/MIME or S/MIME signature");
376 else
378 info->moss_state = 1;
379 info->is_smime = 1;
380 free (info->signing_protocol);
381 info->signing_protocol = xstrdup (s);
384 else if (verbose)
385 printf ("# this protocol is not supported\n");
390 /* Prepare for a multipart/encrypted.
391 FIELD_CTX is the parsed context of the content-type header.*/
392 static void
393 mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
394 rfc822parse_field_t field_ctx)
396 const char *s;
398 (void)info;
399 (void)msg;
401 s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
402 if (s)
403 printf ("h encrypted.protocol: %s\n", s);
407 /* Prepare for old-style pkcs7 messages. */
408 static void
409 pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg,
410 rfc822parse_field_t field_ctx)
412 const char *s;
414 (void)msg;
416 s = rfc822parse_query_parameter (field_ctx, "name", 0);
417 if (s)
418 printf ("h pkcs7.name: %s\n", s);
419 if (info->is_pkcs7)
420 err ("note: ignoring nested pkcs7 data");
421 else
423 info->is_pkcs7 = 1;
424 if (opt_crypto)
426 assert (!info->sig_file);
427 info->sig_file = tmpfile ();
428 if (!info->sig_file)
429 die ("error creating temp file: %s", strerror (errno));
435 /* Print the event received by the parser for debugging as comment
436 line. */
437 static void
438 show_event (rfc822parse_event_t event)
440 const char *s;
442 switch (event)
444 case RFC822PARSE_OPEN: s= "Open"; break;
445 case RFC822PARSE_CLOSE: s= "Close"; break;
446 case RFC822PARSE_CANCEL: s= "Cancel"; break;
447 case RFC822PARSE_T2BODY: s= "T2Body"; break;
448 case RFC822PARSE_FINISH: s= "Finish"; break;
449 case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
450 case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
451 case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
452 case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
453 case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
454 case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
455 case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
456 case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
457 default: s= "[unknown event]"; break;
459 printf ("# *** got RFC822 event %s\n", s);
462 /* This function is called by the parser to communicate events. This
463 callback comminucates with the main program using a structure
464 passed in OPAQUE. Should retrun 0 or set errno and return -1. */
465 static int
466 message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
468 struct parse_info_s *info = opaque;
470 if (debug)
471 show_event (event);
473 if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
475 /* We need to check here whether to start collecting signed data
476 because attachments might come without header lines and thus
477 we won't see the BEGIN_HEADER event. */
478 if (info->moss_state == 1)
480 printf ("c begin_hash\n");
481 info->hashing = 1;
482 info->hashing_level = info->nesting_level;
483 info->moss_state++;
485 if (opt_crypto)
487 assert (!info->hash_file);
488 info->hash_file = tmpfile ();
489 if (!info->hash_file)
490 die ("failed to create temporary file: %s", strerror (errno));
496 if (event == RFC822PARSE_OPEN)
498 /* Initialize for a new message. */
499 info->show_header = 1;
501 else if (event == RFC822PARSE_T2BODY)
503 rfc822parse_field_t ctx;
505 ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
506 if (ctx)
508 const char *s1, *s2;
509 s1 = rfc822parse_query_media_type (ctx, &s2);
510 if (s1)
512 printf ("h media: %*s%s %s\n",
513 info->nesting_level*2, "", s1, s2);
514 if (info->moss_state == 3)
516 char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
517 strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
518 assert (info->signing_protocol);
519 if (strcmp (buf, info->signing_protocol))
520 err ("invalid %s structure; expected `%s', found `%s'",
521 info->is_smime? "S/MIME":"PGP/MIME",
522 info->signing_protocol, buf);
523 else
525 printf ("c begin_signature\n");
526 info->moss_state++;
527 if (opt_crypto)
529 assert (!info->sig_file);
530 info->sig_file = tmpfile ();
531 if (!info->sig_file)
532 die ("error creating temp file: %s",
533 strerror (errno));
536 free (buf);
538 else if (!strcmp (s1, "multipart"))
540 if (!strcmp (s2, "signed"))
541 mime_signed_begin (info, msg, ctx);
542 else if (!strcmp (s2, "encrypted"))
543 mime_encrypted_begin (info, msg, ctx);
545 else if (!strcmp (s1, "application")
546 && (!strcmp (s2, "pkcs7-mime")
547 || !strcmp (s2, "x-pkcs7-mime")))
548 pkcs7_begin (info, msg, ctx);
550 else
551 printf ("h media: %*s none\n", info->nesting_level*2, "");
553 rfc822parse_release_field (ctx);
555 else
556 printf ("h media: %*stext plain [assumed]\n",
557 info->nesting_level*2, "");
560 info->show_header = 0;
561 info->show_data = 1;
562 info->skip_show = 1;
564 else if (event == RFC822PARSE_PREAMBLE)
565 info->show_data_as_note = 1;
566 else if (event == RFC822PARSE_LEVEL_DOWN)
568 printf ("b down\n");
569 info->nesting_level++;
571 else if (event == RFC822PARSE_LEVEL_UP)
573 printf ("b up\n");
574 if (info->nesting_level)
575 info->nesting_level--;
576 else
577 err ("invalid structure (bad nesting level)");
579 else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
581 info->show_data = 0;
582 info->show_boundary = 1;
583 if (event == RFC822PARSE_BOUNDARY)
585 info->show_header = 1;
586 info->skip_show = 1;
587 printf ("b part\n");
589 else
590 printf ("b last\n");
592 if (info->moss_state == 2 && info->nesting_level == info->hashing_level)
594 printf ("c end_hash\n");
595 info->moss_state++;
596 info->hashing = 0;
598 else if (info->moss_state == 4)
600 printf ("c end_signature\n");
601 info->verify_now = 1;
605 return 0;
609 /* Read a message from FP and process it according to the global
610 options. */
611 static void
612 parse_message (FILE *fp)
614 char line[5000];
615 size_t length;
616 rfc822parse_t msg;
617 unsigned int lineno = 0;
618 int no_cr_reported = 0;
619 struct parse_info_s info;
621 memset (&info, 0, sizeof info);
623 msg = rfc822parse_open (message_cb, &info);
624 if (!msg)
625 die ("can't open parser: %s", strerror (errno));
627 /* Fixme: We should not use fgets becuase it can't cope with
628 embedded nul characters. */
629 while (fgets (line, sizeof (line), fp))
631 lineno++;
632 if (lineno == 1 && !strncmp (line, "From ", 5))
633 continue; /* We better ignore a leading From line. */
635 length = strlen (line);
636 if (length && line[length - 1] == '\n')
637 line[--length] = 0;
638 else
639 err ("line number %u too long or last line not terminated", lineno);
640 if (length && line[length - 1] == '\r')
641 line[--length] = 0;
642 else if (verbose && !no_cr_reported)
644 err ("non canonical ended line detected (line %u)", lineno);
645 no_cr_reported = 1;
649 if (rfc822parse_insert (msg, line, length))
650 die ("parser failed: %s", strerror (errno));
652 if (info.hashing)
654 /* Delay hashing of the CR/LF because the last line ending
655 belongs to the next boundary. */
656 if (debug)
657 printf ("# hashing %s`%s'\n", info.hashing==2?"CR,LF+":"", line);
658 if (opt_crypto)
660 if (info.hashing == 2)
661 fputs ("\r\n", info.hash_file);
662 fputs (line, info.hash_file);
663 if (ferror (info.hash_file))
664 die ("error writing to temporary file: %s", strerror (errno));
667 info.hashing = 2;
670 if (info.sig_file && opt_crypto)
672 if (info.verify_now)
674 verify_signature (&info);
675 if (info.hash_file)
676 fclose (info.hash_file);
677 info.hash_file = NULL;
678 fclose (info.sig_file);
679 info.sig_file = NULL;
680 info.moss_state = 0;
681 info.is_smime = 0;
682 info.is_pkcs7 = 0;
684 else
686 fputs (line, info.sig_file);
687 fputs ("\r\n", info.sig_file);
688 if (ferror (info.sig_file))
689 die ("error writing to temporary file: %s", strerror (errno));
693 if (info.show_boundary)
695 if (!opt_no_header)
696 printf (":%s\n", line);
697 info.show_boundary = 0;
700 if (info.skip_show)
701 info.skip_show--;
702 else if (info.show_data)
704 if (info.show_data_as_note)
706 if (verbose)
707 printf ("# DATA: %s\n", line);
708 info.show_data_as_note = 0;
710 else
711 printf (" %s\n", line);
713 else if (info.show_header && !opt_no_header)
714 printf (".%s\n", line);
718 if (info.sig_file && opt_crypto && info.is_pkcs7)
720 verify_signature (&info);
721 fclose (info.sig_file);
722 info.sig_file = NULL;
723 info.is_pkcs7 = 0;
726 rfc822parse_close (msg);
730 int
731 main (int argc, char **argv)
733 int last_argc = -1;
735 if (argc)
737 argc--; argv++;
739 while (argc && last_argc != argc )
741 last_argc = argc;
742 if (!strcmp (*argv, "--"))
744 argc--; argv++;
745 break;
747 else if (!strcmp (*argv, "--help"))
749 puts (
750 "Usage: " PGM " [OPTION] [FILE]\n"
751 "Parse a mail message into an annotated format.\n\n"
752 " --crypto decrypt or verify messages\n"
753 " --no-header don't output the header lines\n"
754 " --verbose enable extra informational output\n"
755 " --debug enable additional debug output\n"
756 " --help display this help and exit\n\n"
757 "With no FILE, or when FILE is -, read standard input.\n\n"
758 "WARNING: This tool is under development.\n"
759 " The semantics may change without notice\n\n"
760 "Report bugs to <bug-gnupg@gnu.org>.");
761 exit (0);
763 else if (!strcmp (*argv, "--verbose"))
765 verbose = 1;
766 argc--; argv++;
768 else if (!strcmp (*argv, "--debug"))
770 verbose = debug = 1;
771 argc--; argv++;
773 else if (!strcmp (*argv, "--crypto"))
775 opt_crypto = 1;
776 argc--; argv++;
778 else if (!strcmp (*argv, "--no-header"))
780 opt_no_header = 1;
781 argc--; argv++;
785 if (argc > 1)
786 die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
788 signal (SIGPIPE, SIG_IGN);
790 if (argc && strcmp (*argv, "-"))
792 FILE *fp = fopen (*argv, "rb");
793 if (!fp)
794 die ("can't open `%s': %s", *argv, strerror (errno));
795 parse_message (fp);
796 fclose (fp);
798 else
799 parse_message (stdin);
801 return 0;
806 Local Variables:
807 compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
808 End: