1 /* gpgparsemail.c - Standalone crypto mail parser
2 * Copyright (C) 2004, 2007 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 /* This utility prints an RFC8222, 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. */
43 #include "rfc822parse.h"
46 #define PGM "gpgparsemail"
51 static int opt_crypto
; /* Decrypt or verify messages. */
52 static int opt_no_header
; /* Don't output the header lines. */
54 /* Structure used to communicate with the parser callback. */
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
60 int show_data_as_note
; /* The next data line should be shown
65 int is_pkcs7
; /* Old style S/MIME message. */
67 int moss_state
; /* State of PGP/MIME or S/MIME parsing. */
68 int is_smime
; /* This is S/MIME and not PGP/MIME. */
70 char *signing_protocol
;
71 int hashing_level
; /* The nesting level we are hashing. */
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
82 /* Print diagnostic message and exit with failure. */
84 die (const char *format
, ...)
89 fprintf (stderr
, "%s: ", PGM
);
91 va_start (arg_ptr
, format
);
92 vfprintf (stderr
, format
, arg_ptr
);
100 /* Print diagnostic message. */
102 err (const char *format
, ...)
107 fprintf (stderr
, "%s: ", PGM
);
109 va_start (arg_ptr
, format
);
110 vfprintf (stderr
, format
, arg_ptr
);
118 void *p
= malloc (n
);
120 die ("out of core: %s", strerror (errno
));
125 /* xcalloc (size_t n, size_t m) */
127 /* void *p = calloc (n, m); */
129 /* die ("out of core: %s", strerror (errno)); */
134 /* xrealloc (void *old, size_t n) */
136 /* void *p = realloc (old, n); */
138 /* die ("out of core: %s", strerror (errno)); */
143 xstrdup (const char *string
)
145 void *p
= malloc (strlen (string
)+1);
147 die ("out of core: %s", strerror (errno
));
154 stpcpy (char *a
,const char *b
)
165 run_gnupg (int smime
, int sig_fd
, int data_fd
, int *close_list
)
175 die ("error creating a pipe: %s", strerror (errno
));
179 die ("error forking process: %s", strerror (errno
));
183 char data_fd_buf
[50];
186 /* Connect our signature fd to stdin. */
189 if (dup2 (sig_fd
, 0) == -1)
190 die ("dup2 stdin failed: %s", strerror (errno
));
193 /* Keep our data fd and format it for gpg/gpgsm use. */
197 sprintf (data_fd_buf
, "-&%d", data_fd
);
199 /* Send stdout to the bit bucket. */
200 fd
= open ("/dev/null", O_WRONLY
);
202 die ("can't open `/dev/null': %s", strerror (errno
));
205 if (dup2 (fd
, 1) == -1)
206 die ("dup2 stderr failed: %s", strerror (errno
));
209 /* Connect stderr to our pipe. */
212 if (dup2 (rp
[1], 2) == -1)
213 die ("dup2 stderr failed: %s", strerror (errno
));
216 /* Close other files. */
217 for (i
=0; (fd
=close_list
[i
]) != -1; i
++)
218 if (fd
> 2 && fd
!= data_fd
)
223 execlp ("gpgsm", "gpgsm",
224 "--enable-special-filenames",
229 "-", data_fd
== -1? NULL
: data_fd_buf
,
232 execlp ("gpg", "gpg",
233 "--enable-special-filenames",
238 "-", data_fd
== -1? NULL
: data_fd_buf
,
241 die ("failed to exec the crypto command: %s", strerror (errno
));
247 fp
= fdopen (rp
[0], "r");
249 die ("can't fdopen pipe for reading: %s", strerror (errno
));
253 assert (sizeof status_buf
> 9);
254 while ((c
=getc (fp
)) != EOF
)
262 is_status
= !memcmp (status_buf
, "[GNUPG:] ", 9);
264 fputs ( "c ", stdout
);
266 fputs ( "# ", stdout
);
267 fwrite (status_buf
, 9, 1, stdout
);
273 if (verbose
&& pos
< 9)
275 fputs ( "# ", stdout
);
276 fwrite (status_buf
, pos
+1, 1, stdout
);
285 if (verbose
&& pos
< 9)
287 fputs ( "# ", stdout
);
288 fwrite (status_buf
, pos
+1, 1, stdout
);
294 while ( (i
=waitpid (pid
, NULL
, 0)) == -1 && errno
== EINTR
)
297 die ("waiting for child failed: %s", strerror (errno
));
305 /* Verify the signature in the current temp files. */
307 verify_signature (struct parse_info_s
*info
)
313 assert (!info
->hash_file
);
314 assert (info
->sig_file
);
315 rewind (info
->sig_file
);
319 assert (info
->hash_file
);
320 assert (info
->sig_file
);
321 rewind (info
->hash_file
);
322 rewind (info
->sig_file
);
325 /* printf ("# Begin hashed data\n"); */
326 /* while ( (c=getc (info->hash_file)) != EOF) */
328 /* printf ("# End hashed data signature\n"); */
329 /* printf ("# Begin signature\n"); */
330 /* while ( (c=getc (info->sig_file)) != EOF) */
332 /* printf ("# End signature\n"); */
333 /* rewind (info->hash_file); */
334 /* rewind (info->sig_file); */
337 run_gnupg (info
->is_smime
, fileno (info
->sig_file
),
338 info
->hash_file
? fileno (info
->hash_file
) : -1, close_list
);
345 /* Prepare for a multipart/signed.
346 FIELD_CTX is the parsed context of the content-type header.*/
348 mime_signed_begin (struct parse_info_s
*info
, rfc822parse_t msg
,
349 rfc822parse_field_t field_ctx
)
355 s
= rfc822parse_query_parameter (field_ctx
, "protocol", 1);
358 printf ("h signed.protocol: %s\n", s
);
359 if (!strcmp (s
, "application/pgp-signature"))
361 if (info
->moss_state
)
362 err ("note: ignoring nested PGP/MIME or S/MIME signature");
365 info
->moss_state
= 1;
367 free (info
->signing_protocol
);
368 info
->signing_protocol
= xstrdup (s
);
371 else if (!strcmp (s
, "application/pkcs7-signature")
372 || !strcmp (s
, "application/x-pkcs7-signature"))
374 if (info
->moss_state
)
375 err ("note: ignoring nested PGP/MIME or S/MIME signature");
378 info
->moss_state
= 1;
380 free (info
->signing_protocol
);
381 info
->signing_protocol
= xstrdup (s
);
385 printf ("# this protocol is not supported\n");
390 /* Prepare for a multipart/encrypted.
391 FIELD_CTX is the parsed context of the content-type header.*/
393 mime_encrypted_begin (struct parse_info_s
*info
, rfc822parse_t msg
,
394 rfc822parse_field_t field_ctx
)
401 s
= rfc822parse_query_parameter (field_ctx
, "protocol", 0);
403 printf ("h encrypted.protocol: %s\n", s
);
407 /* Prepare for old-style pkcs7 messages. */
409 pkcs7_begin (struct parse_info_s
*info
, rfc822parse_t msg
,
410 rfc822parse_field_t field_ctx
)
416 s
= rfc822parse_query_parameter (field_ctx
, "name", 0);
418 printf ("h pkcs7.name: %s\n", s
);
420 err ("note: ignoring nested pkcs7 data");
426 assert (!info
->sig_file
);
427 info
->sig_file
= tmpfile ();
429 die ("error creating temp file: %s", strerror (errno
));
435 /* Print the event received by the parser for debugging as comment
438 show_event (rfc822parse_event_t event
)
444 case RFC822PARSE_OPEN
: s
= "Open"; break;
445 case RFC822PARSE_CLOSE
: s
= "Close"; break;
446 case RFC822PARSE_CANCEL
: s
= "Cancel"; break;
447 case RFC822PARSE_T2BODY
: s
= "T2Body"; break;
448 case RFC822PARSE_FINISH
: s
= "Finish"; break;
449 case RFC822PARSE_RCVD_SEEN
: s
= "Rcvd_Seen"; break;
450 case RFC822PARSE_LEVEL_DOWN
: s
= "Level_Down"; break;
451 case RFC822PARSE_LEVEL_UP
: s
= "Level_Up"; break;
452 case RFC822PARSE_BOUNDARY
: s
= "Boundary"; break;
453 case RFC822PARSE_LAST_BOUNDARY
: s
= "Last_Boundary"; break;
454 case RFC822PARSE_BEGIN_HEADER
: s
= "Begin_Header"; break;
455 case RFC822PARSE_PREAMBLE
: s
= "Preamble"; break;
456 case RFC822PARSE_EPILOGUE
: s
= "Epilogue"; break;
457 default: s
= "[unknown event]"; break;
459 printf ("# *** got RFC822 event %s\n", s
);
462 /* This function is called by the parser to communicate events. This
463 callback comminucates with the main program using a structure
464 passed in OPAQUE. Should retrun 0 or set errno and return -1. */
466 message_cb (void *opaque
, rfc822parse_event_t event
, rfc822parse_t msg
)
468 struct parse_info_s
*info
= opaque
;
473 if (event
== RFC822PARSE_BEGIN_HEADER
|| event
== RFC822PARSE_T2BODY
)
475 /* We need to check here whether to start collecting signed data
476 because attachments might come without header lines and thus
477 we won't see the BEGIN_HEADER event. */
478 if (info
->moss_state
== 1)
480 printf ("c begin_hash\n");
482 info
->hashing_level
= info
->nesting_level
;
487 assert (!info
->hash_file
);
488 info
->hash_file
= tmpfile ();
489 if (!info
->hash_file
)
490 die ("failed to create temporary file: %s", strerror (errno
));
496 if (event
== RFC822PARSE_OPEN
)
498 /* Initialize for a new message. */
499 info
->show_header
= 1;
501 else if (event
== RFC822PARSE_T2BODY
)
503 rfc822parse_field_t ctx
;
505 ctx
= rfc822parse_parse_field (msg
, "Content-Type", -1);
509 s1
= rfc822parse_query_media_type (ctx
, &s2
);
512 printf ("h media: %*s%s %s\n",
513 info
->nesting_level
*2, "", s1
, s2
);
514 if (info
->moss_state
== 3)
516 char *buf
= xmalloc (strlen (s1
) + strlen (s2
) + 2);
517 strcpy (stpcpy (stpcpy (buf
, s1
), "/"), s2
);
518 assert (info
->signing_protocol
);
519 if (strcmp (buf
, info
->signing_protocol
))
520 err ("invalid %s structure; expected `%s', found `%s'",
521 info
->is_smime
? "S/MIME":"PGP/MIME",
522 info
->signing_protocol
, buf
);
525 printf ("c begin_signature\n");
529 assert (!info
->sig_file
);
530 info
->sig_file
= tmpfile ();
532 die ("error creating temp file: %s",
538 else if (!strcmp (s1
, "multipart"))
540 if (!strcmp (s2
, "signed"))
541 mime_signed_begin (info
, msg
, ctx
);
542 else if (!strcmp (s2
, "encrypted"))
543 mime_encrypted_begin (info
, msg
, ctx
);
545 else if (!strcmp (s1
, "application")
546 && (!strcmp (s2
, "pkcs7-mime")
547 || !strcmp (s2
, "x-pkcs7-mime")))
548 pkcs7_begin (info
, msg
, ctx
);
551 printf ("h media: %*s none\n", info
->nesting_level
*2, "");
553 rfc822parse_release_field (ctx
);
556 printf ("h media: %*stext plain [assumed]\n",
557 info
->nesting_level
*2, "");
560 info
->show_header
= 0;
564 else if (event
== RFC822PARSE_PREAMBLE
)
565 info
->show_data_as_note
= 1;
566 else if (event
== RFC822PARSE_LEVEL_DOWN
)
569 info
->nesting_level
++;
571 else if (event
== RFC822PARSE_LEVEL_UP
)
574 if (info
->nesting_level
)
575 info
->nesting_level
--;
577 err ("invalid structure (bad nesting level)");
579 else if (event
== RFC822PARSE_BOUNDARY
|| event
== RFC822PARSE_LAST_BOUNDARY
)
582 info
->show_boundary
= 1;
583 if (event
== RFC822PARSE_BOUNDARY
)
585 info
->show_header
= 1;
592 if (info
->moss_state
== 2 && info
->nesting_level
== info
->hashing_level
)
594 printf ("c end_hash\n");
598 else if (info
->moss_state
== 4)
600 printf ("c end_signature\n");
601 info
->verify_now
= 1;
609 /* Read a message from FP and process it according to the global
612 parse_message (FILE *fp
)
617 unsigned int lineno
= 0;
618 int no_cr_reported
= 0;
619 struct parse_info_s info
;
621 memset (&info
, 0, sizeof info
);
623 msg
= rfc822parse_open (message_cb
, &info
);
625 die ("can't open parser: %s", strerror (errno
));
627 /* Fixme: We should not use fgets becuase it can't cope with
628 embedded nul characters. */
629 while (fgets (line
, sizeof (line
), fp
))
632 if (lineno
== 1 && !strncmp (line
, "From ", 5))
633 continue; /* We better ignore a leading From line. */
635 length
= strlen (line
);
636 if (length
&& line
[length
- 1] == '\n')
639 err ("line number %u too long or last line not terminated", lineno
);
640 if (length
&& line
[length
- 1] == '\r')
642 else if (verbose
&& !no_cr_reported
)
644 err ("non canonical ended line detected (line %u)", lineno
);
649 if (rfc822parse_insert (msg
, line
, length
))
650 die ("parser failed: %s", strerror (errno
));
654 /* Delay hashing of the CR/LF because the last line ending
655 belongs to the next boundary. */
657 printf ("# hashing %s`%s'\n", info
.hashing
==2?"CR,LF+":"", line
);
660 if (info
.hashing
== 2)
661 fputs ("\r\n", info
.hash_file
);
662 fputs (line
, info
.hash_file
);
663 if (ferror (info
.hash_file
))
664 die ("error writing to temporary file: %s", strerror (errno
));
670 if (info
.sig_file
&& opt_crypto
)
674 verify_signature (&info
);
676 fclose (info
.hash_file
);
677 info
.hash_file
= NULL
;
678 fclose (info
.sig_file
);
679 info
.sig_file
= NULL
;
686 fputs (line
, info
.sig_file
);
687 fputs ("\r\n", info
.sig_file
);
688 if (ferror (info
.sig_file
))
689 die ("error writing to temporary file: %s", strerror (errno
));
693 if (info
.show_boundary
)
696 printf (":%s\n", line
);
697 info
.show_boundary
= 0;
702 else if (info
.show_data
)
704 if (info
.show_data_as_note
)
707 printf ("# DATA: %s\n", line
);
708 info
.show_data_as_note
= 0;
711 printf (" %s\n", line
);
713 else if (info
.show_header
&& !opt_no_header
)
714 printf (".%s\n", line
);
718 if (info
.sig_file
&& opt_crypto
&& info
.is_pkcs7
)
720 verify_signature (&info
);
721 fclose (info
.sig_file
);
722 info
.sig_file
= NULL
;
726 rfc822parse_close (msg
);
731 main (int argc
, char **argv
)
739 while (argc
&& last_argc
!= argc
)
742 if (!strcmp (*argv
, "--"))
747 else if (!strcmp (*argv
, "--help"))
750 "Usage: " PGM
" [OPTION] [FILE]\n"
751 "Parse a mail message into an annotated format.\n\n"
752 " --crypto decrypt or verify messages\n"
753 " --no-header don't output the header lines\n"
754 " --verbose enable extra informational output\n"
755 " --debug enable additional debug output\n"
756 " --help display this help and exit\n\n"
757 "With no FILE, or when FILE is -, read standard input.\n\n"
758 "WARNING: This tool is under development.\n"
759 " The semantics may change without notice\n\n"
760 "Report bugs to <bug-gnupg@gnu.org>.");
763 else if (!strcmp (*argv
, "--verbose"))
768 else if (!strcmp (*argv
, "--debug"))
773 else if (!strcmp (*argv
, "--crypto"))
778 else if (!strcmp (*argv
, "--no-header"))
786 die ("usage: " PGM
" [OPTION] [FILE] (try --help for more information)\n");
788 signal (SIGPIPE
, SIG_IGN
);
790 if (argc
&& strcmp (*argv
, "-"))
792 FILE *fp
= fopen (*argv
, "rb");
794 die ("can't open `%s': %s", *argv
, strerror (errno
));
799 parse_message (stdin
);
807 compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c"