#!/usr/bin/perl -w # $Id: gvv,v 1.16 2005/03/25 19:38:29 jason Exp jason $ # This takes hacked-up email messages (usually from mail-news gateways) # with multipart PGP signed content and attempts to verify the signed portion. # Traditional clearsigned messages are also allowed, and -c can reformat # the data as a clearsigned message before the verification step. # Input data is read from stdin (and buffered in memory). Only signed # data is output to a temporary file for verification. The files are created # in $TMPDIR or /tmp. -k keeps the files for later use. "gpg --verify" is # the default command used to verify PGP signatures, but may be overridden # by $gvv_verify_cmd. # -p removes the RE from beginning of all input # lines before verification. Uniformly indented PGP-signed content which # has been quoted _verbatim_ can often be verified/recovered using this # switch. In most cases, one or more dots/periods (".") should be used for # the RE to unindent all input lines equally. require "flush.pl"; use Fcntl; # for sysopen() flags... $clearsign = 0; $fix_kde_bugs = 0; $fix_hushmail_bugs = 0; $fix_mime_crc = 0; $add_dos_le = 0; # DOS line endings, CR+LF v. LF $passthrough = 0; # FIXME: use getopt or something better for this part. if (defined(@ARGV)) { while ($#ARGV >= 0) { if ($ARGV[0] eq "-p") { # strip pattern from input shift @ARGV; $pattern = shift @ARGV; } elsif ($ARGV[0] eq "-c") { $clearsign = 1; # we will artificially clearsign shift @ARGV; } elsif ($ARGV[0] eq "-k") { $keep = 1; shift @ARGV; } elsif ($ARGV[0] eq "-b") { $fix_kde_bugs = 1; shift @ARGV; } elsif ($ARGV[0] eq "-u") { $fix_hushmail_bugs = 1; shift @ARGV; } elsif ($ARGV[0] eq "-m") { $fix_mime_crc = 1; shift @ARGV; } elsif ($ARGV[0] eq "-d") { $add_dos_le = 1; shift @ARGV; } elsif ($ARGV[0] eq "-g") { $passthrough = 1; shift @ARGV; } } # while more args... } # if any args @lines = (); # holds buffered input lines $count = 0; $body_start = 0; # where the signed message starts... $body_end = 0; # and ends. $sig_start = 0; # where the PGP signature starts... $sig_end = 0; # and ends. $in_msg_body = 0; while () { # remove $pattern from beginning of each line s/^$pattern// if (defined($pattern)); # catch some stupid crap, seen from KMail/1.4.6. Bah! # (apparently mutt decodes this automagically, but, then, WTF is the # purpose of clearsigning if you're going to MIME-wrap the content?) # auto-detect a bug... if (!$passthrough && /^=2D----BEGIN PGP SIGNED MESSAGE-----/) { print STDERR "gvv: warning: detected =2D----BEGIN... line (KMail), demunging enabled.\n\n"; $fix_kde_bugs = 1; } # auto-detect binary-mode programs if (!$passthrough && !(/^user-agent: kmail\/1.5.94/i) && (/^user-agent: gnus/i || /^x-mailer: ximian/i || /^User-Agent: KMail/i || /^x-mailer: .*cone/i || /^User-Agent: Wanderlust/i || /^X-Mailer: Mew/i || /X-Mailer: Evolution/)) { print STDERR "gvv: warning: detected Gnus/Ximian/KMail/cone/etc., switching to binary.\n\n"; $add_dos_le = 1; } if (!$passthrough && $fix_kde_bugs) { s/^=2D/-/; # sig (^--) lines s/=20/ /; #s/=2D/-/; # armor lines s/=3D/=/ if ($sig_start); } if (!$passthrough && $fix_mime_crc) { s/=C3/\303/g; s/=B1/\261/g; s/=3D/=/; s/=20//; s/=A3/\243/; s/=F6/\366/; s/=E9/\351/; s/=2D/\055/; } # TDMA at news.gmane.org munges addresses, fix some we know about... # NB: s/=/-/ after the first word as needed... s#jharris-\+8w6ad7VraJ54TAoqtyWWQ\@public\.gmane\.org#jharris\@widomaker.com#g; s#toad-EI5O\+8PHWbJeeLb3ft/vUmD2FQJk\+8\+b\@public\.gmane\.org#toad\@amphibian.dyndns.org#g; s#tomg=sAMEgPp0FME\@public\.gmane\.org#tomg\@em.ca#g; s#root=ZKPxOa1ptvI\@public\.gmane\.org#root\@nova#g; s#tvon=Yuj4RLMN0HYAvxtiuMwx3w\@public\.gmane\.org#tvon\@etria.com#g; s#ian=ddtnN9NK5Ws\@public.gmane.org#ian\@locut.us#g; s#jim=Oxw1nKZkIVjk1uMJSBkQmQ\@public\.gmane\.org#jim\@meyering.net#g; s#eggert=CZoKd5lLoXZBDgjK7y7TUQ\@public\.gmane\.org#eggert\@twinsun.com#g; s#marcus=mXXj517/zsQ\@public\.gmane\.org#marcus\@gnu.org#g; s#dave.anglin=GPT7cTdnlGT\+SYiAP49vUg\@public\.gmane\.org#dave.anglin\@nrc-cnrc.gc.ca#g; s#mooney=w7A1mRLb/pJSpcB2DW7wPboKFM/j8NYTh13vi7wywA4\@public\.gmane\.org#mooney\@dogbert.cc.ndsu.NoDak.edu#g; s#jorton=H\+wXaHxf7aLQT0dZR\+AlfA\@public\.gmane\.org#jorton\@redhat.com#g; s#macro=8Y0ljV8xnLVscumT1wHf3A\@public\.gmane\.org#macro\@ds2.pg.gda.pl#g; s#derek=gOuHfDgrUr5BDgjK7y7TUQ\@public\.gmane\.org#derek\@ximbiot.com>#g; s#rich=Vp4quMj4K/HYB/AZRZ3tHL0Ud\+EcFu5g\@public\.gmane\.org#rich\@phekda.freeserve.co.uk#g; s#junkio=w\+B0EJWtgw4\@public\.gmane\.org#junkio\@cox.net#g; s#kasal=\+ZI9xUNit7I\@public\.gmane\.org#kasal\@ucw.cz#g; s#schwab=l3A5Bk7waGM\@public\.gmane\.org#schwab\@suse.de#g; s#bruno=nWNVUoHt2MvYtjvyW6yDsg\@public\.gmane\.org#bruno\@clisp.org#g; s#release=dw9K06Jb25ceV6sYBzf3Xg\@public\.gmane\.org#release\@spamassassin.org#g; s#spamassassin-devel=5NWGOfrQmneRv\+LV9MX5uipxlwaOVQ5f\@public\.gmane\.org#spamassassin-devel\@lists.sourceforge.net#g; s#greg=738XdyZ4GzZWk0Htik3J/w\@public\.gmane\.org#greg\@turnstep.com#g; s#support=xHchwMmBYmcdnm\+yROfE0A\@public\.gmane\.org#support\@cacert.org#g; s#CAcert=33AaDErTWvB61M2PxycHrA\@public\.gmane\.org#CAcert\@lists.cacert.org#g; s#keith=NDRMbMKbmSlAfugRpC6u6w\@public\.gmane\.org#keith\@nullify.org#g; s#dle=gj5PwTad9jdPBxfDYBASNA\@public\.gmane\.org#dle\@mindspring.com#g; s#mako-8fiUuRrzOP0dnm\+yROfE0A\@public\.gmane\.org#mako\@debian.org#g; s#llwang-DEHPddtdmuLYtjvyW6yDsg\@public\.gmane\.org#llwang\@infor.org#g; s#jullrich-h8DJP0oHRdWaMJb\+Lgu22Q\@public\.gmane\.org#jullrich\@euclidian.com#g; s#jullrich-3yDb4q2fBZk\@public\.gmane\.org#jullrich\@sans.org#g; s#toniw-X3B1VOXEql0\@public\.gmane\.org#toniw\@iki.fi#g; s/\012/\015\012/ if ($add_dos_le); if ($passthrough) { print $_; next; # bypass rest of loop... } push @lines, $_; $in_msg_body = 1 if (/^$/ || /^\015$/); # This will fail if the Content-Type header wasn't stripped from the # message, but I'm not sure if this is a problem since a MUA should # have no problem with such non-munged messages. Got mutt(1)? $body_start = $count if (((/^Content-Type: text/i && !$body_start) || (/^Content-Transfer-Encoding:/i && !$body_start)) && $in_msg_body); $body_end = $count - 3 if (/^Content-Type: application\/pgp-signature/i); # handle stuff that isn't MIME-wrapped too... if (/^-----BEGIN PGP SIGNED MESSAGE-----/) { $body_start = $count; $clearsign = 2; # message is naturally clearsigned if (!$passthrough && $fix_hushmail_bugs) { push @lines, "Hash: MD5,SHA1,RIPEMD160\n"; # why choose? } } $sig_start = $count if (/^-----BEGIN PGP SIGNATURE-----/); $sig_end = $count if (/^-----END PGP SIGNATURE-----/); # Ximian sucks _so_ bad... # (see 1063432397.6462.1.camel@monster.omnifarious.org on gmane.org) $sig_start = $count if (/^-----BEGIN PGP MESSAGE-----/); $sig_end = $count if (/^-----END PGP MESSAGE-----/); $count++; } # while exit 0 if $passthrough; # honor $TMPDIR if (defined ($ENV{'TMPDIR'})) { $tmpdir = $ENV{'TMPDIR'}; } else { $tmpdir = "/tmp"; } # FUTURE: add a .orig file to hold the original message, for easier # comparison when using -k? # these filenames are easy to guess, but... $message = $tmpdir . "/gvv." . $$ . "." . time(); $sig = ""; $sig = $message . ".asc" if (!$clearsign); # we create/open the files atomically and therefore securely. if (!sysopen (MESSAGE, $message, O_RDWR|O_CREAT|O_EXCL, 0600)) { die "can't create $message for writing"; } if (!$clearsign) { if (!sysopen (SIG, $sig, O_RDWR|O_CREAT|O_EXCL, 0600)) { unlink $message; die "can't create $sig for writing"; } } if (!$body_end) { # naturally clearsigned... $body_end = $sig_start - 1; } if ($clearsign == 1) { # artificially clearsigning... print MESSAGE "-----BEGIN PGP SIGNED MESSAGE-----\n"; print MESSAGE "Hash: MD5,SHA1,RIPEMD160\n"; # why choose? print MESSAGE "\n"; } for ($i = $body_start; $i <= $body_end; $i++) { print MESSAGE "- " if (($lines[$i] =~ /^-/) && ($clearsign == 1)); # dash-escape lines if # artificially clearsigning print MESSAGE $lines[$i]; } if ($clearsign == 1) { print MESSAGE "\n"; } for ($i = $sig_start; $i <= $sig_end; $i++) { if (!$clearsign) { print SIG $lines[$i]; } else { print MESSAGE $lines[$i]; } } close MESSAGE; close SIG if (!$clearsign); # use command specified in envt. var if available $name = "gvv_verify_cmd"; if (exists ($ENV{$name})) { $cmd = $ENV{$name}; } else { $cmd = "gpg --verify"; } $cmd .= " $sig $message"; # run it... # FUTURE: add more error checking below? $status = system ($cmd); if (!defined ($keep)) { unlink $message; unlink $sig if (!$clearsign); } else { print STDERR "\n$0: info: kept $message\*\n\n"; } exit ($status / 256); # return what the child process returned...