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