diff options
Diffstat (limited to 'tools/gpgparsemail.c')
-rw-r--r-- | tools/gpgparsemail.c | 705 |
1 files changed, 705 insertions, 0 deletions
diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c new file mode 100644 index 000000000..956cf18d9 --- /dev/null +++ b/tools/gpgparsemail.c @@ -0,0 +1,705 @@ +/* gpgparsemail.c - Standalone crypto mail parser + * Copyright (C) 2003 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + + +/* This utility prints an RFC8222, 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 suuport is included. */ + + +#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 gpgsm_mime; /* gpgsm shall be used from S/MIME. */ + char *signing_protocol; + int hashing_level; /* The nesting level we are hashing. */ + int hashing; + FILE *hash_file; + FILE *sig_file; + int verify_now; /* Falg 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; +} + +static char * +stpcpy (char *a,const char *b) +{ + while (*b) + *a++ = *b++; + *a = 0; + + return (char*)a; +} + + +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]; + const char *cmd = smime? "gpgsm":"gpg"; + 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. */ + 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; + + execlp (cmd, cmd, + "--enable-special-filenames", + "--status-fd", "2", + "--assume-base64", + "--verify", + "--", + "-", 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]; + + 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 (1, fileno (info->sig_file), fileno (info->hash_file), 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; + s = rfc822parse_query_parameter (field_ctx, "protocol", 1); + if (s) + { + printf ("h signed.protocol: %s\n", s); + if (!strcmp (s, "application/pkcs7-signature") + || !strcmp (s, "application/x-pkcs7-signature")) + { + if (info->gpgsm_mime) + err ("note: ignoring nested pkcs7-signature"); + else + { + info->gpgsm_mime = 1; + free (info->signing_protocol); + info->signing_protocol = xstrdup (s); + } + } + 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; + s = rfc822parse_query_parameter (field_ctx, "protocol", 0); + if (s) + printf ("h encrypted.protocol: %s\n", s); +} + + + +/* 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 retrun 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_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->gpgsm_mime == 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)) + err ("invalid S/MIME structure; expected `%s', found `%s'", + info->signing_protocol, buf); + else + { + printf ("c begin_signature\n"); + info->gpgsm_mime++; + 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 + 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->gpgsm_mime == 2 && info->nesting_level == info->hashing_level) + { + printf ("c end_hash\n"); + info->gpgsm_mime++; + info->hashing = 0; + } + else if (info->gpgsm_mime == 4) + { + printf ("c end_signature\n"); + info->verify_now = 1; + } + } + else if (event == RFC822PARSE_BEGIN_HEADER) + { + if (info->gpgsm_mime == 1) + { + printf ("c begin_hash\n"); + info->hashing = 1; + info->hashing_level = info->nesting_level; + info->gpgsm_mime++; + + if (opt_crypto) + { + assert (!info->hash_file); + info->hash_file = tmpfile (); + if (!info->hash_file) + die ("failed to create temporary file: %s", strerror (errno)); + } + } + } + + 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 becuase 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); + fclose (info.hash_file); + info.hash_file = NULL; + fclose (info.sig_file); + info.sig_file = NULL; + info.gpgsm_mime = 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); + + } + + 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" + "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 -g -o gpgparsemail rfc822parse.c gpgparsemail.c" +End: +*/ |