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