kuvert/kuvert

1421 lines
41 KiB
Plaintext
Raw Normal View History

2001-11-06 12:53:15 +00:00
#!/usr/bin/perl
#
# this file is part of kuvert, a wrapper around sendmail that
# does pgp/gpg signing/signing+encrypting transparently, based
# on the content of your public keyring(s) and your preferences.
#
# copyright (c) 1999-2005 Alexander Zangerl <az@snafu.priv.at>
2001-11-06 12:53:15 +00:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: kuvert,v 2.14 2005/02/25 22:09:21 az Exp az $
2001-11-06 12:53:15 +00:00
#--
use strict;
use Sys::Syslog qw(setlogsock openlog syslog closelog);
use Fcntl qw(:flock);
use Getopt::Std;
use MIME::Parser; # for parsing the mime-stream
use Mail::Address; # for parsing to and cc-headers
2001-11-11 11:41:05 +00:00
use FileHandle;
use Term::ReadKey;
use Proc::PID::File;
2001-11-06 12:53:15 +00:00
# some global stuff
2002-02-16 12:02:54 +00:00
# the version number is inserted by make install
my $version="INSERT_VERSION";
my $progname="kuvert";
2001-11-06 12:53:15 +00:00
# who are we gonna pretend to be today?
my($username,$home)=(getpwuid($<))[0,7];
2001-11-06 12:53:15 +00:00
# where is the configuration file
my $rcfile="$home/.kuvert";
# configuration directives, keyring
my (%config,@overrides,%keys);
# the passphrases are stored here if passphrase store is not a/v
2001-11-06 12:53:15 +00:00
my %secrets=();
2003-02-16 13:42:10 +00:00
my ($debug,$barfmail);
my @detailederror=();
2001-11-06 12:53:15 +00:00
my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp");
my $pidname="$progname.$<";
sub main
{
my %options;
2001-11-11 11:41:05 +00:00
2003-02-16 13:42:10 +00:00
if (!getopts("dkrnvb",\%options) || @ARGV)
{
2003-02-16 13:42:10 +00:00
die "usage: $progname [-n] [-d] [-v] [-b]| [-k] | [-r]
-k: kill running $progname
-d: debug mode
-r: reload keyrings and configfile
-n don't fork
2003-02-16 13:42:10 +00:00
-v: output version and exit
-b: complain via mail when dying\n";
}
if ($options{'v'})
{
print STDERR "$progname $version\n";
exit 0;
}
$debug=1 if ($options{"d"});
2003-02-16 13:42:10 +00:00
$barfmail=1 if ($options{"b"});
2001-11-06 12:53:15 +00:00
# kill a already running process
# TERM for kill or HUP for rereading
if ($options{"k"} || $options{"r"})
{
my $pid;
my $sig=($options{"r"}?'USR1':'TERM');
2001-11-06 12:53:15 +00:00
open(PIDF,"$piddir/$pidname.pid") || &bailout("cant open pidfile: $! -- exiting");
$pid=<PIDF>;
close(PIDF);
chomp $pid;
2001-11-06 12:53:15 +00:00
2003-02-16 13:42:10 +00:00
&bailout("no valid pid found, cant kill any process -- exiting")
2001-11-06 12:53:15 +00:00
if (!$pid);
2003-02-16 13:42:10 +00:00
&bailout("cant kill -$sig $pid: $! -- exiting")
if (!kill $sig, $pid);
exit 0;
}
2001-11-06 12:53:15 +00:00
if (! -e $rcfile)
{
open(F,">$rcfile") || &bailout("can't create $rcfile: $! -- exiting");
print F "# configuration file for kuvert\n"
."# see kuvert(1) for details\n";
close(F);
1==chmod(0600,$rcfile) ||
&bailout("can't chmod $rcfile: $! -- exiting");
print STDERR "created blank configuration file $rcfile\n"
}
logit("$progname version $version starting");
2001-11-06 12:53:15 +00:00
# read the config, setup dirs, logging, defaultkeys etc.
&read_config;
2002-09-19 16:25:46 +00:00
# get the passphrase(s) if no external passphrase store is used
# this has to be done before a fork...
if (!$config{secretondemand})
{
# get the passphrases and verify them
# if we do ng or std, ie. keyid!=0
get_secret("std") if ($config{stdkey});
get_secret("ng") if ($config{ngkey});
}
2001-11-06 12:53:15 +00:00
if (!$debug && !$options{"n"})
{
my $res=fork;
2001-11-06 12:53:15 +00:00
2003-02-16 13:42:10 +00:00
&bailout("fork failed: $! -- exiting")
if ($res == -1);
exit 0 if ($res);
}
2001-11-06 12:53:15 +00:00
# check that we're the only instance running
bailout("$progname: some other instance is running!")
if (Proc::PID::File->running(dir=>$piddir,
name=>$pidname));
# make things clean and ready. we're in sole command now.
cleanup($config{tempdir},0);
&read_keyrings;
2001-11-06 12:53:15 +00:00
# install the handler for conf reread
$SIG{'USR1'}=\&handle_reload;
# and the termination-handler
$SIG{'HUP'}=\&handle_term;
$SIG{'INT'}=\&handle_term;
$SIG{'QUIT'}=\&handle_term;
$SIG{'TERM'}=\&handle_term;
2001-11-06 12:53:15 +00:00
# the main loop, left only via signal handler handle_term
while (1)
2001-11-06 12:53:15 +00:00
{
2003-02-16 13:42:10 +00:00
&bailout("cant open $config{queuedir}: $! -- exiting")
if (!opendir(D,"$config{queuedir}"));
my $file;
foreach $file (grep(!/^\./,readdir(D)))
2001-11-06 12:53:15 +00:00
{
if (!open(FH,"$config{queuedir}/$file"))
{
logit("huh? $file just disappeared? $!");
next;
}
# lock it if possible
if (!flock(FH,LOCK_NB|LOCK_EX))
{
close(FH);
logit("$file is locked, skipping.");
next;
}
2001-11-06 12:53:15 +00:00
#ok, open & locked, let's proceed
logit("processing $file for $username");
2003-04-25 07:52:15 +00:00
$barfmail=0; # avoid duplicate mails, we're eval()ing!
eval { process_file(*FH,"$config{queuedir}/$file"); };
2003-04-25 07:52:15 +00:00
$barfmail=1 if ($options{"b"});
if ($@)
{
2003-04-25 07:52:15 +00:00
chomp(my $error=$@);
rename("$config{queuedir}/$file","$config{queuedir}/.$file")
2003-02-16 13:42:10 +00:00
|| &bailout("cant rename $config{queuedir}/$file: $! -- exiting");
2003-04-25 07:52:15 +00:00
logit("failed to process $file, left as \".$file\".\n");
send_bounce($error,$file);
}
else
{
logit("done with file $file");
unlink("$config{queuedir}/$file")
2003-02-16 13:42:10 +00:00
|| &bailout("cant unlink $config{queuedir}/$file: $! -- exiting");
}
# and clean up the cruft left behind, please!
cleanup("$config{tempdir}",0);
# unlock the file
2003-02-16 13:42:10 +00:00
bailout("problem unlocking $config{queuedir}/$file: $! -- exiting")
if (!flock(FH,LOCK_UN));
close(FH);
2001-11-06 12:53:15 +00:00
}
closedir(D);
&handle_term("debug mode") if ($debug);
sleep($config{interval});
2001-11-06 12:53:15 +00:00
}
}
# processes a file in the queue, does not remove stuff from the tempdir or the queue
# exception on errors
2001-11-06 12:53:15 +00:00
sub process_file
{
my ($fh,$file)=@_;
my $parser = new MIME::Parser;
# dump mime object to tempdir
$parser->output_dir($config{tempdir});
2001-11-06 12:53:15 +00:00
# retain rfc1522-encoded headers, please
$parser->decode_headers(0);
# make the parser ignore all filename info and just invent filenames.
$parser->filer->ignore_filename(1);
2001-11-06 12:53:15 +00:00
my $in_ent;
2001-11-06 12:53:15 +00:00
eval { $in_ent=$parser->read(\$fh); };
bailout("could not parse MIME stream, last header was ".$parser->last_head)
if ($@);
2001-11-06 12:53:15 +00:00
2003-02-21 11:41:06 +00:00
# add version header
2003-02-22 04:57:58 +00:00
$in_ent->head->add('X-Mailer',"$progname $version")
2003-02-21 11:41:06 +00:00
if ($config{identify});
2001-11-06 12:53:15 +00:00
# extract and delete instruction header
my $custom_conf=lc($in_ent->head->get("x-kuvert"));
$in_ent->head->delete("x-kuvert");
2001-11-06 12:53:15 +00:00
# strip trailing and leading whitespace from the custom header
$custom_conf =~ s/^\s*(\S*)\s*$/$1/;
# check the custom header for validity
undef $custom_conf
unless ($custom_conf=~/^(none|std(sign)?|ng(sign)?|fallback)(-force)?$/);
2001-11-06 12:53:15 +00:00
# extract a possible resend-request-header, if set call mta immediately
if ($custom_conf eq "none" || $in_ent->head->get("resent-to"))
2001-11-06 12:53:15 +00:00
{
logit(($custom_conf eq "none"?"resending ":"")
."sign/encrypt disabled, calling $config{mta} -t");
2001-11-06 12:53:15 +00:00
# we do not send the original file here because this file possibly
# holds the instruction header...
&send_entity($in_ent,"-t");
2001-11-06 12:53:15 +00:00
$in_ent->purge;
return;
2001-11-06 12:53:15 +00:00
}
my (@recip_all,@recip_bcc);
# get the recipients
map { push @recip_all, lc($_->address); }
Mail::Address->parse($in_ent->head->get("To"),
$in_ent->head->get("Cc"));
map { push @recip_bcc, lc($_->address); }
Mail::Address->parse($in_ent->head->get("Bcc"));
# but don't leak Bcc...
$in_ent->head->delete("Bcc");
# cry out loud if there is a problem with the submitted mail
# and no recipients were distinguishable...
# happens sometimes, with mbox-style 'From bla' lines in the headers...
bailout("no recipients found! the mail headers seem to be garbled.")
if (!@recip_all && !@recip_bcc);
# figure out what to do for specific recipients
my %actions=findaction($custom_conf,\@recip_all,\@recip_bcc);
my $orig_header;
my $input="$config{tempdir}/.input";
# take care of raw mails, before mangling the headers
my @recips=grep($actions{$_} eq "none",keys %actions);
if (@recips)
{
logit("sending mail (raw) to ".join(",",@recips));
&send_entity($in_ent,@recips);
}
# prepare various stuff we need only when encrypting or signing
if(grep(/(ng|std)/,values(%actions)))
2001-11-06 12:53:15 +00:00
{
# copy (mail)header, split header info
# in mime-related (remains with the entity) and non-mime
# (is saved in the new, outermost header-object)
$orig_header=$in_ent->head->dup;
2001-11-06 12:53:15 +00:00
# content-* stays with the entity and the rest moves to orig_header
foreach my $headername ($in_ent->head->tags)
2001-11-06 12:53:15 +00:00
{
if ($headername !~ /^content-/i)
{
# remove the stuff from the entity
$in_ent->head->delete($headername);
}
else
{
# remove this stuff from the orig_header
$orig_header->delete($headername);
}
2001-11-06 12:53:15 +00:00
}
# any text/plain parts of the entity have to be fixed with the
# correct content-transfer-encoding (qp), since any transfer 8->7bit
# on the way otherwise will break the signature.
# this is not necessary if encrypting, but done anyways since
# it doesnt hurt and we want to be on the safe side.
2001-11-06 12:53:15 +00:00
qp_fix_parts($in_ent);
2001-11-06 12:53:15 +00:00
# now we've got a $in_entity which is ready to be encrypted/signed
# and the mail-headers are saved in $orig_header
2001-11-06 12:53:15 +00:00
# since old pgp has problems with stuff signed/encrypted
# by newer software that uses partial-length headers when fed
# data via pipe, we write out our $in_entity to a tempfile
# which is then used in the relevant signing/encryption operations.
2001-11-06 12:53:15 +00:00
bailout("cant open >$input: $!")
if (!open(F,">$input"));
$in_ent->print(\*F);
close(F);
2001-11-06 12:53:15 +00:00
}
foreach my $action qw(ng ngsign std stdsign bcc-ng bcc-std)
2001-11-06 12:53:15 +00:00
{
my @recips=grep($actions{$_} eq $action,keys %actions);
next if (!@recips);
2001-11-06 12:53:15 +00:00
my $type=($action=~/ng/?"ng":"std");
2001-11-06 12:53:15 +00:00
if ($action=~/bcc/)
{
# send stuff single file, one completely separate mail per bcc recipient...ugly and slow
# but the Right Thing, otherwise we leak encryption key information
# (only necessary for encryption)
foreach (@recips)
{
logit("sending mail (bcc,crypt,$type) to $_");
&crypt_send($in_ent,$input,$type,$orig_header,[$keys{$type}->{$_}],$_);
}
next;
}
if ($action=~/sign/)
{
logit("sending mail (sign,$type) to ".join(",",@recips));
&sign_send($in_ent,$input,$type,$orig_header,@recips);
next;
}
else
{
my @recipkeys;
map { push @recipkeys,$keys{$type}->{$_}; } @recips;
logit("sending mail (crypt,$type) to ".join(",",@recips));
&crypt_send($in_ent,$input,$type,$orig_header,\@recipkeys,@recips);
}
2001-11-06 12:53:15 +00:00
}
}
# sign an entity and send the resulting email to the listed recipients
# args: entity, location of dump of entity, type, outermost headers, recipients
# exception on errors
2001-11-06 12:53:15 +00:00
sub sign_send
{
my ($ent,$dumpfile,$type,$header,@recips)=@_;
my $output="$config{tempdir}/.signout";
2001-11-06 12:53:15 +00:00
# generate a new top-entity to be mailed
my $newent=new MIME::Entity;
# make a private copy of the main header and set this one
2001-11-06 12:53:15 +00:00
$newent->head($header->dup);
# make it a multipart/signed
# and set the needed content-type-fields on this top entity
$newent->head->mime_attr("MIME-Version"=>"1.0");
$newent->head->mime_attr("Content-Type"=>"multipart/signed");
$newent->head->mime_attr("Content-Type.Boundary"=>
&MIME::Entity::make_boundary);
$newent->head->mime_attr("Content-Type.Protocol"=>
"application/pgp-signature");
$newent->head->mime_attr("content-Type.Micalg" => ($type eq "ng"?"pgp-sha1":"pgp-md5"));
2001-11-06 12:53:15 +00:00
$newent->preamble(["This is a multi-part message in MIME format.\n",
2002-09-19 14:58:21 +00:00
"It has been signed conforming to RFC3156.\n",
"You need GPG or PGP to check the signature.\n"]);
2001-11-06 12:53:15 +00:00
# add the passed entity as part
$newent->add_part($ent);
# generate the signature, repeat until proper passphrase given
while (&sign_encrypt(0,$type,$dumpfile,$output,undef))
{
# get rid of broken passphrase and lets try again
if ($config{secretondemand})
{
$debug && logit("bad passphrase, retry");
my $cmd=sprintf($config{delsecret},$config{$type."key"});
my $res=0xffff & system("$cmd >$config{tempdir}/subproc 2>&1");
bailout("error deleting broken passphrase from store: $res",
"$config{tempdir}/subproc")
if ($res);
}
else
{
# bad passphrase but we're on our own -> cant recover
bailout("bad passphrase, but no passphrase store to query!");
}
}
2001-11-06 12:53:15 +00:00
# attach the signature
$newent->attach(Type => "application/pgp-signature",
2003-01-15 22:57:54 +00:00
Path => "$output",
Filename => undef,
2001-11-06 12:53:15 +00:00
Disposition => "inline",
Encoding => "7bit");
# and send the resulting thing, not cleaning up
&send_entity($newent,@recips);
2001-11-06 12:53:15 +00:00
}
# encrypt and sign an entity, send the resulting email to the listed recipients
# args: entity, location of dump of entity, type, outermost headers, recipient keys, recipient addresses
2001-11-06 12:53:15 +00:00
sub crypt_send
{
my ($ent,$dumpfile,$type,$header,$rec_keys,@recips)=@_;
my $output="$config{tempdir}/.encout";
2001-11-06 12:53:15 +00:00
# generate a new top-entity to be mailed
my $newent=new MIME::Entity;
# make a private copy of the main header and set this one
2001-11-06 12:53:15 +00:00
$newent->head($header->dup);
# make it a multipart/encrypted
# and set the needed content-type-fields on this top entity
$newent->head->mime_attr("MIME-Version"=>"1.0");
$newent->head->mime_attr("Content-Type"=>"multipart/encrypted");
$newent->head->mime_attr("Content-Type.Boundary"=>
&MIME::Entity::make_boundary);
$newent->head->mime_attr("Content-Type.Protocol"=>
"application/pgp-encrypted");
# set the new preamble
$newent->preamble(["This is a multi-part message in MIME format.\n",
"It has been encrypted conforming to RFC3156.\n",
"You need PGP or GPG to view the content.\n"]);
2001-11-06 12:53:15 +00:00
# attach the needed dummy-part
$newent->attach(Type=>"application/pgp-encrypted",
Data=>"Version: 1\n",
Encoding=>"7bit");
# generate the encrypted data, repeat until proper passphrase given
while (&sign_encrypt(1,$type,$dumpfile,$output,@{$rec_keys}))
{
# get rid of broken passphrase and lets try again
if ($config{secretondemand})
{
$debug && logit("bad passphrase, retry");
my $cmd=sprintf($config{delsecret},$config{$type."key"});
my $res=0xffff & system("$cmd >$config{tempdir}/subproc 2>&1");
bailout("error deleting broken passphrase from store: $res",
"$config{tempdir}/subproc")
if ($res);
}
else
{
# bad passphrase but we're on our own -> cant recover
bailout("bad passphrase, but no passphrase store to query!");
}
}
2001-11-06 12:53:15 +00:00
# attach the encrypted data
$newent->attach(Type => "application/octet-stream",
Path => "$output",
2001-11-06 12:53:15 +00:00
Filename => undef,
Disposition => "inline",
Encoding=>"7bit");
# and send the resulting thing
&send_entity($newent,@recips);
2001-11-06 12:53:15 +00:00
}
# send entity to $mta, passing $args to $mta
# ent is a MIME::Entity and args is either "-t" or a list of recipients
# exception on errors
2001-11-06 12:53:15 +00:00
sub send_entity
{
my ($ent,@args)=@_;
my $pid=open(TOMTA,"|-");
bailout("cant open pipe to $config{mta}: $!") if (!defined $pid);
if ($pid)
{
$ent->print(\*TOMTA);
close(TOMTA) || bailout("error talking to child $config{mta}: $?");
}
else
{
my @cmd=split(/\s+/,$config{mta});
exec(@cmd,@args) || bailout("error execing $cmd[0]: $!");
}
2001-11-06 12:53:15 +00:00
}
# remove temporary stuff left behind in directory $what
# remove_what set: remove the dir, too.
# exception on error, no retval
2001-11-06 12:53:15 +00:00
sub cleanup
{
my ($what,$remove_what)=@_;
my ($name,$res);
opendir(F,$what) || bailout("cant opendir $what: $!");
2001-11-06 12:53:15 +00:00
foreach $name (readdir(F))
{
next if ($name =~ /^\.{1,2}$/o);
(-d "$what/$name")?&cleanup("$what/$name",1):
(unlink("$what/$name") || bailout("cant unlink $what/$name: $!"));
2001-11-06 12:53:15 +00:00
}
closedir(F);
$remove_what && (rmdir("$what") || bailout("cant rmdir $what: $!"));
2001-11-06 12:53:15 +00:00
return 0;
}
# log termination, cleanup, exit
sub handle_term
{
my ($sig)=@_;
logit("got termination signal SIG$sig, cleaning up");
my $res=&cleanup($config{tempdir},1);
logit("problem cleaning up $config{tempdir}: $res")
2001-11-06 12:53:15 +00:00
if ($res);
# wipe keys
if ($config{secretondemand})
{
foreach ($config{ngkey},$config{stdkey})
{
next if (!$_);
my $cmd=sprintf($config{delsecret},$_);
my $res=0xffff & system $cmd;
&logit("problem deleting secret for $_: $res")
if ($res);
}
}
close $config{logfh} if ($config{logfh});
2001-11-06 12:53:15 +00:00
exit 0;
}
# reread configuration file and keyrings
2002-09-19 16:25:46 +00:00
# no args or return value; intended as a sighandler.
2001-11-25 11:39:53 +00:00
sub handle_reload
2002-09-19 16:25:46 +00:00
{
logit("rereading config file");
&read_config;
&read_keyrings;
}
# read keyrings into global hashes
2002-10-27 13:45:50 +00:00
# note: this must happen after the config is read, so that
# the right tools are used (gpg vs. pgp)
2002-09-19 16:25:46 +00:00
sub read_keyrings
{
my ($lastkey,$lasttype,@tmp,$name,$now,@info);
2002-10-27 13:45:50 +00:00
my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled',
'r'=>'revoked','e'=>'expired');
%{$keys{std}}=();
2002-10-27 13:45:50 +00:00
if ($config{usepgp})
2002-10-27 13:45:50 +00:00
{
if (!$config{stdkey})
2002-10-27 13:45:50 +00:00
{
logit("ignoring std keyring, no key a/v.");
}
else
{
logit("reading std keyring.");
$now=time;
#get the keys and dump the trailer and header lines
# this does not care if pgp is not existent...but then, we're not
# needing the pgp keyring
@tmp=`$config{pgppath} -kv 2>$config{tempdir}/subproc`;
bailout("failure reading keyring with $config{pgppath}: $?",
"$config{tempdir}/subproc") if ($?);
foreach (@tmp)
2002-10-27 13:45:50 +00:00
{
if (/^pub\s+\d+\/(\S+)\s+(.+)$/)
2002-10-27 13:45:50 +00:00
{
my ($key,$userspec)=($1,$2);
if ($userspec =~ /(\s|<)([^\s<]+\@[^\s>]+)>?/)
{
$name=lc($2);
}
else
{
undef $name;
}
if ($name)
{
$keys{std}->{$name}="0x$key";
$lastkey=$key;
&logit("got stdkey 0x$key for $name") if ($debug);
}
else
{
$lastkey=$key;
&logit("saved stdkey 0x$key, no address known yet")
if ($debug);
}
next;
2002-10-27 13:45:50 +00:00
}
if (/^\s+.*(\s|<)([^\s<]+\@[^\s>]+)>?\s*$/)
2002-10-27 13:45:50 +00:00
{
my $name=lc($2);
$keys{std}->{$name}="0x$lastkey";
&logit("got stdkey (uid) 0x$lastkey for $name") if ($debug);
2002-10-27 13:45:50 +00:00
}
}
}
}
%{$keys{ng}}=();
2002-10-27 13:45:50 +00:00
if ($config{ngkey} || !$config{usepgp} && $config{stdkey})
{
logit("reading ".(!$config{usepgp} && $config{stdkey}?"combined":"ng")." keyring.");
2002-10-27 13:45:50 +00:00
# this does not care if gpg is not existent...but then, we're not
# needing the gpg keyring
@tmp=`$config{gpgpath} -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$config{tempdir}/subproc`;
bailout("failure reading keyring with $config{gpgpath}: $?",
"$config{tempdir}/subproc") if ($?);
foreach (@tmp)
2002-10-27 13:45:50 +00:00
{
@info=split(/:/);
# only public keys and uids are of interest
next if ($info[0] ne "pub" && $info[0] ne "uid");
$info[4] =~ s/^.{8}//; # truncate key-id
# rsa-keys only if !$usepgp
# and be sure to skip these uid's, too
if ($config{usepgp} && $info[3] eq "1")
2002-10-27 13:45:50 +00:00
{
&logit("ignoring stdkey 0x$info[4]") if ($debug && $info[4]);
undef $lastkey;
next;
}
elsif (!$config{ngkey} && $info[3] ne "1")
{
&logit("ignoring ngkey 0x$info[4]") if ($debug && $info[4]);
undef $lastkey;
next;
2002-10-27 13:45:50 +00:00
}
$info[9] =~ s/\\x3a/:/g; # re-insert colons, please
# remember the email address
# if no address given: remember this key
# but go on to the uid's to get an email address to
# work with
if ($info[9] =~ /(\s|<)([^\s<]+\@[^\s>]+)>?/)
2002-10-27 13:45:50 +00:00
{
$name=lc($2);
2002-10-27 13:45:50 +00:00
}
else
2002-10-27 13:45:50 +00:00
{
undef $name;
2002-10-27 13:45:50 +00:00
}
# check the key: public part or uid?
if ($info[0] eq "pub")
2002-10-27 13:45:50 +00:00
{
# lets associate this key with the current email address
# if an address is known
$lastkey=$info[4];
$lasttype=$info[3]==1?"std":"ng";
2002-10-27 13:45:50 +00:00
if ($name)
{
# ignore expired, revoked and other bad keys
if (defined $badcauses{$info[1]})
{
&logit("ignoring ".($info[3]==1?"std":"ng")
." key 0x$info[4], reason: "
.$badcauses{$info[1]});
2002-10-27 13:45:50 +00:00
next;
}
$keys{$lasttype}->{$name}="0x$lastkey";
&logit("got $lasttype key 0x$lastkey for $name")
2002-10-27 13:45:50 +00:00
if ($debug);
}
else
{
&logit("saved $lasttype key 0x$lastkey, no address known yet")
if ($debug);
}
next;
}
else
{
# uid: associate the current address with the key
# given in the most recent public key line
# if no such key saved: the pub key was an rsa key &
# we're set to ignore those
if (!$lastkey)
{
$name="<no valid address>" if (!$name);
&logit("ignoring uid $name, belongs to std key?")
2002-10-27 13:45:50 +00:00
if ($debug);
}
else
{
if ($name)
{
# ignore expired, revoked and other bad keys
if (defined $badcauses{$info[1]})
{
&logit("ignoring ".($info[3]==1?"std":"ng")
." uid $name for 0x$lastkey, "
."reason: ".$badcauses{$info[1]});
next;
}
$keys{$lasttype}->{$name}="0x$lastkey";
&logit("got $lasttype key (uid) 0x$lastkey for $name")
if ($debug);
}
else
{
&logit("ignoring uid without valid address")
if ($debug);
}
}
2002-10-27 13:45:50 +00:00
}
}
}
else
{
logit("ignoring ng keyring, no key a/v.");
}
2002-09-19 16:25:46 +00:00
}
# reads the configuration file, sets config variables
# exception on major problems
# no retval. changes %config and @overrides on success.
2002-09-19 16:25:46 +00:00
sub read_config
2001-11-06 12:53:15 +00:00
{
my @over;
# default settings
my $defaction="none";
my %newconf=(ngkey=>undef,
2003-02-16 13:42:10 +00:00
stdkey=>undef,
pgppath=>"/usr/bin/pgp",
gpgpath=>"/usr/bin/gpg",
usepgp=>0,
getsecret=>undef,
delsecret=>undef,
2003-02-16 13:42:10 +00:00
mta=>"/usr/lib/sendmail -om -oi -oem",
secretondemand=>0,
alwaystrust=>0,
interval=>60,
tempdir=>($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$username.$$",
queuedir=>"$home/.kuvert_queue",
logfile=>undef,
2003-02-21 11:41:06 +00:00
logfh=>undef,
2003-02-22 04:57:58 +00:00
identify=>0);
2002-09-19 16:25:46 +00:00
2003-02-16 13:42:10 +00:00
&bailout("cant open $rcfile: $! -- exiting")
if (!open (F,$rcfile));
2002-09-19 16:25:46 +00:00
logit("reading config file");
while (<F>)
2001-11-06 12:53:15 +00:00
{
2002-09-19 16:25:46 +00:00
chomp;
next if (/^\#/ || /^\s*$/); # strip comments and empty lines
if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/)
2002-09-19 16:25:46 +00:00
{
my ($key,$action)=(lc($1),lc($2));
if ($key eq "default")
{
$defaction=$action;
$debug && logit("changing default action to $action");
}
else
{
push @over,{"key"=>$key,
"re"=>qr/$key/,
"action"=>$action};
$debug && logit("got conf $action for $key");
}
2002-09-19 16:25:46 +00:00
}
elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/)
2002-09-19 16:25:46 +00:00
{
my ($key,$value)=(lc($1),$2);
if (grep($_ eq $key, keys %newconf))
{
$newconf{$key}=$value;
$debug && logit("set config $key to $value");
}
else
{
2003-02-16 13:42:10 +00:00
&bailout("bad config entry \"$_\" -- exiting");
}
2002-09-19 16:25:46 +00:00
}
else
{
2003-02-16 13:42:10 +00:00
&bailout("bad config entry \"$_\" -- exiting");
}
}
close F;
# last per-address override is the catch-all default
push @over,{"key"=>"default",
"re"=>qr/.*/,
"action"=>"$defaction"};
# generate queuedir if not existing
if (!-d $newconf{queuedir})
{
unlink "$newconf{queuedir}";
2003-02-16 13:42:10 +00:00
&bailout("cant mkdir $newconf{queuedir}: $! -- exiting")
if (!mkdir($newconf{queuedir},0700));
}
# check queuedir owner & perm
elsif ((stat($newconf{queuedir}))[4] != $<)
{
2003-02-16 13:42:10 +00:00
&bailout("$newconf{queuedir} is not owned by you -- exiting");
}
elsif ((stat($newconf{queuedir}))[2] & 0777 != 0700)
{
2003-02-16 13:42:10 +00:00
&bailout("$newconf{queuedir} does not have mode 0700 -- exiting");
}
# make tempdir
if (!-d $newconf{tempdir})
{
unlink "$newconf{tempdir}";
if (!mkdir($newconf{tempdir},0700))
2002-09-19 16:25:46 +00:00
{
2003-02-16 13:42:10 +00:00
&bailout("cant mkdir $newconf{tempdir}: $! -- exiting");
2002-09-19 16:25:46 +00:00
}
}
elsif ((stat($newconf{tempdir}))[4] != $<)
{
2003-02-16 13:42:10 +00:00
&bailout("$newconf{tempdir} is not owned by you -- exiting");
}
elsif ((stat($newconf{tempdir}))[2]&0777 != 0700)
{
2003-02-16 13:42:10 +00:00
&bailout("$newconf{tempdir} does not have mode 0700 -- exiting");
}
# close old logfile if there is one
close($config{logfile})
if ($config{logfile} && $config{logfile} ne $newconf{logfile});
if ($newconf{logfile})
{
2003-02-16 13:42:10 +00:00
&bailout("cant open logfile $newconf{logfile}: $! -- exiting")
if (!open($newconf{logfh},">>$newconf{logfile}"));
$newconf{logfh}->autoflush(1);
2001-11-06 12:53:15 +00:00
}
# secret on demand is only possible with both a get and a del command
$newconf{secretondemand}=0
if (!$newconf{getsecret} || !$newconf{delsecret});
# sanity checks: external executables
&bailout("bad executable '$newconf{mta}' -- exiting")
if ($newconf{mta}=~/^(\S+)/ && ! -x $1);
if ($newconf{secretondemand})
{
&bailout("bad executable '$newconf{getsecret}' -- exiting")
if ($newconf{getsecret}
&& $newconf{getsecret}=~/^(\S+)/ && ! -x $1);
&bailout("bad executable '$newconf{delsecret}' -- exiting")
if ($newconf{delsecret}
&& $newconf{delsecret}=~/^(\S+)/ && ! -x $1);
}
&bailout("bad executable '$newconf{pgppath}' -- exiting")
if ($newconf{usepgp} && $newconf{stdkey} ne "0"
&& (!$newconf{pgppath} || $newconf{pgppath}=~/^(\S+)/ && ! -x $1));
&bailout("bad executable '$newconf{gpgpath}' -- exiting")
if ($newconf{ngkey} ne "0"
&& ( !$newconf{gpgpath} || $newconf{gpgpath}=~/^(\S+)/ && ! -x $1));
# figure out the default keys if none were supplied, check them
if ($newconf{ngkey})
{
my $res=0xffff & system("$newconf{gpgpath} -q --batch --list-secret-keys --with-colons $newconf{ngkey} >$newconf{tempdir}/subproc 2>&1");
2003-02-16 13:42:10 +00:00
bailout("bad ngkey spec '$newconf{ngkey}' -- exiting","$newconf{tempdir}/subproc") if ($res);
}
elsif (!defined $newconf{ngkey})
{
2003-02-16 13:42:10 +00:00
open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc |") || bailout("cant fork $newconf{gpgpath} to list sec keys: $! -- exiting");
while (<F>)
{
my @list=split(/:/);
next if ($list[0] ne "sec" || $list[3] ne "17");
$list[4] =~ s/^.{8}//; # truncate key-id
$newconf{ngkey}="0x$list[4]";
$debug && logit("set ngkey to $newconf{ngkey}");
last;
}
close F;
2003-02-16 13:42:10 +00:00
bailout("error running $newconf{gpgpath}: $? -- exiting","$newconf{tempdir}/subproc") if ($?);
bailout("could not find ngkey -- exiting") if (!$newconf{ngkey});
}
2002-09-19 14:58:21 +00:00
if ($newconf{stdkey})
2002-09-19 14:58:21 +00:00
{
if ($newconf{usepgp})
{
my $res=0xffff & system("$newconf{pgppath} -kv $newconf{stdkey} $home/.pgp/secring.pgp >$newconf{tempdir}/subproc 2>&1");
2003-02-16 13:42:10 +00:00
bailout("bad stdkey spec \"$newconf{stdkey}\" -- exiting","$newconf{tempdir}/subproc") if ($res);
}
else
{
my $res=0xffff & system("$newconf{gpgpath} -q --batch --list-secret-keys --with-colons $newconf{stdkey} >$newconf{tempdir}/subproc 2>&1");
2003-02-16 13:42:10 +00:00
bailout("bad stdkey spec \"$newconf{stdkey}\" -- exiting","$newconf{tempdir}/subproc")
if ($res);
}
2002-09-19 14:58:21 +00:00
}
elsif (!defined $newconf{stdkey})
2002-09-19 09:51:25 +00:00
{
if ($newconf{usepgp})
2002-09-19 09:51:25 +00:00
{
open(F,"$newconf{pgppath} -kv $home/.pgp/secring.pgp 2>$newconf{tempdir}/subproc |")
2003-02-16 13:42:10 +00:00
|| bailout("cant fork $newconf{pgppath} to list sec keys: $! -- exiting");
while (<F>)
{
if (/^sec\s+\d+\/(\S+)\s+/)
{
$newconf{stdkey}="0x$1";
$debug && logit("set stdkey to $newconf{stdkey}");
last;
}
}
close F;
2003-02-16 13:42:10 +00:00
bailout("error running $newconf{pgppath}: $? -- exiting","$newconf{tempdir}/subproc")
if ($?);
2002-09-19 09:51:25 +00:00
}
else
{
open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc|")
2003-02-16 13:42:10 +00:00
|| bailout("cant run $newconf{gpgpath} to list sec keys: $! -- exiting","$newconf{tempdir}/subproc");
while (<F>)
{
my @list=split(/:/);
next if ($list[0] ne "sec" || $list[3] ne "1");
$list[4] =~ s/^.{8}//; # truncate key-id
$newconf{stdkey}="0x$list[4]";
$debug && logit("set stdkey to $newconf{stdkey}");
last;
}
close F;
2003-02-16 13:42:10 +00:00
bailout("error running $newconf{gpgpath}: $? -- exiting","$newconf{tempdir}/subproc")
if ($?);
}
2003-02-16 13:42:10 +00:00
bailout("could not find stdkey -- exiting") if (!$newconf{stdkey});
}
# finally make sure that no action conflicts with the keys we may lack
2003-02-16 13:42:10 +00:00
bailout("no keys whatsoever a/v! -- exiting") if (!$newconf{stdkey} && !$newconf{ngkey});
2003-02-16 13:42:10 +00:00
bailout("config specifies ng but no ng key a/v -- exiting")
if (!$newconf{ngkey} && grep($_->{action} =~ /^ng/, @over));
2003-02-16 13:42:10 +00:00
bailout("config specifies std but no std key a/v -- exiting")
if (!$newconf{stdkey} && grep($_->{action} =~ /^std/, @over));
# everything seems ok, overwrite global vars config and override
%config=%newconf;
@overrides=@over;
return;
2001-11-06 12:53:15 +00:00
}
# traverses the entity and sets all parts with
# type == text/plain, charset != us-ascii, transfer-encoding 8bit
# to transfer-encoding qp.
# input: entity, retval: none
2001-11-06 12:53:15 +00:00
sub qp_fix_parts
{
my ($entity)=@_;
if ($entity->is_multipart)
{
foreach ($entity->parts)
{
&qp_fix_parts($_);
}
}
else
{
if ($entity->head->mime_type eq "text/plain"
&& $entity->head->mime_encoding eq "8bit"
&& lc($entity->head->mime_attr("content-type.charset"))
ne "us-ascii")
{
bailout("changing Content-Transfer-Encoding failed")
if ($entity->head->mime_attr("content-transfer-encoding"
=> "quoted-printable")!="quoted-printable");
2001-11-06 12:53:15 +00:00
}
}
}
# notifies the sender of a problem, via email
# retrieves the detailed error message from @detailederror
# no return value, exception on problems
2001-11-06 12:53:15 +00:00
sub send_bounce
{
my ($res,$file)=@_;
open(F,"|$config{mta} $username") ||
2003-02-16 13:42:10 +00:00
bailout("cant fork $config{mta}: $! -- exiting");
print F "From: $username\nTo: $username\nSubject: $progname Mail Sending Failure\n\n"
2003-04-25 07:52:15 +00:00
."Your mail $config{queuedir}/$file could not be sent to some or all recipients.\n\n"
."The error message was:\n\n$res\n\n\n";
print F "Detailed error message:\n\n"
.join("",@detailederror)."\n\n\n" if (@detailederror);
print F "$progname has no reliable way of figuring out whether this failure did affect\n"
."all recipients of your mail, so please look into the log for further error indications.\n\n"
."$progname has backed the failed mail up as $config{queuedir}/.$file;\n"
."If you wish to retry again for all original recipients, just rename the file back to\n"
."$config{queuedir}/$file or otherwise remove the backup file.\n";
2001-11-06 12:53:15 +00:00
close F;
2003-02-16 13:42:10 +00:00
bailout("error running $config{mta}: $? -- exiting") if ($?);
2001-11-06 12:53:15 +00:00
}
# get, verify and store a secret
# input: what kind of secret
# retval: none, changes %secrets, exception on major errors
# note: only used when secretondemand is unset.
sub get_secret
2001-11-06 12:53:15 +00:00
{
my ($type)=@_;
my $id=$config{($type eq "std"?"stdkey":"ngkey")};
my $res;
do
2001-11-06 12:53:15 +00:00
{
# do-it-yourself
# the previous attempt failed...
print "wrong passphrase, try again.\n"
if ($res);
print "enter secret for key $id:\n";
ReadMode("noecho");
chomp (my $phrase=<STDIN>);
ReadMode("restore");
bailout("error reading $type passphrase: $!")
if (!defined($phrase));
print "\n";
$secrets{$id}=$phrase;
$phrase="x" x 64;
$res=sign_encrypt(0,$type,undef,undef);
2001-11-06 12:53:15 +00:00
}
while ($res);
2001-11-06 12:53:15 +00:00
}
# sign/encrypt a file, or test the passphrase if infile and outfile are undef.
# input: encrypt, type std/ng, infile and outfile path, recipient keys if encrypt.
# returns: 0 if ok, 1 if bad passphrase, exception on other errors
sub sign_encrypt
2001-11-06 12:53:15 +00:00
{
my ($enc,$type,$infile,$outfile,@recips)=@_;
my ($passphrase,$passphrase_cmd,$cmd);
# passphrase issues
if ($config{secretondemand})
2001-11-06 12:53:15 +00:00
{
$cmd="|".sprintf($config{getsecret},
($type eq "std"?$config{stdkey}:$config{ngkey}));
2001-11-06 12:53:15 +00:00
}
else
{
$passphrase=$secrets{$config{($type eq "std"?"stdkey":"ngkey")}};
2001-11-06 12:53:15 +00:00
}
# how to arrange the command
if (!$enc)
2001-11-06 12:53:15 +00:00
{
if ($type eq "std" && $config{usepgp})
2001-11-06 12:53:15 +00:00
{
$cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode -u $config{stdkey} -sbat";
2001-11-06 12:53:15 +00:00
}
else
2001-11-06 12:53:15 +00:00
{
2003-01-15 22:57:54 +00:00
$cmd.="|$config{gpgpath} -q -t --batch --armor --detach-sign --passphrase-fd 0 --status-fd 1 --default-key";
if ($type eq "std")
2001-11-06 12:53:15 +00:00
{
$cmd.=" $config{stdkey} --rfc1991 --cipher-algo idea --digest-algo md5 --compress-algo 1";
2001-11-06 12:53:15 +00:00
}
else
{
$cmd.=" $config{ngkey}";
2001-11-06 12:53:15 +00:00
}
}
# only check the passphrase: pgp needs -f(ilter) flag then
if (!$infile && !$outfile)
2001-11-06 12:53:15 +00:00
{
$cmd.=" -f" if ($type eq "std" && $config{usepgp});
2001-11-06 12:53:15 +00:00
}
else
2001-11-06 12:53:15 +00:00
{
$cmd.=" -o $outfile $infile";
2001-11-06 12:53:15 +00:00
}
}
else # encrypt and sign
2001-11-06 12:53:15 +00:00
{
if ($type eq "std" && $config{usepgp})
2001-11-06 12:53:15 +00:00
{
$cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode "
."-u $config{stdkey} -seat -o $outfile $infile "
.join(" ",@recips);
2001-11-06 12:53:15 +00:00
}
2002-09-19 16:25:46 +00:00
else
{
# gpg: normal mode...
if ($type ne "std")
2002-09-19 16:25:46 +00:00
{
$cmd.="|$config{gpgpath} -q -t --batch --armor --passphrase-fd 0 "
."--status-fd 1 --default-key $config{ngkey} -r "
.join(" -r ",@recips)
.($config{alwaystrust}?" --always-trust":"")
." --encrypt --sign -o $outfile $infile";
2002-09-19 16:25:46 +00:00
}
else
2002-09-19 16:25:46 +00:00
{
# or compatibility-mode, bah
# very elaborate but working procedure, found by
# Gero Treuner <gero@faveve.uni-stuttgart.de>
# http://muppet.faveve.uni-stuttgart.de/~gero/gpg-2comp
# first, generate the signature and store it
$cmd.="|$config{gpgpath} --batch -q --detach-sign "
."--default-key $config{stdkey} "
."--passphrase-fd 0 --status-fd 1 -o $outfile $infile";
# the rest is done later on
2002-09-19 16:25:46 +00:00
}
}
2001-11-06 12:53:15 +00:00
}
$cmd.=" >$config{tempdir}/subproc 2>&1";
2001-11-06 12:53:15 +00:00
unlink($outfile) if (-e $outfile);
2001-11-06 12:53:15 +00:00
open(F,$cmd) || bailout("cannot open pipe $cmd: $!");
print F "$passphrase\n" if ($passphrase);
$passphrase="x" x 64;
close F;
# compatibility mode? there's more to do, unfortunately
return 0
if (!$? && !($enc && $type eq "std" && !$config{usepgp})) ;
if ($?)
2001-11-06 12:53:15 +00:00
{
# hmm, things went wrong: try to figure out what happened.
# if it's just the passphrase, return 1.
# if it's something else, bailout...won't get better with retries.
# pgp's way of saying "bad passphrase".
return 1 if ($type eq "std" && $config{usepgp} && ($?>>8) eq 20);
# with gpg we'll have to look at the output
if ($type eq "ng" || !$config{usepgp})
2001-11-06 12:53:15 +00:00
{
open F,"$config{tempdir}/subproc";
my @result=<F>;
close F;
return 1 if (grep(/^\[GNUPG:\] BAD_PASSPHRASE/,@result));
2001-11-06 12:53:15 +00:00
}
bailout("error running sign prog: $?","$config{tempdir}/subproc") if ($? == 0xff00);
bailout("sign prog died from signal " . ($? & 0x7f),"$config{tempdir}/subproc") if ($? <= 0x80);
bailout(("sign prog returned error ".($?>>8)),"$config{tempdir}/subproc") if ($?>>8);
2001-11-06 12:53:15 +00:00
}
# ok, must be in compat mode...let's complete the nasty construction
# next, convert the cleartext to the internal literal structure
unlink("$outfile.inter1") if (-e "$outfile.inter1");
my $res=0xffff
& system("$config{gpgpath} --batch -q --store --batch -z 0 -o $outfile.inter1 "
."$infile >$config{tempdir}/subproc 2>&1");
bailout("error running gpg","$config{tempdir}/subproc") if ($res);
# now compress signature and literal in the required order
open(F,"$outfile") || bailout("cant open $outfile: $!");
open(G,"$outfile.inter1") || bailout("cant open $outfile.inter1: $!");
unlink("$outfile.inter2") if (-e "$outfile.inter2");;
open(H,"|$config{gpgpath} --no-literal --store --batch --compress-algo 1 "
."-o $outfile.inter2 >$config{tempdir}/subproc 2>&1")
|| bailout("cant open pipe to $config{gpgpath}: $!");
print H <F>;
print H <G>;
close F;
close G;
close H;
bailout("error running $config{gpgpath}: $?","$config{tempdir}/subproc") if ($?);
# and finally encrypt all this for the wanted recipients.
unlink($outfile);
$cmd="$config{gpgpath} --no-literal --batch --encrypt --rfc1991 --cipher-algo idea "
.($config{alwaystrust}?"--always-trust ":"")
."--armor -o $outfile -r "
.join(" -r ",@recips)
." $outfile.inter2 >$config{tempdir}/subproc 2>&1";
$res=0xffff & system($cmd);
bailout("error running $config{gpgpath}: $res","$config{tempdir}/subproc") if ($res);
return 0;
}
# find the correct action for a given email address
# input: addresses and custom-header, bcc-addresses
# result: hash with address as key and action as value
# the fallback and -force options are expanded into atoms, ie.
# resulting actions are: ng, ngsign, std, stdsign, none,
# or bcc-{ng,std}.
# note: ng and std means encryption here, no check for keys necessary anymore
sub findaction
{
my ($custom,$allref,$bccref)=@_;
my(@affected,%actions,$addr);
# lookup addresses in configured overrides
foreach $addr (@{$allref},@{$bccref})
{
foreach (@overrides)
{
if ($addr =~ $_->{re})
{
$actions{$addr}=$_->{action};
$debug && logit("found directive: $addr -> $actions{$addr}");
last;
}
}
# custom set? then override the config except where action=none
if ($custom && $actions{$addr} ne "none")
{
$debug && logit("custom conf header: overrides $addr -> $custom");
$actions{$addr}=$custom;
next;
}
&bailout("internal error, no action found for $addr") if (!exists $actions{$addr});
}
# no -force options for bcc
foreach $addr (@{$bccref})
{
$actions{$addr}=~s/^(\S+)-force$/$1/;
}
# check the found actions: anyone with -force options?
# note: normal addresses only, bcc don't count here
foreach $addr (@{$allref})
{
next if ($actions{$addr} !~ /^(\S+)-force$/);
my $force=$1;
$debug && logit("found force directive: $addr -> $actions{$addr}");
# yuck, must find affected addresses: those with action=none
# have to be disregarded and unchanged.
@affected = grep($actions{$_} ne "none",@{$allref});
# (almost) unconditionally apply the simple force options:
# none,ngsign,stdsign; others need more logic
if ($force eq "std")
{
# downgrade to sign if not all keys a/v
$force="stdsign" if (grep(!exists $keys{std}->{$_}, @affected));
}
elsif ($force eq "ng")
{
$force="ngsign" if (grep(!exists $keys{ng}->{$_}, @affected));
}
elsif ($force eq "fallback")
{
# fallback-logic: ng-crypt or std-crypt, otherwise ngsign or stdsign
# -force: ng- or std-crypt for all, otherwise ngsign or stdsign
$force="ngsign"
if (grep(!exists $keys{ng}->{$_}
&& !exists $keys{std}->{$_}, @affected));
}
# apply forced action to the affected addresses
map { $actions{$_}=$force; } (@affected);
$debug && logit("final force directive: $force");
# the first force-option wins, naturally.
last;
}
# check the actions for fallback, ng or std and expand that
# also bail out if no suitable keys available!
foreach $addr (@{$allref},@{$bccref})
{
if ($actions{$addr} eq "fallback")
{
($config{ngkey} && $keys{ng}->{$addr} && ($actions{$addr}="ng"))
|| ($config{stdkey} && $keys{std}->{$addr} && ($actions{$addr}="std"))
|| ($config{ngkey} && ($actions{$addr}="ngsign"))
|| ($config{stdkey} && ($actions{$addr}="stdsign"))
|| &bailout("oooops. no keys available for fallback action for $addr");
}
elsif ($actions{$addr} =~ /^ng(sign)?$/)
{
bailout("no ng key available but ng action required for $addr")
if (!$config{ngkey});
$actions{$addr}="ngsign" if ($actions{$addr} eq "ng" && !$keys{ng}->{$addr});
}
elsif ($actions{$addr} =~ /^std(sign)?$/)
{
bailout("no std key available but std action required for $addr")
if (!$config{stdkey});
$actions{$addr}="stdsign" if ($actions{$addr} eq "std" && !$keys{std}->{$addr});
}
$debug && logit("final action: $addr -> $actions{$addr}");
}
# tag ng and std actions for bcc recipients:
# those must be handled separately (separate encryption step...)
foreach $addr (@{$bccref})
{
$actions{$addr}=~s/^(ng|std)$/bcc-$1/;
}
return %actions;
}
2002-09-19 16:25:46 +00:00
# logging and dying with a message
# does not return
2003-02-16 13:42:10 +00:00
# if barfmail is set, then a mail with the log information is sent (message and detailfn-content)
# args: the message to spit out, path to a file with details.
# the details from the file are logged only, not printed in the die-message
2002-09-19 16:25:46 +00:00
sub bailout
{
my ($msg,$detailfn)=@_;
2001-11-06 12:53:15 +00:00
2003-04-25 07:52:15 +00:00
if ($detailfn && open(DF,$detailfn))
{
push @detailederror,<DF>;
close DF;
}
2003-02-16 13:42:10 +00:00
if ($barfmail)
{
# i'd like to call bailout without looping.
my $oldbarfmail=$barfmail;
$barfmail=0;
my $mta=$config{mta}||"/usr/lib/sendmail"; # this could run before the config is read
open (F,"|$mta $username") ||
bailout("cant fork $mta: $!");
print F "From: $username\nTo: $username\nSubject: $progname General Failure\n\n"
."$progname has encountered a serious/fatal failure.\n\n"
2003-04-25 07:52:15 +00:00
."The error message was:\n\n$msg\n\n\n";
print F "Detailed error message:\n\n"
.join("",@detailederror)."\n\n\n" if (@detailederror);
2003-02-16 13:42:10 +00:00
close F;
bailout("error running $mta: $?") if ($?);
$barfmail=$oldbarfmail;
}
logit($msg,$detailfn);
die($msg."\n");
2002-09-19 16:25:46 +00:00
}
2003-02-16 13:42:10 +00:00
# log the msg(s) to syslog or the logfile
# the detailed info is put into @detailederror
# args: message, path to file with details
# no retval.
sub logit
{
my ($msg,$detailfn)=@_;
if ($detailfn)
{
@detailederror=();
if (open(DF,$detailfn))
{
push @detailederror,<DF>;
close DF;
}
}
if ($config{logfh})
{
# logfile is opened with autoflush set to 1,
# so no extra flushing needed
# we're more or less emulating the syslog format here...
print { $config{logfh} } scalar(localtime)." $progname\[$$\] $msg\n";
}
else
{
setlogsock('unix');
openlog($progname,"pid,cons","mail");
syslog("notice",$msg);
closelog;
}
}
2001-11-06 12:53:15 +00:00
&main;