Preparing 2.0.7
[gnupg.git] / tools / gpgparsemail.c
1 /* gpgparsemail.c - Standalone crypto mail parser
2  *      Copyright (C) 2004, 2007 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20
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. */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
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>
42
43 #include "rfc822parse.h"
44
45
46 #define PGM "gpgparsemail"
47
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. */
53
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;
64
65   int is_pkcs7;                /* Old style S/MIME message. */
66
67   int moss_state;              /* State of PGP/MIME or S/MIME parsing.  */
68   int is_smime;                /* This is S/MIME and not PGP/MIME. */
69
70   char *signing_protocol;
71   int hashing_level;           /* The nesting level we are hashing. */
72   int hashing;                 
73   FILE *hash_file;
74
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. */
79 };
80
81
82 /* Print diagnostic message and exit with failure. */
83 static void
84 die (const char *format, ...)
85 {
86   va_list arg_ptr;
87
88   fflush (stdout);
89   fprintf (stderr, "%s: ", PGM);
90
91   va_start (arg_ptr, format);
92   vfprintf (stderr, format, arg_ptr);
93   va_end (arg_ptr);
94   putc ('\n', stderr);
95
96   exit (1);
97 }
98
99
100 /* Print diagnostic message. */
101 static void
102 err (const char *format, ...)
103 {
104   va_list arg_ptr;
105
106   fflush (stdout);
107   fprintf (stderr, "%s: ", PGM);
108
109   va_start (arg_ptr, format);
110   vfprintf (stderr, format, arg_ptr);
111   va_end (arg_ptr);
112   putc ('\n', stderr);
113 }
114
115 static void *
116 xmalloc (size_t n)
117 {
118   void *p = malloc (n);
119   if (!p)
120     die ("out of core: %s", strerror (errno));
121   return p;
122 }
123
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 /* } */
132
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 /* } */
141
142 static char *
143 xstrdup (const char *string)
144 {
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;
150 }
151
152 #ifndef HAVE_STPCPY
153 static char *
154 stpcpy (char *a,const char *b)
155 {
156   while (*b)
157     *a++ = *b++;
158   *a = 0;
159   
160   return (char*)a;
161 }
162 #endif
163
164 static int
165 run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
166 {
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;
173
174   if (pipe (rp) == -1)
175     die ("error creating a pipe: %s", strerror (errno));
176
177   pid = fork ();
178   if (pid == -1)
179     die ("error forking process: %s", strerror (errno));
180
181   if (!pid)
182     { /* Child. */
183       char data_fd_buf[50];
184       int fd;
185
186       /* Connect our signature fd to stdin. */
187       if (sig_fd != 0)
188         {
189           if (dup2 (sig_fd, 0) == -1)
190             die ("dup2 stdin failed: %s", strerror (errno));
191         }
192       
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);
198
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)
204         {
205           if (dup2 (fd, 1) == -1)
206             die ("dup2 stderr failed: %s", strerror (errno));
207         }
208       
209       /* Connect stderr to our pipe. */
210       if (rp[1] != 2)
211         {
212           if (dup2 (rp[1], 2) == -1)
213             die ("dup2 stderr failed: %s", strerror (errno));
214         }
215
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;
221
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);
240       
241       die ("failed to exec the crypto command: %s", strerror (errno));
242     }
243
244   /* Parent. */ 
245   close (rp[1]);
246
247   fp = fdopen (rp[0], "r");
248   if (!fp)
249     die ("can't fdopen pipe for reading: %s", strerror (errno));
250
251   pos = 0;
252   is_status = 0;
253   assert (sizeof status_buf > 9);
254   while ((c=getc (fp)) != EOF)
255     {
256       if (pos < 9)
257         status_buf[pos] = c;
258       else 
259         {
260           if (pos == 9)
261             {
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);
268             }
269           putchar (c);
270         }
271       if (c == '\n')
272         {
273           if (verbose && pos < 9)
274             {
275               fputs ( "# ", stdout);
276               fwrite (status_buf, pos+1, 1, stdout);
277             }
278           pos = 0;
279         }
280       else
281         pos++;
282     }
283   if (pos)
284     {
285       if (verbose && pos < 9)
286         {
287           fputs ( "# ", stdout);
288           fwrite (status_buf, pos+1, 1, stdout);
289         }
290       putchar ('\n');
291     }
292   fclose (fp);
293
294   while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
295     ;
296   if (i == -1)
297     die ("waiting for child failed: %s", strerror (errno));
298
299   return 0;
300 }
301
302
303
304
305 /* Verify the signature in the current temp files. */
306 static void
307 verify_signature (struct parse_info_s *info)
308 {
309   int close_list[10];
310
311   if (info->is_pkcs7)
312     {
313       assert (!info->hash_file);
314       assert (info->sig_file);
315       rewind (info->sig_file);
316     }
317   else
318     {
319       assert (info->hash_file);
320       assert (info->sig_file);
321       rewind (info->hash_file);
322       rewind (info->sig_file);
323     }
324
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); */
335
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);
339 }
340
341
342
343
344
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)
350 {
351   const char *s;
352   s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
353   if (s)
354     {
355       printf ("h signed.protocol: %s\n", s);
356       if (!strcmp (s, "application/pgp-signature"))
357         {
358           if (info->moss_state)
359             err ("note: ignoring nested PGP/MIME or S/MIME signature");
360           else
361             {
362               info->moss_state = 1;
363               info->is_smime = 0;
364               free (info->signing_protocol);
365               info->signing_protocol = xstrdup (s);
366             }
367         }
368       else if (!strcmp (s, "application/pkcs7-signature")
369                || !strcmp (s, "application/x-pkcs7-signature"))
370         {
371           if (info->moss_state)
372             err ("note: ignoring nested PGP/MIME or S/MIME signature");
373           else
374             {
375               info->moss_state = 1;
376               info->is_smime = 1;
377               free (info->signing_protocol);
378               info->signing_protocol = xstrdup (s);
379             }
380         }
381       else if (verbose)
382         printf ("# this protocol is not supported\n");
383     }
384 }
385
386
387 /* Prepare for a multipart/encrypted. 
388    FIELD_CTX is the parsed context of the content-type header.*/
389 static void
390 mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
391                       rfc822parse_field_t field_ctx)
392 {
393   const char *s;
394   s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
395   if (s)
396     printf ("h encrypted.protocol: %s\n", s);
397 }
398
399
400 /* Prepare for old-style pkcs7 messages. */
401 static void
402 pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg,
403              rfc822parse_field_t field_ctx)
404 {
405   const char *s;
406   s = rfc822parse_query_parameter (field_ctx, "name", 0);
407   if (s)
408     printf ("h pkcs7.name: %s\n", s);
409   if (info->is_pkcs7)
410     err ("note: ignoring nested pkcs7 data");
411   else
412     {
413       info->is_pkcs7 = 1;
414       if (opt_crypto)
415         {
416           assert (!info->sig_file);
417           info->sig_file = tmpfile ();
418           if (!info->sig_file)
419             die ("error creating temp file: %s", strerror (errno));
420         }
421     }
422 }
423
424
425 /* Print the event received by the parser for debugging as comment
426    line. */
427 static void
428 show_event (rfc822parse_event_t event)
429 {
430   const char *s;
431
432   switch (event)
433     {
434     case RFC822PARSE_OPEN: s= "Open"; break;
435     case RFC822PARSE_CLOSE: s= "Close"; break;
436     case RFC822PARSE_CANCEL: s= "Cancel"; break;
437     case RFC822PARSE_T2BODY: s= "T2Body"; break;
438     case RFC822PARSE_FINISH: s= "Finish"; break;
439     case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
440     case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
441     case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
442     case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
443     case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
444     case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
445     case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
446     case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
447     default: s= "[unknown event]"; break;
448     }
449   printf ("# *** got RFC822 event %s\n", s);
450 }
451
452 /* This function is called by the parser to communicate events.  This
453    callback comminucates with the main program using a structure
454    passed in OPAQUE. Should retrun 0 or set errno and return -1. */
455 static int
456 message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
457 {
458   struct parse_info_s *info = opaque;
459
460   if (debug)
461     show_event (event);
462
463   if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
464     {
465       /* We need to check here whether to start collecting signed data
466          because attachments might come without header lines and thus
467          we won't see the BEGIN_HEADER event.  */
468       if (info->moss_state == 1)
469         {
470           printf ("c begin_hash\n");
471           info->hashing = 1;
472           info->hashing_level = info->nesting_level;
473           info->moss_state++;
474
475           if (opt_crypto)
476             {
477               assert (!info->hash_file);
478               info->hash_file = tmpfile ();
479               if (!info->hash_file)
480                 die ("failed to create temporary file: %s", strerror (errno));
481             }
482         }
483     }
484
485
486   if (event == RFC822PARSE_OPEN)
487     {
488       /* Initialize for a new message. */
489       info->show_header = 1;
490     }
491   else if (event == RFC822PARSE_T2BODY)
492     {
493       rfc822parse_field_t ctx;
494
495       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
496       if (ctx)
497         {
498           const char *s1, *s2;
499           s1 = rfc822parse_query_media_type (ctx, &s2);
500           if (s1)
501             {
502               printf ("h media: %*s%s %s\n", 
503                       info->nesting_level*2, "", s1, s2);
504               if (info->moss_state == 3)
505                 {
506                   char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
507                   strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
508                   assert (info->signing_protocol);
509                   if (strcmp (buf, info->signing_protocol))
510                     err ("invalid %s structure; expected `%s', found `%s'",
511                          info->is_smime? "S/MIME":"PGP/MIME",
512                          info->signing_protocol, buf);
513                   else
514                     {
515                       printf ("c begin_signature\n");
516                       info->moss_state++;
517                       if (opt_crypto)
518                         {
519                           assert (!info->sig_file);
520                           info->sig_file = tmpfile ();
521                           if (!info->sig_file)
522                             die ("error creating temp file: %s",
523                                  strerror (errno));
524                         }
525                     }
526                   free (buf);
527                 }
528               else if (!strcmp (s1, "multipart"))
529                 {
530                   if (!strcmp (s2, "signed"))
531                     mime_signed_begin (info, msg, ctx);
532                   else if (!strcmp (s2, "encrypted"))
533                     mime_encrypted_begin (info, msg, ctx);
534                 }
535               else if (!strcmp (s1, "application")
536                        && (!strcmp (s2, "pkcs7-mime")
537                            || !strcmp (s2, "x-pkcs7-mime")))
538                 pkcs7_begin (info, msg, ctx);
539             }
540           else
541             printf ("h media: %*s none\n", info->nesting_level*2, "");
542               
543           rfc822parse_release_field (ctx);
544         }
545       else
546         printf ("h media: %*stext plain [assumed]\n",
547                 info->nesting_level*2, "");
548
549
550       info->show_header = 0;
551       info->show_data = 1;
552       info->skip_show = 1;
553     }
554   else if (event == RFC822PARSE_PREAMBLE)
555     info->show_data_as_note = 1;
556   else if (event == RFC822PARSE_LEVEL_DOWN)
557     {
558       printf ("b down\n");
559       info->nesting_level++;
560     }
561   else if (event == RFC822PARSE_LEVEL_UP)
562     {
563       printf ("b up\n");
564       if (info->nesting_level)
565         info->nesting_level--;
566       else 
567         err ("invalid structure (bad nesting level)");
568     }
569   else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
570     {
571       info->show_data = 0;
572       info->show_boundary = 1;
573       if (event == RFC822PARSE_BOUNDARY)
574         {
575           info->show_header = 1;
576           info->skip_show = 1;
577           printf ("b part\n");
578         }
579       else 
580         printf ("b last\n");
581
582       if (info->moss_state == 2 && info->nesting_level == info->hashing_level)
583         {
584           printf ("c end_hash\n");
585           info->moss_state++;
586           info->hashing = 0;
587         }
588       else if (info->moss_state == 4)
589         {
590           printf ("c end_signature\n");
591           info->verify_now = 1;
592         }
593     }
594
595   return 0;
596 }
597
598
599 /* Read a message from FP and process it according to the global
600    options. */
601 static void
602 parse_message (FILE *fp)
603 {
604   char line[5000];
605   size_t length;
606   rfc822parse_t msg;
607   unsigned int lineno = 0;
608   int no_cr_reported = 0;
609   struct parse_info_s info;
610
611   memset (&info, 0, sizeof info);
612
613   msg = rfc822parse_open (message_cb, &info);
614   if (!msg)
615     die ("can't open parser: %s", strerror (errno));
616
617   /* Fixme: We should not use fgets becuase it can't cope with
618      embedded nul characters. */
619   while (fgets (line, sizeof (line), fp))
620     {
621       lineno++;
622       if (lineno == 1 && !strncmp (line, "From ", 5))
623         continue;  /* We better ignore a leading From line. */
624
625       length = strlen (line);
626       if (length && line[length - 1] == '\n')
627         line[--length] = 0;
628       else
629         err ("line number %u too long or last line not terminated", lineno);
630       if (length && line[length - 1] == '\r')
631         line[--length] = 0;
632       else if (verbose && !no_cr_reported)
633         {
634           err ("non canonical ended line detected (line %u)", lineno);
635           no_cr_reported = 1;
636         }
637
638
639       if (rfc822parse_insert (msg, line, length))
640         die ("parser failed: %s", strerror (errno));
641       
642       if (info.hashing)
643         {
644           /* Delay hashing of the CR/LF because the last line ending
645              belongs to the next boundary. */
646           if (debug)
647             printf ("# hashing %s`%s'\n", info.hashing==2?"CR,LF+":"", line);
648           if (opt_crypto)
649             {
650               if (info.hashing == 2)
651                 fputs ("\r\n", info.hash_file);
652               fputs (line, info.hash_file);
653               if (ferror (info.hash_file))
654                 die ("error writing to temporary file: %s", strerror (errno));
655             }
656
657           info.hashing = 2;
658         }
659
660       if (info.sig_file && opt_crypto)
661         {
662           if (info.verify_now)
663             {
664               verify_signature (&info);
665               if (info.hash_file)
666                 fclose (info.hash_file);
667               info.hash_file = NULL;
668               fclose (info.sig_file);
669               info.sig_file = NULL;
670               info.moss_state = 0;
671               info.is_smime = 0;
672               info.is_pkcs7 = 0;
673             }
674           else
675             {
676               fputs (line, info.sig_file);
677               fputs ("\r\n", info.sig_file);
678               if (ferror (info.sig_file))
679                 die ("error writing to temporary file: %s", strerror (errno));
680             }
681         }
682       
683       if (info.show_boundary)
684         {
685           if (!opt_no_header)
686             printf (":%s\n", line);
687           info.show_boundary = 0;
688         }
689
690       if (info.skip_show)
691         info.skip_show--;
692       else if (info.show_data)
693         {
694           if (info.show_data_as_note)
695             {
696               if (verbose)
697                 printf ("# DATA: %s\n", line);
698               info.show_data_as_note = 0;
699             }
700           else
701             printf (" %s\n", line);
702         }
703       else if (info.show_header && !opt_no_header)
704         printf (".%s\n", line);
705
706     }
707
708   if (info.sig_file && opt_crypto && info.is_pkcs7)
709     {
710       verify_signature (&info);
711       fclose (info.sig_file);
712       info.sig_file = NULL;
713       info.is_pkcs7 = 0;
714     }
715
716   rfc822parse_close (msg);
717 }
718
719
720 int 
721 main (int argc, char **argv)
722 {
723   int last_argc = -1;
724  
725   if (argc)
726     {
727       argc--; argv++;
728     }
729   while (argc && last_argc != argc )
730     {
731       last_argc = argc;
732       if (!strcmp (*argv, "--"))
733         {
734           argc--; argv++;
735           break;
736         }
737       else if (!strcmp (*argv, "--help"))
738         {
739           puts (
740                 "Usage: " PGM " [OPTION] [FILE]\n"
741                 "Parse a mail message into an annotated format.\n\n"
742                 "  --crypto    decrypt or verify messages\n"
743                 "  --no-header don't output the header lines\n"
744                 "  --verbose   enable extra informational output\n"
745                 "  --debug     enable additional debug output\n"
746                 "  --help      display this help and exit\n\n"
747                 "With no FILE, or when FILE is -, read standard input.\n\n"
748                 "WARNING: This tool is under development.\n"
749                 "         The semantics may change without notice\n\n"
750                 "Report bugs to <bug-gnupg@gnu.org>.");
751           exit (0);
752         }
753       else if (!strcmp (*argv, "--verbose"))
754         {
755           verbose = 1;
756           argc--; argv++;
757         }
758       else if (!strcmp (*argv, "--debug"))
759         {
760           verbose = debug = 1;
761           argc--; argv++;
762         }
763       else if (!strcmp (*argv, "--crypto"))
764         {
765           opt_crypto = 1;
766           argc--; argv++;
767         }
768       else if (!strcmp (*argv, "--no-header"))
769         {
770           opt_no_header = 1;
771           argc--; argv++;
772         }
773     }          
774  
775   if (argc > 1)
776     die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
777
778   signal (SIGPIPE, SIG_IGN);
779
780   if (argc && strcmp (*argv, "-"))
781     {
782       FILE *fp = fopen (*argv, "rb");
783       if (!fp)
784         die ("can't open `%s': %s", *argv, strerror (errno));
785       parse_message (fp);
786       fclose (fp);
787     }
788   else
789     parse_message (stdin);
790
791   return 0;
792 }
793
794
795 /*
796 Local Variables:
797 compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
798 End:
799 */