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