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