gpg: When comparing keyids, use the keyid, not the fingerprint's suffix.
[gnupg.git] / tools / gpgparsemail.c
index 956cf18..98bbad0 100644 (file)
@@ -1,11 +1,11 @@
 /* gpgparsemail.c - Standalone crypto mail parser
- *     Copyright (C) 2003 Free Software Foundation, Inc.
+ *     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 2 of the License, or
+ * 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,
  * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 
-/* This utility prints an RFC8222, possible MIME structured, message
+/* 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 suuport is included. */
+   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>
@@ -60,13 +62,19 @@ struct parse_info_s {
   int show_boundary;
   int nesting_level;
 
-  int gpgsm_mime;              /* gpgsm shall be used from S/MIME. */
+  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. */
+
   char *signing_protocol;
   int hashing_level;           /* The nesting level we are hashing. */
-  int hashing;                 
+  int hashing;
   FILE *hash_file;
-  FILE *sig_file;
-  int  verify_now;             /* Falg set when all signature data is
+
+  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. */
 };
 
@@ -141,16 +149,17 @@ xstrdup (const char *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)
@@ -160,7 +169,6 @@ run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
   int i, c, is_status;
   unsigned int pos;
   char status_buf[10];
-  const char *cmd = smime? "gpgsm":"gpg";
   FILE *fp;
 
   if (pipe (rp) == -1)
@@ -181,20 +189,23 @@ run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
           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);
