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 <https://www.gnu.org/licenses/>.
21 /* This utility prints an RFC822, 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. */
43 #include "rfc822parse.h"
46 #define PGM "gpgparsemail"
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. */
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
60 int show_data_as_note; /* The next data line should be shown
65 int is_pkcs7; /* Old style S/MIME message. */
67 int smfm_state; /* State of PGP/MIME or S/MIME parsing. */
68 int is_smime; /* This is S/MIME and not PGP/MIME. */
70 const char *signing_protocol;
71 const char *signing_protocol_2; /* there are two ways to present
73 int hashing_level; /* The nesting level we are hashing. */
77 FILE *sig_file; /* Signature part with MIME or full
78 pkcs7 data if IS_PCKS7 is set. */
79 int verify_now; /* Flag set when all signature data is
84 /* Print diagnostic message and exit with failure. */
86 die (const char *format, ...)
91 fprintf (stderr, "%s: ", PGM);
93 va_start (arg_ptr, format);
94 vfprintf (stderr, format, arg_ptr);
102 /* Print diagnostic message. */
104 err (const char *format, ...)
109 fprintf (stderr, "%s: ", PGM);
111 va_start (arg_ptr, format);
112 vfprintf (stderr, format, arg_ptr);
120 void *p = malloc (n);
122 die ("out of core: %s", strerror (errno));
127 /* xcalloc (size_t n, size_t m) */
129 /* void *p = calloc (n, m); */
131 /* die ("out of core: %s", strerror (errno)); */
136 /* xrealloc (void *old, size_t n) */
138 /* void *p = realloc (old, n); */
140 /* die ("out of core: %s", strerror (errno)); */
145 /* xstrdup (const char *string) */
147 /* void *p = malloc (strlen (string)+1); */
149 /* die ("out of core: %s", strerror (errno)); */
150 /* strcpy (p, string); */
156 stpcpy (char *a,const char *b)
167 run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
177 die ("error creating a pipe: %s", strerror (errno));
181 die ("error forking process: %s", strerror (errno));
185 char data_fd_buf[50];
188 /* Connect our signature fd to stdin. */
191 if (dup2 (sig_fd, 0) == -1)
192 die ("dup2 stdin failed: %s", strerror (errno));
195 /* Keep our data fd and format it for gpg/gpgsm use. */
199 sprintf (data_fd_buf, "-&%d", data_fd);
201 /* Send stdout to the bit bucket. */
202 fd = open ("/dev/null", O_WRONLY);
204 die ("can't open '/dev/null': %s", strerror (errno));
207 if (dup2 (fd, 1) == -1)
208 die ("dup2 stderr failed: %s", strerror (errno));
211 /* Connect stderr to our pipe. */
214 if (dup2 (rp[1], 2) == -1)
215 die ("dup2 stderr failed: %s", strerror (errno));
218 /* Close other files. */
219 for (i=0; (fd=close_list[i]) != -1; i++)
220 if (fd > 2 && fd != data_fd)
225 execlp ("gpgsm", "gpgsm",
226 "--enable-special-filenames",
231 "-", data_fd == -1? NULL : data_fd_buf,
234 execlp ("gpg", "gpg",
235 "--enable-special-filenames",
240 "-", data_fd == -1? NULL : data_fd_buf,
243 die ("failed to exec the crypto command: %s", strerror (errno));
249 fp = fdopen (rp[0], "r");
251 die ("can't fdopen pipe for reading: %s", strerror (errno));
255 assert (sizeof status_buf > 9);
256 while ((c=getc (fp)) != EOF)
264 is_status = !memcmp (status_buf, "[GNUPG:] ", 9);
266 fputs ( "c ", stdout);
268 fputs ( "# ", stdout);
269 fwrite (status_buf, 9, 1, stdout);
275 if (verbose && pos < 9)
277 fputs ( "# ", stdout);
278 fwrite (status_buf, pos+1, 1, stdout);
287 if (verbose && pos < 9)
289 fputs ( "# ", stdout);
290 fwrite (status_buf, pos+1, 1, stdout);
296 while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
299 die ("waiting for child failed: %s", strerror (errno));
307 /* Verify the signature in the current temp files. */
309 verify_signature (struct parse_info_s *info)
315 assert (!info->hash_file);
316 assert (info->sig_file);
317 rewind (info->sig_file);
321 assert (info->hash_file);
322 assert (info->sig_file);
323 rewind (info->hash_file);
324 rewind (info->sig_file);
327 /* printf ("# Begin hashed data\n"); */
328 /* while ( (c=getc (info->hash_file)) != EOF) */
330 /* printf ("# End hashed data signature\n"); */
331 /* printf ("# Begin signature\n"); */
332 /* while ( (c=getc (info->sig_file)) != EOF) */
334 /* printf ("# End signature\n"); */
335 /* rewind (info->hash_file); */
336 /* rewind (info->sig_file); */
339 run_gnupg (info->is_smime, fileno (info->sig_file),
340 info->hash_file ? fileno (info->hash_file) : -1, close_list);
347 /* Prepare for a multipart/signed.
348 FIELD_CTX is the parsed context of the content-type header.*/
350 mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg,
351 rfc822parse_field_t field_ctx)
357 s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
360 printf ("h signed.protocol: %s\n", s);
361 if (!strcmp (s, "application/pgp-signature"))
363 if (info->smfm_state)
364 err ("note: ignoring nested PGP/MIME or S/MIME signature");
367 info->smfm_state = 1;
369 info->signing_protocol = "application/pgp-signature";
370 info->signing_protocol_2 = NULL;
373 else if (!strcmp (s, "application/pkcs7-signature")
374 || !strcmp (s, "application/x-pkcs7-signature"))
376 if (info->smfm_state)
377 err ("note: ignoring nested PGP/MIME or S/MIME signature");
380 info->smfm_state = 1;
382 info->signing_protocol = "application/pkcs7-signature";
383 info->signing_protocol_2 = "application/x-pkcs7-signature";
387 printf ("# this protocol is not supported\n");
392 /* Prepare for a multipart/encrypted.
393 FIELD_CTX is the parsed context of the content-type header.*/
395 mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
396 rfc822parse_field_t field_ctx)
403 s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
405 printf ("h encrypted.protocol: %s\n", s);
409 /* Prepare for old-style pkcs7 messages. */
411 pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg,
412 rfc822parse_field_t field_ctx)
418 s = rfc822parse_query_parameter (field_ctx, "name", 0);
420 printf ("h pkcs7.name: %s\n", s);
422 err ("note: ignoring nested pkcs7 data");
428 assert (!info->sig_file);
429 info->sig_file = tmpfile ();
431 die ("error creating temp file: %s", strerror (errno));
437 /* Print the event received by the parser for debugging as comment
440 show_event (rfc822parse_event_t event)
446 case RFC822PARSE_OPEN: s= "Open"; break;
447 case RFC822PARSE_CLOSE: s= "Close"; break;
448 case RFC822PARSE_CANCEL: s= "Cancel"; break;
449 case RFC822PARSE_T2BODY: s= "T2Body"; break;
450 case RFC822PARSE_FINISH: s= "Finish"; break;
451 case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
452 case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
453 case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
454 case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
455 case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
456 case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
457 case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
458 case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
459 default: s= "[unknown event]"; break;
461 printf ("# *** got RFC822 event %s\n", s);
464 /* This function is called by the parser to communicate events. This
465 callback comminucates with the main program using a structure
466 passed in OPAQUE. Should retrun 0 or set errno and return -1. */
468 message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
470 struct parse_info_s *info = opaque;
475 if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
477 /* We need to check here whether to start collecting signed data
478 because attachments might come without header lines and thus
479 we won't see the BEGIN_HEADER event. */
480 if (info->smfm_state == 1)
482 printf ("c begin_hash\n");
484 info->hashing_level = info->nesting_level;
489 assert (!info->hash_file);
490 info->hash_file = tmpfile ();
491 if (!info->hash_file)
492 die ("failed to create temporary file: %s", strerror (errno));
498 if (event == RFC822PARSE_OPEN)
500 /* Initialize for a new message. */
501 info->show_header = 1;
503 else if (event == RFC822PARSE_T2BODY)
505 rfc822parse_field_t ctx;
507 ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
511 s1 = rfc822parse_query_media_type (ctx, &s2);
514 printf ("h media: %*s%s %s\n",
515 info->nesting_level*2, "", s1, s2);
516 if (info->smfm_state == 3)
518 char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
519 strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
520 assert (info->signing_protocol);
521 if (strcmp (buf, info->signing_protocol) &&
522 (!info->signing_protocol_2
523 || strcmp (buf,info->signing_protocol_2)))
524 err ("invalid %s structure; expected %s%s%s, found '%s'",
525 info->is_smime? "S/MIME":"PGP/MIME",
526 info->signing_protocol,
527 info->signing_protocol_2 ? " or " : "",
528 info->signing_protocol_2 ? info->signing_protocol_2:"",
532 printf ("c begin_signature\n");
536 assert (!info->sig_file);
537 info->sig_file = tmpfile ();
539 die ("error creating temp file: %s",
545 else if (!strcmp (s1, "multipart"))
547 if (!strcmp (s2, "signed"))
548 mime_signed_begin (info, msg, ctx);
549 else if (!strcmp (s2, "encrypted"))
550 mime_encrypted_begin (info, msg, ctx);
552 else if (!strcmp (s1, "application")
553 && (!strcmp (s2, "pkcs7-mime")
554 || !strcmp (s2, "x-pkcs7-mime")))
555 pkcs7_begin (info, msg, ctx);
558 printf ("h media: %*s none\n", info->nesting_level*2, "");
560 rfc822parse_release_field (ctx);
563 printf ("h media: %*stext plain [assumed]\n",
564 info->nesting_level*2, "");
567 info->show_header = 0;
571 else if (event == RFC822PARSE_PREAMBLE)
572 info->show_data_as_note = 1;
573 else if (event == RFC822PARSE_LEVEL_DOWN)
576 info->nesting_level++;
578 else if (event == RFC822PARSE_LEVEL_UP)
581 if (info->nesting_level)
582 info->nesting_level--;
584 err ("invalid structure (bad nesting level)");
586 else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
589 info->show_boundary = 1;
590 if (event == RFC822PARSE_BOUNDARY)
592 info->show_header = 1;
599 if (info->smfm_state == 2 && info->nesting_level == info->hashing_level)
601 printf ("c end_hash\n");
605 else if (info->smfm_state == 4)
607 printf ("c end_signature\n");
608 info->verify_now = 1;
616 /* Read a message from FP and process it according to the global
619 parse_message (FILE *fp)
624 unsigned int lineno = 0;
625 int no_cr_reported = 0;
626 struct parse_info_s info;
628 memset (&info, 0, sizeof info);
630 msg = rfc822parse_open (message_cb, &info);
632 die ("can't open parser: %s", strerror (errno));
634 /* Fixme: We should not use fgets because it can't cope with
635 embedded nul characters. */
636 while (fgets (line, sizeof (line), fp))
639 if (lineno == 1 && !strncmp (line, "From ", 5))
640 continue; /* We better ignore a leading From line. */
642 length = strlen (line);
643 if (length && line[length - 1] == '\n')
646 err ("line number %u too long or last line not terminated", lineno);
647 if (length && line[length - 1] == '\r')
649 else if (verbose && !no_cr_reported)
651 err ("non canonical ended line detected (line %u)", lineno);
656 if (rfc822parse_insert (msg, line, length))
657 die ("parser failed: %s", strerror (errno));
661 /* Delay hashing of the CR/LF because the last line ending
662 belongs to the next boundary. */
664 printf ("# hashing %s'%s'\n", info.hashing==2?"CR,LF+":"", line);
667 if (info.hashing == 2)
668 fputs ("\r\n", info.hash_file);
669 fputs (line, info.hash_file);
670 if (ferror (info.hash_file))
671 die ("error writing to temporary file: %s", strerror (errno));
677 if (info.sig_file && opt_crypto)
681 verify_signature (&info);
683 fclose (info.hash_file);
684 info.hash_file = NULL;
685 fclose (info.sig_file);
686 info.sig_file = NULL;
693 fputs (line, info.sig_file);
694 fputs ("\r\n", info.sig_file);
695 if (ferror (info.sig_file))
696 die ("error writing to temporary file: %s", strerror (errno));
700 if (info.show_boundary)
703 printf (":%s\n", line);
704 info.show_boundary = 0;
709 else if (info.show_data)
711 if (info.show_data_as_note)
714 printf ("# DATA: %s\n", line);
715 info.show_data_as_note = 0;
718 printf (" %s\n", line);
720 else if (info.show_header && !opt_no_header)
721 printf (".%s\n", line);
725 if (info.sig_file && opt_crypto && info.is_pkcs7)
727 verify_signature (&info);
728 fclose (info.sig_file);
729 info.sig_file = NULL;
733 rfc822parse_close (msg);
738 main (int argc, char **argv)
746 while (argc && last_argc != argc )
749 if (!strcmp (*argv, "--"))
754 else if (!strcmp (*argv, "--help"))
757 "Usage: " PGM " [OPTION] [FILE]\n"
758 "Parse a mail message into an annotated format.\n\n"
759 " --crypto decrypt or verify messages\n"
760 " --no-header don't output the header lines\n"
761 " --verbose enable extra informational output\n"
762 " --debug enable additional debug output\n"
763 " --help display this help and exit\n\n"
764 "With no FILE, or when FILE is -, read standard input.\n\n"
765 "WARNING: This tool is under development.\n"
766 " The semantics may change without notice\n\n"
767 "Report bugs to <bug-gnupg@gnu.org>.");
770 else if (!strcmp (*argv, "--verbose"))
775 else if (!strcmp (*argv, "--debug"))
780 else if (!strcmp (*argv, "--crypto"))
785 else if (!strcmp (*argv, "--no-header"))
793 die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
795 signal (SIGPIPE, SIG_IGN);
797 if (argc && strcmp (*argv, "-"))
799 FILE *fp = fopen (*argv, "rb");
801 die ("can't open '%s': %s", *argv, strerror (errno));
806 parse_message (stdin);
814 compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"