summaryrefslogtreecommitdiffstats
path: root/tools/gpgparsemail.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gpgparsemail.c')
-rw-r--r--tools/gpgparsemail.c816
1 files changed, 816 insertions, 0 deletions
diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c
new file mode 100644
index 0000000..b122097
--- /dev/null
+++ b/tools/gpgparsemail.c
@@ -0,0 +1,816 @@
+/* gpgparsemail.c - Standalone crypto mail parser
+ * Copyright (C) 2004, 2007 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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, see <https://www.gnu.org/licenses/>.
+ */
+
+
+/* This utility prints an RFC822, possible MIME structured, message
+ in an annotated format with the first column having an indicator
+ for the content of the line. Several options are available to
+ scrutinize the message. S/MIME and OpenPGP support is included. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <time.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include "rfc822parse.h"
+
+
+#define PGM "gpgparsemail"
+
+/* Option flags. */
+static int verbose;
+static int debug;
+static int opt_crypto; /* Decrypt or verify messages. */
+static int opt_no_header; /* Don't output the header lines. */
+
+/* Structure used to communicate with the parser callback. */
+struct parse_info_s {
+ int show_header; /* Show the header lines. */
+ int show_data; /* Show the data lines. */
+ unsigned int skip_show; /* Temporary disable above for these
+ number of lines. */
+ int show_data_as_note; /* The next data line should be shown
+ as a note. */
+ int show_boundary;
+ int nesting_level;
+
+ int is_pkcs7; /* Old style S/MIME message. */
+
+ int smfm_state; /* State of PGP/MIME or S/MIME parsing. */
+ int is_smime; /* This is S/MIME and not PGP/MIME. */
+
+ const char *signing_protocol;
+ const char *signing_protocol_2; /* there are two ways to present
+ PKCS7 */
+ int hashing_level; /* The nesting level we are hashing. */
+ int hashing;
+ FILE *hash_file;
+
+ FILE *sig_file; /* Signature part with MIME or full
+ pkcs7 data if IS_PCKS7 is set. */
+ int verify_now; /* Flag set when all signature data is
+ available. */
+};
+
+
+/* Print diagnostic message and exit with failure. */
+static void
+die (const char *format, ...)
+{
+ va_list arg_ptr;
+
+ fflush (stdout);
+ fprintf (stderr, "%s: ", PGM);
+
+ va_start (arg_ptr, format);
+ vfprintf (stderr, format, arg_ptr);
+ va_end (arg_ptr);
+ putc ('\n', stderr);
+
+ exit (1);
+}
+
+
+/* Print diagnostic message. */
+static void
+err (const char *format, ...)
+{
+ va_list arg_ptr;
+
+ fflush (stdout);
+ fprintf (stderr, "%s: ", PGM);
+
+ va_start (arg_ptr, format);
+ vfprintf (stderr, format, arg_ptr);
+ va_end (arg_ptr);
+ putc ('\n', stderr);
+}
+
+static void *
+xmalloc (size_t n)
+{
+ void *p = malloc (n);
+ if (!p)
+ die ("out of core: %s", strerror (errno));
+ return p;
+}
+
+/* static void * */
+/* xcalloc (size_t n, size_t m) */
+/* { */
+/* void *p = calloc (n, m); */
+/* if (!p) */
+/* die ("out of core: %s", strerror (errno)); */
+/* return p; */
+/* } */
+
+/* static void * */
+/* xrealloc (void *old, size_t n) */
+/* { */
+/* void *p = realloc (old, n); */
+/* if (!p) */
+/* die ("out of core: %s", strerror (errno)); */
+/* return p; */
+/* } */
+
+/* static char * */
+/* xstrdup (const char *string) */
+/* { */
+/* void *p = malloc (strlen (string)+1); */
+/* if (!p) */
+/* die ("out of core: %s", strerror (errno)); */
+/* strcpy (p, string); */
+/* return p; */
+/* } */
+
+#ifndef HAVE_STPCPY
+static char *
+stpcpy (char *a,const char *b)
+{
+ while (*b)
+ *a++ = *b++;
+ *a = 0;
+
+ return (char*)a;
+}
+#endif
+
+static int
+run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
+{
+ int rp[2];
+ pid_t pid;
+ int i, c, is_status;
+ unsigned int pos;
+ char status_buf[10];
+ FILE *fp;
+
+ if (pipe (rp) == -1)
+ die ("error creating a pipe: %s", strerror (errno));
+
+ pid = fork ();
+ if (pid == -1)
+ die ("error forking process: %s", strerror (errno));
+
+ if (!pid)
+ { /* Child. */
+ char data_fd_buf[50];
+ int fd;
+
+ /* Connect our signature fd to stdin. */
+ if (sig_fd != 0)
+ {
+ if (dup2 (sig_fd, 0) == -1)
+ die ("dup2 stdin failed: %s", strerror (errno));
+ }
+
+ /* Keep our data fd and format it for gpg/gpgsm use. */
+ if (data_fd == -1)
+ *data_fd_buf = 0;
+ else
+ sprintf (data_fd_buf, "-&%d", data_fd);
+
+ /* Send stdout to the bit bucket. */
+ fd = open ("/dev/null", O_WRONLY);
+ if (fd == -1)
+ die ("can't open '/dev/null': %s", strerror (errno));
+ if (fd != 1)
+ {
+ if (dup2 (fd, 1) == -1)
+ die ("dup2 stderr failed: %s", strerror (errno));
+ }
+
+ /* Connect stderr to our pipe. */
+ if (rp[1] != 2)
+ {
+ if (dup2 (rp[1], 2) == -1)
+ die ("dup2 stderr failed: %s", strerror (errno));
+ }
+
+ /* Close other files. */
+ for (i=0; (fd=close_list[i]) != -1; i++)
+ if (fd > 2 && fd != data_fd)
+ close (fd);
+ errno = 0;
+
+ if (smime)
+ execlp ("gpgsm", "gpgsm",
+ "--enable-special-filenames",
+ "--status-fd", "2",
+ "--assume-base64",
+ "--verify",
+ "--",
+ "-", data_fd == -1? NULL : data_fd_buf,
+ NULL);
+ else
+ execlp ("gpg", "gpg",
+ "--enable-special-filenames",
+ "--status-fd", "2",
+ "--verify",
+ "--debug=512",
+ "--",
+ "-", data_fd == -1? NULL : data_fd_buf,
+ NULL);
+
+ die ("failed to exec the crypto command: %s", strerror (errno));
+ }
+
+ /* Parent. */
+ close (rp[1]);
+
+ fp = fdopen (rp[0], "r");
+ if (!fp)
+ die ("can't fdopen pipe for reading: %s", strerror (errno));
+
+ pos = 0;
+ is_status = 0;
+ assert (sizeof status_buf > 9);
+ while ((c=getc (fp)) != EOF)
+ {
+ if (pos < 9)
+ status_buf[pos] = c;
+ else
+ {
+ if (pos == 9)
+ {
+ is_status = !memcmp (status_buf, "[GNUPG:] ", 9);
+ if (is_status)
+ fputs ( "c ", stdout);
+ else if (verbose)
+ fputs ( "# ", stdout);
+ fwrite (status_buf, 9, 1, stdout);
+ }
+ putchar (c);
+ }
+ if (c == '\n')
+ {
+ if (verbose && pos < 9)
+ {
+ fputs ( "# ", stdout);
+ fwrite (status_buf, pos+1, 1, stdout);
+ }
+ pos = 0;
+ }
+ else
+ pos++;
+ }
+ if (pos)
+ {
+ if (verbose && pos < 9)
+ {
+ fputs ( "# ", stdout);
+ fwrite (status_buf, pos+1, 1, stdout);
+ }
+ putchar ('\n');
+ }
+ fclose (fp);
+
+ while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
+ ;
+ if (i == -1)
+ die ("waiting for child failed: %s", strerror (errno));
+
+ return 0;
+}
+
+
+
+
+/* Verify the signature in the current temp files. */
+static void
+verify_signature (struct parse_info_s *info)
+{
+ int close_list[10];
+
+ if (info->is_pkcs7)
+ {
+ assert (!info->hash_file);
+ assert (info->sig_file);
+ rewind (info->sig_file);
+ }
+ else
+ {
+ assert (info->hash_file);
+ assert (info->sig_file);
+ rewind (info->hash_file);
+ rewind (info->sig_file);
+ }
+
+/* printf ("# Begin hashed data\n"); */
+/* while ( (c=getc (info->hash_file)) != EOF) */
+/* putchar (c); */
+/* printf ("# End hashed data signature\n"); */
+/* printf ("# Begin signature\n"); */
+/* while ( (c=getc (info->sig_file)) != EOF) */
+/* putchar (c); */
+/* printf ("# End signature\n"); */
+/* rewind (info->hash_file); */
+/* rewind (info->sig_file); */
+
+ close_list[0] = -1;
+ run_gnupg (info->is_smime, fileno (info->sig_file),
+ info->hash_file ? fileno (info->hash_file) : -1, close_list);
+}
+
+
+
+
+
+/* Prepare for a multipart/signed.
+ FIELD_CTX is the parsed context of the content-type header.*/
+static void
+mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg,
+ rfc822parse_field_t field_ctx)
+{
+ const char *s;
+
+ (void)msg;
+
+ s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
+ if (s)
+ {
+ printf ("h signed.protocol: %s\n", s);
+ if (!strcmp (s, "application/pgp-signature"))
+ {
+ if (info->smfm_state)
+ err ("note: ignoring nested PGP/MIME or S/MIME signature");
+ else
+ {
+ info->smfm_state = 1;
+ info->is_smime = 0;
+ info->signing_protocol = "application/pgp-signature";
+ info->signing_protocol_2 = NULL;
+ }
+ }
+ else if (!strcmp (s, "application/pkcs7-signature")
+ || !strcmp (s, "application/x-pkcs7-signature"))
+ {
+ if (info->smfm_state)
+ err ("note: ignoring nested PGP/MIME or S/MIME signature");
+ else
+ {
+ info->smfm_state = 1;
+ info->is_smime = 1;
+ info->signing_protocol = "application/pkcs7-signature";
+ info->signing_protocol_2 = "application/x-pkcs7-signature";
+ }
+ }
+ else if (verbose)
+ printf ("# this protocol is not supported\n");
+ }
+}
+
+
+/* Prepare for a multipart/encrypted.
+ FIELD_CTX is the parsed context of the content-type header.*/
+static void
+mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
+ rfc822parse_field_t field_ctx)
+{
+ const char *s;
+
+ (void)info;
+ (void)msg;
+
+ s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
+ if (s)
+ printf ("h encrypted.protocol: %s\n", s);
+}
+
+
+/* Prepare for old-style pkcs7 messages. */
+static void
+pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg,
+ rfc822parse_field_t field_ctx)
+{
+ const char *s;
+
+ (void)msg;
+
+ s = rfc822parse_query_parameter (field_ctx, "name", 0);
+ if (s)
+ printf ("h pkcs7.name: %s\n", s);
+ if (info->is_pkcs7)
+ err ("note: ignoring nested pkcs7 data");
+ else
+ {
+ info->is_pkcs7 = 1;
+ if (opt_crypto)
+ {
+ assert (!info->sig_file);
+ info->sig_file = tmpfile ();
+ if (!info->sig_file)
+ die ("error creating temp file: %s", strerror (errno));
+ }
+ }
+}
+
+
+/* Print the event received by the parser for debugging as comment
+ line. */
+static void
+show_event (rfc822parse_event_t event)
+{
+ const char *s;
+
+ switch (event)
+ {
+ case RFC822PARSE_OPEN: s= "Open"; break;
+ case RFC822PARSE_CLOSE: s= "Close"; break;
+ case RFC822PARSE_CANCEL: s= "Cancel"; break;
+ case RFC822PARSE_T2BODY: s= "T2Body"; break;
+ case RFC822PARSE_FINISH: s= "Finish"; break;
+ case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
+ case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
+ case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
+ case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
+ case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+ case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
+ case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
+ case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
+ default: s= "[unknown event]"; break;
+ }
+ printf ("# *** got RFC822 event %s\n", s);
+}
+
+/* This function is called by the parser to communicate events. This
+ callback comminucates with the main program using a structure
+ passed in OPAQUE. Should return 0 or set errno and return -1. */
+static int
+message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
+{
+ struct parse_info_s *info = opaque;
+
+ if (debug)
+ show_event (event);
+
+ if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
+ {
+ /* We need to check here whether to start collecting signed data
+ because attachments might come without header lines and thus
+ we won't see the BEGIN_HEADER event. */
+ if (info->smfm_state == 1)
+ {
+ printf ("c begin_hash\n");
+ info->hashing = 1;
+ info->hashing_level = info->nesting_level;
+ info->smfm_state++;
+
+ if (opt_crypto)
+ {
+ assert (!info->hash_file);
+ info->hash_file = tmpfile ();
+ if (!info->hash_file)
+ die ("failed to create temporary file: %s", strerror (errno));
+ }
+ }
+ }
+
+
+ if (event == RFC822PARSE_OPEN)
+ {
+ /* Initialize for a new message. */
+ info->show_header = 1;
+ }
+ else if (event == RFC822PARSE_T2BODY)
+ {
+ rfc822parse_field_t ctx;
+
+ ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+ if (ctx)
+ {
+ const char *s1, *s2;
+ s1 = rfc822parse_query_media_type (ctx, &s2);
+ if (s1)
+ {
+ printf ("h media: %*s%s %s\n",
+ info->nesting_level*2, "", s1, s2);
+ if (info->smfm_state == 3)
+ {
+ char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
+ strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
+ assert (info->signing_protocol);
+ if (strcmp (buf, info->signing_protocol) &&
+ (!info->signing_protocol_2
+ || strcmp (buf,info->signing_protocol_2)))
+ err ("invalid %s structure; expected %s%s%s, found '%s'",
+ info->is_smime? "S/MIME":"PGP/MIME",
+ info->signing_protocol,
+ info->signing_protocol_2 ? " or " : "",
+ info->signing_protocol_2 ? info->signing_protocol_2:"",
+ buf);
+ else
+ {
+ printf ("c begin_signature\n");
+ info->smfm_state++;
+ if (opt_crypto)
+ {
+ assert (!info->sig_file);
+ info->sig_file = tmpfile ();
+ if (!info->sig_file)
+ die ("error creating temp file: %s",
+ strerror (errno));
+ }
+ }
+ free (buf);
+ }
+ else if (!strcmp (s1, "multipart"))
+ {
+ if (!strcmp (s2, "signed"))
+ mime_signed_begin (info, msg, ctx);
+ else if (!strcmp (s2, "encrypted"))
+ mime_encrypted_begin (info, msg, ctx);
+ }
+ else if (!strcmp (s1, "application")
+ && (!strcmp (s2, "pkcs7-mime")
+ || !strcmp (s2, "x-pkcs7-mime")))
+ pkcs7_begin (info, msg, ctx);
+ }
+ else
+ printf ("h media: %*s none\n", info->nesting_level*2, "");
+
+ rfc822parse_release_field (ctx);
+ }
+ else
+ printf ("h media: %*stext plain [assumed]\n",
+ info->nesting_level*2, "");
+
+
+ info->show_header = 0;
+ info->show_data = 1;
+ info->skip_show = 1;
+ }
+ else if (event == RFC822PARSE_PREAMBLE)
+ info->show_data_as_note = 1;
+ else if (event == RFC822PARSE_LEVEL_DOWN)
+ {
+ printf ("b down\n");
+ info->nesting_level++;
+ }
+ else if (event == RFC822PARSE_LEVEL_UP)
+ {
+ printf ("b up\n");
+ if (info->nesting_level)
+ info->nesting_level--;
+ else
+ err ("invalid structure (bad nesting level)");
+ }
+ else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
+ {
+ info->show_data = 0;
+ info->show_boundary = 1;
+ if (event == RFC822PARSE_BOUNDARY)
+ {
+ info->show_header = 1;
+ info->skip_show = 1;
+ printf ("b part\n");
+ }
+ else
+ printf ("b last\n");
+
+ if (info->smfm_state == 2 && info->nesting_level == info->hashing_level)
+ {
+ printf ("c end_hash\n");
+ info->smfm_state++;
+ info->hashing = 0;
+ }
+ else if (info->smfm_state == 4)
+ {
+ printf ("c end_signature\n");
+ info->verify_now = 1;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Read a message from FP and process it according to the global
+ options. */
+static void
+parse_message (FILE *fp)
+{
+ char line[5000];
+ size_t length;
+ rfc822parse_t msg;
+ unsigned int lineno = 0;
+ int no_cr_reported = 0;
+ struct parse_info_s info;
+
+ memset (&info, 0, sizeof info);
+
+ msg = rfc822parse_open (message_cb, &info);
+ if (!msg)
+ die ("can't open parser: %s", strerror (errno));
+
+ /* Fixme: We should not use fgets because it can't cope with
+ embedded nul characters. */
+ while (fgets (line, sizeof (line), fp))
+ {
+ lineno++;
+ if (lineno == 1 && !strncmp (line, "From ", 5))
+ continue; /* We better ignore a leading From line. */
+
+ length = strlen (line);
+ if (length && line[length - 1] == '\n')
+ line[--length] = 0;
+ else
+ err ("line number %u too long or last line not terminated", lineno);
+ if (length && line[length - 1] == '\r')
+ line[--length] = 0;
+ else if (verbose && !no_cr_reported)
+ {
+ err ("non canonical ended line detected (line %u)", lineno);
+ no_cr_reported = 1;
+ }
+
+
+ if (rfc822parse_insert (msg, line, length))
+ die ("parser failed: %s", strerror (errno));
+
+ if (info.hashing)
+ {
+ /* Delay hashing of the CR/LF because the last line ending
+ belongs to the next boundary. */
+ if (debug)
+ printf ("# hashing %s'%s'\n", info.hashing==2?"CR,LF+":"", line);
+ if (opt_crypto)
+ {
+ if (info.hashing == 2)
+ fputs ("\r\n", info.hash_file);
+ fputs (line, info.hash_file);
+ if (ferror (info.hash_file))
+ die ("error writing to temporary file: %s", strerror (errno));
+ }
+
+ info.hashing = 2;
+ }
+
+ if (info.sig_file && opt_crypto)
+ {
+ if (info.verify_now)
+ {
+ verify_signature (&info);
+ if (info.hash_file)
+ fclose (info.hash_file);
+ info.hash_file = NULL;
+ fclose (info.sig_file);
+ info.sig_file = NULL;
+ info.smfm_state = 0;
+ info.is_smime = 0;
+ info.is_pkcs7 = 0;
+ }
+ else
+ {
+ fputs (line, info.sig_file);
+ fputs ("\r\n", info.sig_file);
+ if (ferror (info.sig_file))
+ die ("error writing to temporary file: %s", strerror (errno));
+ }
+ }
+
+ if (info.show_boundary)
+ {
+ if (!opt_no_header)
+ printf (":%s\n", line);
+ info.show_boundary = 0;
+ }
+
+ if (info.skip_show)
+ info.skip_show--;
+ else if (info.show_data)
+ {
+ if (info.show_data_as_note)
+ {
+ if (verbose)
+ printf ("# DATA: %s\n", line);
+ info.show_data_as_note = 0;
+ }
+ else
+ printf (" %s\n", line);
+ }
+ else if (info.show_header && !opt_no_header)
+ printf (".%s\n", line);
+
+ }
+
+ if (info.sig_file && opt_crypto && info.is_pkcs7)
+ {
+ verify_signature (&info);
+ fclose (info.sig_file);
+ info.sig_file = NULL;
+ info.is_pkcs7 = 0;
+ }
+
+ rfc822parse_close (msg);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int last_argc = -1;
+
+ if (argc)
+ {
+ argc--; argv++;
+ }
+ while (argc && last_argc != argc )
+ {
+ last_argc = argc;
+ if (!strcmp (*argv, "--"))
+ {
+ argc--; argv++;
+ break;
+ }
+ else if (!strcmp (*argv, "--help"))
+ {
+ puts (
+ "Usage: " PGM " [OPTION] [FILE]\n"
+ "Parse a mail message into an annotated format.\n\n"
+ " --crypto decrypt or verify messages\n"
+ " --no-header don't output the header lines\n"
+ " --verbose enable extra informational output\n"
+ " --debug enable additional debug output\n"
+ " --help display this help and exit\n\n"
+ "With no FILE, or when FILE is -, read standard input.\n\n"
+ "WARNING: This tool is under development.\n"
+ " The semantics may change without notice\n\n"
+ "Report bugs to <bug-gnupg@gnu.org>.");
+ exit (0);
+ }
+ else if (!strcmp (*argv, "--verbose"))
+ {
+ verbose = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--debug"))
+ {
+ verbose = debug = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--crypto"))
+ {
+ opt_crypto = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--no-header"))
+ {
+ opt_no_header = 1;
+ argc--; argv++;
+ }
+ }
+
+ if (argc > 1)
+ die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
+
+ signal (SIGPIPE, SIG_IGN);
+
+ if (argc && strcmp (*argv, "-"))
+ {
+ FILE *fp = fopen (*argv, "rb");
+ if (!fp)
+ die ("can't open '%s': %s", *argv, strerror (errno));
+ parse_message (fp);
+ fclose (fp);
+ }
+ else
+ parse_message (stdin);
+
+ return 0;
+}
+
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
+End:
+*/