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