tools/gpgtar: Implement symmetric encryption.
[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 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. */
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 smfm_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
353   (void)msg;
354
355   s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
356   if (s)
357     {
358       printf ("h signed.protocol: %s\n", s);
359       if (!strcmp (s, "application/pgp-signature"))
360         {
361           if (info->smfm_state)
362             err ("note: ignoring nested PGP/MIME or S/MIME signature");
363           else
364             {
365               info->smfm_state = 1;
366               info->is_smime = 0;
367               free (info->signing_protocol);
368               info->signing_protocol = xstrdup (s);
369             }
370         }
371       else if (!strcmp (s, "application/pkcs7-signature")
372                || !strcmp (s, "application/x-pkcs7-signature"))
373         {
374           if (info->smfm_state)
375             err ("note: ignoring nested PGP/MIME or S/MIME signature");
376           else
377             {
378               info->smfm_state = 1;
379               info->is_smime = 1;
380               free (info->signing_protocol);
381               info->signing_protocol = xstrdup (s);
382             }
383         }
384       else if (verbose)
385         printf ("# this protocol is not supported\n");
386     }
387 }
388
389
390 /* Prepare for a multipart/encrypted.
391    FIELD_CTX is the parsed context of the content-type header.*/
392 static void
393 mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
394                       rfc822parse_field_t field_ctx)
395 {
396   const char *s;
397
398   (void)info;
399   (void)msg;
400
401   s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
402   if (s)
403     printf ("h encrypted.protocol: %s\n", s);
404 }
405
406
407 /* Prepare for old-style pkcs7 messages. */
408 static void
409 pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg,
410              rfc822parse_field_t field_ctx)
411 {
412   const char *s;
413
414   (void)msg;
415
416   s = rfc822parse_query_parameter (field_ctx, "name", 0);
417   if (s)
418     printf ("h pkcs7.name: %s\n", s);
419   if (info->is_pkcs7)
420     err ("note: ignoring nested pkcs7 data");
421   else
422     {
423       info->is_pkcs7 = 1;
424       if (opt_crypto)
425         {
426           assert (!info->sig_file);
427           info->sig_file = tmpfile ();
428           if (!info->sig_file)
429             die ("error creating temp file: %s", strerror (errno));
430         }
431     }
432 }
433
434
435 /* Print the event received by the parser for debugging as comment
436    line. */
437 static void
438 show_event (rfc822parse_event_t event)
439 {
440   const char *s;
441
442   switch (event)
443     {
444     case RFC822PARSE_OPEN: s= "Open"; break;
445     case RFC822PARSE_CLOSE: s= "Close"; break;
446     case RFC822PARSE_CANCEL: s= "Cancel"; break;
447     case RFC822PARSE_T2BODY: s= "T2Body"; break;
448     case RFC822PARSE_FINISH: s= "Finish"; break;
449     case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
450     case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
451     case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
452     case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
453     case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
454     case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
455     case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
456     case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
457     default: s= "[unknown event]"; break;
458     }
459   printf ("# *** got RFC822 event %s\n", s);
460 }
461
462 /* This function is called by the parser to communicate events.  This
463    callback comminucates with the main program using a structure
464    passed in OPAQUE. Should retrun 0 or set errno and return -1. */
465 static int
466 message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
467 {
468   struct parse_info_s *info = opaque;
469
470   if (debug)
471     show_event (event);
472
473   if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
474     {
475       /* We need to check here whether to start collecting signed data
476          because attachments might come without header lines and thus
477          we won't see the BEGIN_HEADER event.  */
478       if (info->smfm_state == 1)
479         {
480           printf ("c begin_hash\n");
481           info->hashing = 1;
482           info->hashing_level = info->nesting_level;
483           info->smfm_state++;
484
485           if (opt_crypto)
486             {
487               assert (!info->hash_file);
488               info->hash_file = tmpfile ();
489               if (!info->hash_file)
490                 die ("failed to create temporary file: %s", strerror (errno));
491             }
492         }
493     }
494
495
496   if (event == RFC822PARSE_OPEN)
497     {
498       /* Initialize for a new message. */
499       info->show_header = 1;
500     }
501   else if (event == RFC822PARSE_T2BODY)
502     {
503       rfc822parse_field_t ctx;
504
505       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
506       if (ctx)
507         {
508           const char *s1, *s2;
509           s1 = rfc822parse_query_media_type (ctx, &s2);
510           if (s1)
511             {
512               printf ("h media: %*s%s %s\n",
513                       info->nesting_level*2, "", s1, s2);
514               if (info->smfm_state == 3)
515                 {
516                   char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
517                   strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
518                   assert (info->signing_protocol);
519                   if (strcmp (buf, info->signing_protocol))
520                     err ("invalid %s structure; expected '%s', found '%s'",
521                          info->is_smime? "S/MIME":"PGP/MIME",
522                          info->signing_protocol, buf);
523                   else
524                     {
525                       printf ("c begin_signature\n");
526                       info->smfm_state++;
527                       if (opt_crypto)
528                         {
529                           assert (!info->sig_file);
530                           info->sig_file = tmpfile ();
531                           if (!info->sig_file)
532                             die ("error creating temp file: %s",
533                                  strerror (errno));
534                         }
535                     }
536                   free (buf);
537                 }
538               else if (!strcmp (s1, "multipart"))
539                 {
540                   if (!strcmp (s2, "signed"))
541                     mime_signed_begin (info, msg, ctx);
542                   else if (!strcmp (s2, "encrypted"))
543                     mime_encrypted_begin (info, msg, ctx);
544                 }
545               else if (!strcmp (s1, "application")
546                        && (!strcmp (s2, "pkcs7-mime")
547                            || !strcmp (s2, "x-pkcs7-mime")))
548                 pkcs7_begin (info, msg, ctx);
549             }
550           else
551             printf ("h media: %*s none\n", info->nesting_level*2, "");
552
553           rfc822parse_release_field (ctx);
554         }
555       else
556         printf ("h media: %*stext plain [assumed]\n",
557                 info->nesting_level*2, "");
558
559
560       info->show_header = 0;
561       info->show_data = 1;
562       info->skip_show = 1;
563     }
564   else if (event == RFC822PARSE_PREAMBLE)
565     info->show_data_as_note = 1;
566   else if (event == RFC822PARSE_LEVEL_DOWN)
567     {
568       printf ("b down\n");
569       info->nesting_level++;
570     }
571   else if (event == RFC822PARSE_LEVEL_UP)
572     {
573       printf ("b up\n");
574       if (info->nesting_level)
575         info->nesting_level--;
576       else
577         err ("invalid structure (bad nesting level)");
578     }
579   else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
580     {
581       info->show_data = 0;
582       info->show_boundary = 1;
583       if (event == RFC822PARSE_BOUNDARY)
584         {
585           info->show_header = 1;
586           info->skip_show = 1;
587           printf ("b part\n");
588         }
589       else
590         printf ("b last\n");
591
592       if (info->smfm_state == 2 && info->nesting_level == info->hashing_level)
593         {
594           printf ("c end_hash\n");
595           info->smfm_state++;
596           info->hashing = 0;
597         }
598       else if (info->smfm_state == 4)
599         {
600           printf ("c end_signature\n");
601           info->verify_now = 1;
602         }
603     }
604
605   return 0;
606 }
607
608
609 /* Read a message from FP and process it according to the global
610    options. */
611 static void
612 parse_message (FILE *fp)
613 {
614   char line[5000];
615   size_t length;
616   rfc822parse_t msg;
617   unsigned int lineno = 0;
618   int no_cr_reported = 0;
619   struct parse_info_s info;
620
621   memset (&info, 0, sizeof info);
622
623   msg = rfc822parse_open (message_cb, &info);
624   if (!msg)
625     die ("can't open parser: %s", strerror (errno));
626
627   /* Fixme: We should not use fgets because it can't cope with
628      embedded nul characters. */
629   while (fgets (line, sizeof (line), fp))
630     {
631       lineno++;
632       if (lineno == 1 && !strncmp (line, "From ", 5))
633         continue;  /* We better ignore a leading From line. */
634
635       length = strlen (line);
636       if (length && line[length - 1] == '\n')
637         line[--length] = 0;
638       else
639         err ("line number %u too long or last line not terminated", lineno);
640       if (length && line[length - 1] == '\r')
641         line[--length] = 0;
642       else if (verbose && !no_cr_reported)
643         {
644           err ("non canonical ended line detected (line %u)", lineno);
645           no_cr_reported = 1;
646         }
647
648
649       if (rfc822parse_insert (msg, line, length))
650         die ("parser failed: %s", strerror (errno));
651
652       if (info.hashing)
653         {
654           /* Delay hashing of the CR/LF because the last line ending
655              belongs to the next boundary. */
656           if (debug)
657             printf ("# hashing %s'%s'\n", info.hashing==2?"CR,LF+":"", line);
658           if (opt_crypto)
659             {
660               if (info.hashing == 2)
661                 fputs ("\r\n", info.hash_file);
662               fputs (line, info.hash_file);
663               if (ferror (info.hash_file))
664                 die ("error writing to temporary file: %s", strerror (errno));
665             }
666
667           info.hashing = 2;
668         }
669
670       if (info.sig_file && opt_crypto)
671         {
672           if (info.verify_now)
673             {
674               verify_signature (&info);
675               if (info.hash_file)
676                 fclose (info.hash_file);
677               info.hash_file = NULL;
678               fclose (info.sig_file);
679               info.sig_file = NULL;
680               info.smfm_state = 0;
681               info.is_smime = 0;
682               info.is_pkcs7 = 0;
683             }
684           else
685             {
686               fputs (line, info.sig_file);
687               fputs ("\r\n", info.sig_file);
688               if (ferror (info.sig_file))
689                 die ("error writing to temporary file: %s", strerror (errno));
690             }
691         }
692
693       if (info.show_boundary)
694         {
695           if (!opt_no_header)
696             printf (":%s\n", line);
697           info.show_boundary = 0;
698         }
699
700       if (info.skip_show)
701         info.skip_show--;
702       else if (info.show_data)
703         {
704           if (info.show_data_as_note)
705             {
706               if (verbose)
707                 printf ("# DATA: %s\n", line);
708               info.show_data_as_note = 0;
709             }
710           else
711             printf (" %s\n", line);
712         }
713       else if (info.show_header && !opt_no_header)
714         printf (".%s\n", line);
715
716     }
717
718   if (info.sig_file && opt_crypto && info.is_pkcs7)
719     {
720       verify_signature (&info);
721       fclose (info.sig_file);
722       info.sig_file = NULL;
723       info.is_pkcs7 = 0;
724     }
725
726   rfc822parse_close (msg);
727 }
728
729
730 int
731 main (int argc, char **argv)
732 {
733   int last_argc = -1;
734
735   if (argc)
736     {
737       argc--; argv++;
738     }
739   while (argc && last_argc != argc )
740     {
741       last_argc = argc;
742       if (!strcmp (*argv, "--"))
743         {
744           argc--; argv++;
745           break;
746         }
747       else if (!strcmp (*argv, "--help"))
748         {
749           puts (
750                 "Usage: " PGM " [OPTION] [FILE]\n"
751                 "Parse a mail message into an annotated format.\n\n"
752                 "  --crypto    decrypt or verify messages\n"
753                 "  --no-header don't output the header lines\n"
754                 "  --verbose   enable extra informational output\n"
755                 "  --debug     enable additional debug output\n"
756                 "  --help      display this help and exit\n\n"
757                 "With no FILE, or when FILE is -, read standard input.\n\n"
758                 "WARNING: This tool is under development.\n"
759                 "         The semantics may change without notice\n\n"
760                 "Report bugs to <bug-gnupg@gnu.org>.");
761           exit (0);
762         }
763       else if (!strcmp (*argv, "--verbose"))
764         {
765           verbose = 1;
766           argc--; argv++;
767         }
768       else if (!strcmp (*argv, "--debug"))
769         {
770           verbose = debug = 1;
771           argc--; argv++;
772         }
773       else if (!strcmp (*argv, "--crypto"))
774         {
775           opt_crypto = 1;
776           argc--; argv++;
777         }
778       else if (!strcmp (*argv, "--no-header"))
779         {
780           opt_no_header = 1;
781           argc--; argv++;
782         }
783     }
784
785   if (argc > 1)
786     die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
787
788   signal (SIGPIPE, SIG_IGN);
789
790   if (argc && strcmp (*argv, "-"))
791     {
792       FILE *fp = fopen (*argv, "rb");
793       if (!fp)
794         die ("can't open '%s': %s", *argv, strerror (errno));
795       parse_message (fp);
796       fclose (fp);
797     }
798   else
799     parse_message (stdin);
800
801   return 0;
802 }
803
804
805 /*
806 Local Variables:
807 compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
808 End:
809 */