+      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));
+        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)
        {
@@ -208,19 +219,29 @@ run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
           close (fd);
       errno = 0;
 
-      execlp (cmd, cmd,
-              "--enable-special-filenames",
-              "--status-fd", "2",
-              "--assume-base64",
-              "--verify",
-              "--",
-              "-", data_fd_buf,
-              NULL);
+      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. */ 
+  /* Parent. */
   close (rp[1]);
 
   fp = fdopen (rp[0], "r");
@@ -234,7 +255,7 @@ run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
     {
       if (pos < 9)
         status_buf[pos] = c;
-      else 
+      else
         {
           if (pos == 9)
             {
@@ -287,10 +308,19 @@ 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);
+  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) */
@@ -304,32 +334,49 @@ verify_signature (struct parse_info_s *info)
 /*   rewind (info->sig_file); */
 
   close_list[0] = -1;
-  run_gnupg (1, fileno (info->sig_file), fileno (info->hash_file), close_list);
+  run_gnupg (info->is_smime, fileno (info->sig_file),
+             info->hash_file ? fileno (info->hash_file) : -1, close_list);
 }
 
 
 
 
 
-/* Prepare for a multipart/signed. 
+/* 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/pkcs7-signature")
-          || !strcmp (s, "application/x-pkcs7-signature"))
+      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;
+              free (info->signing_protocol);
+              info->signing_protocol = xstrdup (s);
+            }
+        }
+      else if (!strcmp (s, "application/pkcs7-signature")
+               || !strcmp (s, "application/x-pkcs7-signature"))
         {
-          if (info->gpgsm_mime)
-            err ("note: ignoring nested pkcs7-signature");
+          if (info->smfm_state)
+            err ("note: ignoring nested PGP/MIME or S/MIME signature");
           else
             {
-              info->gpgsm_mime = 1;
+              info->smfm_state = 1;
+              info->is_smime = 1;
               free (info->signing_protocol);
               info->signing_protocol = xstrdup (s);
             }
@@ -340,19 +387,50 @@ mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg,
 }
 
 
-/* Prepare for a multipart/encrypted. 
+/* 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. */
@@ -391,6 +469,30 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
 
   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. */
@@ -407,20 +509,21 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
           s1 = rfc822parse_query_media_type (ctx, &s2);
           if (s1)
             {
-              printf ("h media: %*s%s %s\n", 
+              printf ("h media: %*s%s %s\n",
                       info->nesting_level*2, "", s1, s2);
-              if (info->gpgsm_mime == 3)
+              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))
-                    err ("invalid S/MIME structure; expected `%s', found `%s'",
+                    err ("invalid %s structure; expected '%s', found '%s'",
+                         info->is_smime? "S/MIME":"PGP/MIME",
                          info->signing_protocol, buf);
                   else
                     {
                       printf ("c begin_signature\n");
-                      info->gpgsm_mime++;
+                      info->smfm_state++;
                       if (opt_crypto)
                         {
                           assert (!info->sig_file);
@@ -439,6 +542,10 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
                   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, "");
@@ -448,6 +555,8 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
       else
         printf ("h media: %*stext plain [assumed]\n",
                 info->nesting_level*2, "");
+
+
       info->show_header = 0;
       info->show_data = 1;
       info->skip_show = 1;
@@ -464,7 +573,7 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
       printf ("b up\n");
       if (info->nesting_level)
         info->nesting_level--;
-      else 
+      else
         err ("invalid structure (bad nesting level)");
     }
   else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
@@ -477,39 +586,21 @@ message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
           info->skip_show = 1;
           printf ("b part\n");
         }
-      else 
+      else
         printf ("b last\n");
 
-      if (info->gpgsm_mime == 2 && info->nesting_level == info->hashing_level)
+      if (info->smfm_state == 2 && info->nesting_level == info->hashing_level)
         {
           printf ("c end_hash\n");
-          info->gpgsm_mime++;
+          info->smfm_state++;
           info->hashing = 0;
         }
-      else if (info->gpgsm_mime == 4)
+      else if (info->smfm_state == 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;
 }
@@ -533,7 +624,7 @@ parse_message (FILE *fp)
   if (!msg)
     die ("can't open parser: %s", strerror (errno));
 
-  /* Fixme: We should not use fgets becuase it can't cope with
+  /* Fixme: We should not use fgets because it can't cope with
      embedded nul characters. */
   while (fgets (line, sizeof (line), fp))
     {
@@ -557,13 +648,13 @@ parse_message (FILE *fp)
 
       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);
+            printf ("# hashing %s'%s'\n", info.hashing==2?"CR,LF+":"", line);
           if (opt_crypto)
             {
               if (info.hashing == 2)
@@ -581,11 +672,14 @@ parse_message (FILE *fp)
           if (info.verify_now)
             {
               verify_signature (&info);
-              fclose (info.hash_file);
+              if (info.hash_file)
+                fclose (info.hash_file);
               info.hash_file = NULL;
               fclose (info.sig_file);
               info.sig_file = NULL;
-              info.gpgsm_mime = 0;
+              info.smfm_state = 0;
+              info.is_smime = 0;
+              info.is_pkcs7 = 0;
             }
           else
             {
@@ -595,7 +689,7 @@ parse_message (FILE *fp)
                 die ("error writing to temporary file: %s", strerror (errno));
             }
         }
-      
+
       if (info.show_boundary)
         {
           if (!opt_no_header)
@@ -621,15 +715,23 @@ parse_message (FILE *fp)
 
     }
 
+  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 
+int
 main (int argc, char **argv)
 {
   int last_argc = -1;
+
   if (argc)
     {
       argc--; argv++;
@@ -653,6 +755,8 @@ main (int argc, char **argv)
                 "  --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);
         }
@@ -676,8 +780,8 @@ main (int argc, char **argv)
           opt_no_header = 1;
           argc--; argv++;
         }
-    }          
+    }
+
   if (argc > 1)
     die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
 
@@ -687,7 +791,7 @@ main (int argc, char **argv)
     {
       FILE *fp = fopen (*argv, "rb");
       if (!fp)
-        die ("can't open `%s': %s", *argv, strerror (errno));
+        die ("can't open '%s': %s", *argv, strerror (errno));
       parse_message (fp);
       fclose (fp);
     }
@@ -700,6 +804,6 @@ main (int argc, char **argv)
 
 /*
 Local Variables:
-compile-command: "gcc -Wall -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
+compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
 End:
 */