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.
|
|
|
|
#
|
2005-11-04 06:21:20 +00:00
|
|
|
# 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.
|
|
|
|
#
|
2005-11-04 06:21:20 +00:00
|
|
|
# $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;
|
2003-01-12 14:05:48 +00:00
|
|
|
use Term::ReadKey;
|
2005-11-04 06:21:20 +00:00
|
|
|
use Proc::PID::File;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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";
|
2003-01-12 14:05:48 +00:00
|
|
|
my $progname="kuvert";
|
2001-11-06 12:53:15 +00:00
|
|
|
# who are we gonna pretend to be today?
|
2003-01-12 14:05:48 +00:00
|
|
|
my($username,$home)=(getpwuid($<))[0,7];
|
2001-11-06 12:53:15 +00:00
|
|
|
# where is the configuration file
|
2003-01-12 14:05:48 +00:00
|
|
|
my $rcfile="$home/.kuvert";
|
|
|
|
# configuration directives, keyring
|
|
|
|
my (%config,@overrides,%keys);
|
2005-11-04 06:21:20 +00:00
|
|
|
# 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);
|
2003-01-21 12:27:01 +00:00
|
|
|
my @detailederror=();
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2005-11-04 06:21:20 +00:00
|
|
|
my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp");
|
|
|
|
my $pidname="$progname.$<";
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
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-01-12 14:05:48 +00:00
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
die "usage: $progname [-n] [-d] [-v] [-b]| [-k] | [-r]
|
2003-01-12 14:05:48 +00:00
|
|
|
-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";
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
2005-11-04 06:21:20 +00:00
|
|
|
open(PIDF,"$piddir/$pidname.pid") || &bailout("cant open pidfile: $! -- exiting");
|
2003-01-12 14:05:48 +00:00
|
|
|
$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")
|
2003-01-12 14:05:48 +00:00
|
|
|
if (!kill $sig, $pid);
|
|
|
|
exit 0;
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-08-03 02:06:53 +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"
|
|
|
|
}
|
2002-09-19 09:13:13 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
logit("$progname version $version starting");
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# read the config, setup dirs, logging, defaultkeys etc.
|
|
|
|
&read_config;
|
2002-09-19 16:25:46 +00:00
|
|
|
|
2005-11-04 06:21:20 +00:00
|
|
|
# get the passphrase(s) if no external passphrase store is used
|
|
|
|
# this has to be done before a fork...
|
2003-01-12 14:05:48 +00:00
|
|
|
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
|
|
|
|
2003-01-12 14:05:48 +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")
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($res == -1);
|
|
|
|
exit 0 if ($res);
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2005-11-04 06:21:20 +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);
|
2003-01-12 14:05:48 +00:00
|
|
|
&read_keyrings;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +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")
|
2003-01-12 14:05:48 +00:00
|
|
|
if (!opendir(D,"$config{queuedir}"));
|
|
|
|
|
|
|
|
my $file;
|
|
|
|
foreach $file (grep(!/^\./,readdir(D)))
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +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!
|
2003-01-12 14:05:48 +00:00
|
|
|
eval { process_file(*FH,"$config{queuedir}/$file"); };
|
2003-04-25 07:52:15 +00:00
|
|
|
$barfmail=1 if ($options{"b"});
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($@)
|
|
|
|
{
|
2003-04-25 07:52:15 +00:00
|
|
|
chomp(my $error=$@);
|
2003-01-21 12:27:01 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
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);
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
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");
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
# and clean up the cruft left behind, please!
|
2003-01-12 15:21:03 +00:00
|
|
|
cleanup("$config{tempdir}",0);
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# unlock the file
|
2003-02-16 13:42:10 +00:00
|
|
|
bailout("problem unlocking $config{queuedir}/$file: $! -- exiting")
|
2003-01-12 14:05:48 +00:00
|
|
|
if (!flock(FH,LOCK_UN));
|
|
|
|
close(FH);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
closedir(D);
|
|
|
|
&handle_term("debug mode") if ($debug);
|
|
|
|
sleep($config{interval});
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# processes a file in the queue, does not remove stuff from the tempdir or the queue
|
2003-01-12 15:21:03 +00:00
|
|
|
# exception on errors
|
2001-11-06 12:53:15 +00:00
|
|
|
sub process_file
|
|
|
|
{
|
|
|
|
my ($fh,$file)=@_;
|
|
|
|
|
|
|
|
my $parser = new MIME::Parser;
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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);
|
2003-01-12 14:05:48 +00:00
|
|
|
# make the parser ignore all filename info and just invent filenames.
|
|
|
|
$parser->filer->ignore_filename(1);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
my $in_ent;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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
|
2003-01-12 14:05:48 +00:00
|
|
|
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/;
|
2002-04-25 14:31:58 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
{
|
2003-01-12 14:05:48 +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...
|
2003-01-12 14:05:48 +00:00
|
|
|
&send_entity($in_ent,"-t");
|
2001-11-06 12:53:15 +00:00
|
|
|
$in_ent->purge;
|
2003-01-12 14:05:48 +00:00
|
|
|
return;
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
my (@recip_all,@recip_bcc);
|
|
|
|
|
2002-04-25 14:31:58 +00:00
|
|
|
# get the recipients
|
2003-01-12 14:05:48 +00:00
|
|
|
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");
|
2002-01-02 06:39:34 +00:00
|
|
|
|
2001-11-10 04:55:38 +00:00
|
|
|
# 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...
|
2003-01-12 14:05:48 +00:00
|
|
|
bailout("no recipients found! the mail headers seem to be garbled.")
|
|
|
|
if (!@recip_all && !@recip_bcc);
|
2001-11-10 04:55:38 +00:00
|
|
|
|
2002-04-25 14:31:58 +00:00
|
|
|
# figure out what to do for specific recipients
|
2003-01-12 14:05:48 +00:00
|
|
|
my %actions=findaction($custom_conf,\@recip_all,\@recip_bcc);
|
2001-11-10 04:55:38 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
my $orig_header;
|
|
|
|
my $input="$config{tempdir}/.input";
|
2003-08-03 01:45:37 +00:00
|
|
|
|
|
|
|
# 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);
|
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# prepare various stuff we need only when encrypting or signing
|
|
|
|
if(grep(/(ng|std)/,values(%actions)))
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
{
|
2003-01-12 14:05:48 +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
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
qp_fix_parts($in_ent);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
bailout("cant open >$input: $!")
|
|
|
|
if (!open(F,">$input"));
|
|
|
|
$in_ent->print(\*F);
|
|
|
|
close(F);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-08-03 01:45:37 +00:00
|
|
|
foreach my $action qw(ng ngsign std stdsign bcc-ng bcc-std)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
my @recips=grep($actions{$_} eq $action,keys %actions);
|
2003-01-15 15:03:03 +00:00
|
|
|
next if (!@recips);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
my $type=($action=~/ng/?"ng":"std");
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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 $_");
|
2003-01-21 12:27:01 +00:00
|
|
|
&crypt_send($in_ent,$input,$type,$orig_header,[$keys{$type}->{$_}],$_);
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +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
|
2003-01-12 15:21:03 +00:00
|
|
|
# exception on errors
|
2001-11-06 12:53:15 +00:00
|
|
|
sub sign_send
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
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;
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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");
|
2003-01-12 14:05:48 +00:00
|
|
|
$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",
|
2003-01-12 14:05:48 +00:00
|
|
|
"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);
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# generate the signature, repeat until proper passphrase given
|
|
|
|
while (&sign_encrypt(0,$type,$dumpfile,$output,undef))
|
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
# get rid of broken passphrase and lets try again
|
2005-11-04 06:21:20 +00:00
|
|
|
if ($config{secretondemand})
|
2003-01-15 15:03:03 +00:00
|
|
|
{
|
|
|
|
$debug && logit("bad passphrase, retry");
|
2005-11-04 06:21:20 +00:00
|
|
|
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",
|
2003-01-15 15:03:03 +00:00
|
|
|
"$config{tempdir}/subproc")
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# bad passphrase but we're on our own -> cant recover
|
2005-11-04 06:21:20 +00:00
|
|
|
bailout("bad passphrase, but no passphrase store to query!");
|
2003-01-15 15:03:03 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
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",
|
2003-01-12 14:05:48 +00:00
|
|
|
Filename => undef,
|
2001-11-06 12:53:15 +00:00
|
|
|
Disposition => "inline",
|
|
|
|
Encoding => "7bit");
|
|
|
|
# and send the resulting thing, not cleaning up
|
2003-01-12 14:05:48 +00:00
|
|
|
&send_entity($newent,@recips);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
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;
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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",
|
2003-01-12 14:05:48 +00:00
|
|
|
"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");
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# generate the encrypted data, repeat until proper passphrase given
|
|
|
|
while (&sign_encrypt(1,$type,$dumpfile,$output,@{$rec_keys}))
|
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
# get rid of broken passphrase and lets try again
|
2005-11-04 06:21:20 +00:00
|
|
|
if ($config{secretondemand})
|
2003-01-15 15:03:03 +00:00
|
|
|
{
|
|
|
|
$debug && logit("bad passphrase, retry");
|
2005-11-04 06:21:20 +00:00
|
|
|
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",
|
2003-01-15 15:03:03 +00:00
|
|
|
"$config{tempdir}/subproc")
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# bad passphrase but we're on our own -> cant recover
|
2005-11-04 06:21:20 +00:00
|
|
|
bailout("bad passphrase, but no passphrase store to query!");
|
2003-01-15 15:03:03 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
# attach the encrypted data
|
|
|
|
$newent->attach(Type => "application/octet-stream",
|
2003-01-12 14:05:48 +00:00
|
|
|
Path => "$output",
|
2001-11-06 12:53:15 +00:00
|
|
|
Filename => undef,
|
|
|
|
Disposition => "inline",
|
|
|
|
Encoding=>"7bit");
|
|
|
|
|
|
|
|
# and send the resulting thing
|
2003-01-12 14:05:48 +00:00
|
|
|
&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
|
2003-01-12 15:21:03 +00:00
|
|
|
# exception on errors
|
2001-11-06 12:53:15 +00:00
|
|
|
sub send_entity
|
|
|
|
{
|
|
|
|
my ($ent,@args)=@_;
|
|
|
|
|
2005-02-25 22:09:21 +00:00
|
|
|
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.
|
2003-01-12 15:21:03 +00:00
|
|
|
# exception on error, no retval
|
2001-11-06 12:53:15 +00:00
|
|
|
sub cleanup
|
|
|
|
{
|
|
|
|
my ($what,$remove_what)=@_;
|
|
|
|
my ($name,$res);
|
|
|
|
|
2003-01-12 15:21:03 +00:00
|
|
|
opendir(F,$what) || bailout("cant opendir $what: $!");
|
2001-11-06 12:53:15 +00:00
|
|
|
foreach $name (readdir(F))
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
next if ($name =~ /^\.{1,2}$/o);
|
2003-01-12 15:21:03 +00:00
|
|
|
(-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);
|
2003-01-12 15:21:03 +00:00
|
|
|
$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");
|
2003-01-12 14:05:48 +00:00
|
|
|
my $res=&cleanup($config{tempdir},1);
|
|
|
|
logit("problem cleaning up $config{tempdir}: $res")
|
2001-11-06 12:53:15 +00:00
|
|
|
if ($res);
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# wipe keys
|
2005-11-04 06:21:20 +00:00
|
|
|
if ($config{secretondemand})
|
2003-01-12 14:05:48 +00:00
|
|
|
{
|
2005-11-04 06:21:20 +00:00
|
|
|
foreach ($config{ngkey},$config{stdkey})
|
2003-01-12 14:05:48 +00:00
|
|
|
{
|
2005-11-04 06:21:20 +00:00
|
|
|
next if (!$_);
|
|
|
|
my $cmd=sprintf($config{delsecret},$_);
|
|
|
|
my $res=0xffff & system $cmd;
|
|
|
|
&logit("problem deleting secret for $_: $res")
|
|
|
|
if ($res);
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
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');
|
2003-01-12 14:05:48 +00:00
|
|
|
%{$keys{std}}=();
|
2002-10-27 13:45:50 +00:00
|
|
|
|
2003-01-15 15:03:03 +00:00
|
|
|
if ($config{usepgp})
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
if (!$config{stdkey})
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +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
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
if (/^pub\s+\d+\/(\S+)\s+(.+)$/)
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +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
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
if (/^\s+.*(\s|<)([^\s<]+\@[^\s>]+)>?\s*$/)
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
%{$keys{ng}}=();
|
2002-10-27 13:45:50 +00:00
|
|
|
|
2003-01-15 15:03:03 +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
|
|
|
|
2003-01-15 15:03:03 +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
|
|
|
{
|
2003-01-15 15:03:03 +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
|
|
|
{
|
2003-01-21 12:27:01 +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]);
|
2003-01-15 15:03:03 +00:00
|
|
|
undef $lastkey;
|
|
|
|
next;
|
2002-10-27 13:45:50 +00:00
|
|
|
}
|
2003-01-15 15:03:03 +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
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
$name=lc($2);
|
2002-10-27 13:45:50 +00:00
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
else
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
undef $name;
|
2002-10-27 13:45:50 +00:00
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
|
|
|
|
# check the key: public part or uid?
|
|
|
|
if ($info[0] eq "pub")
|
2002-10-27 13:45:50 +00:00
|
|
|
{
|
2003-01-15 15:03:03 +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")
|
2003-01-15 15:03:03 +00:00
|
|
|
." key 0x$info[4], reason: "
|
|
|
|
.$badcauses{$info[1]});
|
2002-10-27 13:45:50 +00:00
|
|
|
next;
|
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
|
|
|
|
$keys{$lasttype}->{$name}="0x$lastkey";
|
|
|
|
|
|
|
|
&logit("got $lasttype key 0x$lastkey for $name")
|
2002-10-27 13:45:50 +00:00
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
&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);
|
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
logit("ignoring ng keyring, no key a/v.");
|
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# reads the configuration file, sets config variables
|
2003-01-12 15:21:03 +00:00
|
|
|
# exception on major problems
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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
|
|
|
{
|
2003-01-12 14:05:48 +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,
|
2005-11-04 06:21:20 +00:00
|
|
|
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")
|
2003-01-12 14:05:48 +00:00
|
|
|
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
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/)
|
2002-09-19 16:25:46 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +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
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/)
|
2002-09-19 16:25:46 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +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");
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
else
|
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
&bailout("bad config entry \"$_\" -- exiting");
|
2003-01-15 15:03:03 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
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")
|
2003-01-12 14:05:48 +00:00
|
|
|
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");
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
elsif ((stat($newconf{queuedir}))[2] & 0777 != 0700)
|
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
&bailout("$newconf{queuedir} does not have mode 0700 -- exiting");
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# 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
|
|
|
}
|
2002-09-19 16:43:25 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
elsif ((stat($newconf{tempdir}))[4] != $<)
|
2002-09-19 16:43:25 +00:00
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
&bailout("$newconf{tempdir} is not owned by you -- exiting");
|
2002-09-19 16:43:25 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
elsif ((stat($newconf{tempdir}))[2]&0777 != 0700)
|
2002-09-19 16:43:25 +00:00
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
&bailout("$newconf{tempdir} does not have mode 0700 -- exiting");
|
2002-09-19 16:43:25 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# close old logfile if there is one
|
|
|
|
close($config{logfile})
|
|
|
|
if ($config{logfile} && $config{logfile} ne $newconf{logfile});
|
2002-09-19 16:43:25 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{logfile})
|
2002-09-19 16:43:25 +00:00
|
|
|
{
|
2003-02-16 13:42:10 +00:00
|
|
|
&bailout("cant open logfile $newconf{logfile}: $! -- exiting")
|
2003-01-12 14:05:48 +00:00
|
|
|
if (!open($newconf{logfh},">>$newconf{logfile}"));
|
|
|
|
$newconf{logfh}->autoflush(1);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
2005-11-04 06:21:20 +00:00
|
|
|
# secret on demand is only possible with both a get and a del command
|
|
|
|
$newconf{secretondemand}=0
|
|
|
|
if (!$newconf{getsecret} || !$newconf{delsecret});
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# sanity checks: external executables
|
|
|
|
&bailout("bad executable '$newconf{mta}' -- exiting")
|
|
|
|
if ($newconf{mta}=~/^(\S+)/ && ! -x $1);
|
2005-11-04 06:21:20 +00:00
|
|
|
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);
|
|
|
|
}
|
2003-01-12 15:21:03 +00:00
|
|
|
&bailout("bad executable '$newconf{pgppath}' -- exiting")
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{usepgp} && $newconf{stdkey} ne "0"
|
2003-01-12 15:21:03 +00:00
|
|
|
&& (!$newconf{pgppath} || $newconf{pgppath}=~/^(\S+)/ && ! -x $1));
|
|
|
|
&bailout("bad executable '$newconf{gpgpath}' -- exiting")
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{ngkey} ne "0"
|
2003-01-12 15:21:03 +00:00
|
|
|
&& ( !$newconf{gpgpath} || $newconf{gpgpath}=~/^(\S+)/ && ! -x $1));
|
2003-01-12 14:05:48 +00:00
|
|
|
# figure out the default keys if none were supplied, check them
|
|
|
|
if ($newconf{ngkey})
|
2002-09-19 16:43:25 +00:00
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
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);
|
2002-09-19 16:43:25 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
elsif (!defined $newconf{ngkey})
|
2002-09-19 16:43:25 +00:00
|
|
|
{
|
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");
|
2003-01-12 14:05:48 +00:00
|
|
|
while (<F>)
|
|
|
|
{
|
|
|
|
my @list=split(/:/);
|
2003-01-15 15:03:03 +00:00
|
|
|
next if ($list[0] ne "sec" || $list[3] ne "17");
|
|
|
|
$list[4] =~ s/^.{8}//; # truncate key-id
|
2003-01-12 14:05:48 +00:00
|
|
|
$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 16:43:25 +00:00
|
|
|
}
|
2002-09-19 14:58:21 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{stdkey})
|
2002-09-19 14:58:21 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{usepgp})
|
|
|
|
{
|
2003-01-15 15:03:03 +00:00
|
|
|
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);
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
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")
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($res);
|
|
|
|
}
|
2002-09-19 14:58:21 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
elsif (!defined $newconf{stdkey})
|
2002-09-19 09:51:25 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($newconf{usepgp})
|
2002-09-19 09:51:25 +00:00
|
|
|
{
|
2003-01-12 15:21:03 +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");
|
2003-01-12 14:05:48 +00:00
|
|
|
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")
|
2003-01-15 15:03:03 +00:00
|
|
|
if ($?);
|
2002-09-19 09:51:25 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
else
|
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
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");
|
2003-01-12 14:05:48 +00:00
|
|
|
while (<F>)
|
|
|
|
{
|
|
|
|
my @list=split(/:/);
|
2003-01-15 15:03:03 +00:00
|
|
|
next if ($list[0] ne "sec" || $list[3] ne "1");
|
|
|
|
$list[4] =~ s/^.{8}//; # truncate key-id
|
2003-01-12 14:05:48 +00:00
|
|
|
$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")
|
2003-01-15 15:03:03 +00:00
|
|
|
if ($?);
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
2003-02-16 13:42:10 +00:00
|
|
|
bailout("could not find stdkey -- exiting") if (!$newconf{stdkey});
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# 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-01-15 15:03:03 +00:00
|
|
|
|
2003-02-16 13:42:10 +00:00
|
|
|
bailout("config specifies ng but no ng key a/v -- exiting")
|
2003-01-12 14:05:48 +00:00
|
|
|
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")
|
2003-01-12 14:05:48 +00:00
|
|
|
if (!$newconf{stdkey} && grep($_->{action} =~ /^std/, @over));
|
2003-01-15 15:03:03 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# 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.
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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")
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# notifies the sender of a problem, via email
|
2003-01-21 12:27:01 +00:00
|
|
|
# retrieves the detailed error message from @detailederror
|
2003-01-12 15:21:03 +00:00
|
|
|
# no return value, exception on problems
|
2001-11-06 12:53:15 +00:00
|
|
|
sub send_bounce
|
|
|
|
{
|
|
|
|
my ($res,$file)=@_;
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
open(F,"|$config{mta} $username") ||
|
2003-02-16 13:42:10 +00:00
|
|
|
bailout("cant fork $config{mta}: $! -- exiting");
|
2003-01-12 14:05:48 +00:00
|
|
|
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"
|
2003-01-12 14:05:48 +00:00
|
|
|
."$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
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# get, verify and store a secret
|
|
|
|
# input: what kind of secret
|
2003-01-12 15:21:03 +00:00
|
|
|
# retval: none, changes %secrets, exception on major errors
|
2003-01-12 14:05:48 +00:00
|
|
|
# note: only used when secretondemand is unset.
|
|
|
|
sub get_secret
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
my ($type)=@_;
|
|
|
|
my $id=$config{($type eq "std"?"stdkey":"ngkey")};
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
do
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2005-11-04 06:21:20 +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;
|
2003-01-12 14:05:48 +00:00
|
|
|
$res=sign_encrypt(0,$type,undef,undef);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
while ($res);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +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.
|
2003-01-12 15:21:03 +00:00
|
|
|
# returns: 0 if ok, 1 if bad passphrase, exception on other errors
|
2003-01-12 14:05:48 +00:00
|
|
|
sub sign_encrypt
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
my ($enc,$type,$infile,$outfile,@recips)=@_;
|
|
|
|
my ($passphrase,$passphrase_cmd,$cmd);
|
|
|
|
|
|
|
|
# passphrase issues
|
2005-11-04 06:21:20 +00:00
|
|
|
if ($config{secretondemand})
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2005-11-04 06:21:20 +00:00
|
|
|
$cmd="|".sprintf($config{getsecret},
|
|
|
|
($type eq "std"?$config{stdkey}:$config{ngkey}));
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
$passphrase=$secrets{$config{($type eq "std"?"stdkey":"ngkey")}};
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# how to arrange the command
|
|
|
|
if (!$enc)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($type eq "std" && $config{usepgp})
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
$cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode -u $config{stdkey} -sbat";
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +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";
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($type eq "std")
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$cmd.=" $config{stdkey} --rfc1991 --cipher-algo idea --digest-algo md5 --compress-algo 1";
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
else
|
2003-01-12 14:05:48 +00:00
|
|
|
{
|
|
|
|
$cmd.=" $config{ngkey}";
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# only check the passphrase: pgp needs -f(ilter) flag then
|
|
|
|
if (!$infile && !$outfile)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$cmd.=" -f" if ($type eq "std" && $config{usepgp});
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
else
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$cmd.=" -o $outfile $infile";
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
else # encrypt and sign
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($type eq "std" && $config{usepgp})
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
$cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode "
|
2003-01-15 15:03:03 +00:00
|
|
|
."-u $config{stdkey} -seat -o $outfile $infile "
|
2003-01-12 14:05:48 +00:00
|
|
|
.join(" ",@recips);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
else
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
# gpg: normal mode...
|
|
|
|
if ($type ne "std")
|
2002-09-19 16:25:46 +00:00
|
|
|
{
|
2003-01-12 15:21:03 +00:00
|
|
|
$cmd.="|$config{gpgpath} -q -t --batch --armor --passphrase-fd 0 "
|
2003-01-12 14:05:48 +00:00
|
|
|
."--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
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
else
|
2002-09-19 16:25:46 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +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
|
2003-01-12 15:21:03 +00:00
|
|
|
$cmd.="|$config{gpgpath} --batch -q --detach-sign "
|
2003-01-12 14:05:48 +00:00
|
|
|
."--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
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
$cmd.=" >$config{tempdir}/subproc 2>&1";
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-15 15:03:03 +00:00
|
|
|
unlink($outfile) if (-e $outfile);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +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
|
|
|
{
|
2003-01-12 14:05:48 +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
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
open F,"$config{tempdir}/subproc";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
2003-01-15 15:03:03 +00:00
|
|
|
|
|
|
|
return 1 if (grep(/^\[GNUPG:\] BAD_PASSPHRASE/,@result));
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
bailout("error running sign prog: $?","$config{tempdir}/subproc") if ($? == 0xff00);
|
|
|
|
bailout("sign prog died from signal " . ($? & 0x7f),"$config{tempdir}/subproc") if ($? <= 0x80);
|
2003-01-21 12:27:01 +00:00
|
|
|
bailout(("sign prog returned error ".($?>>8)),"$config{tempdir}/subproc") if ($?>>8);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# ok, must be in compat mode...let's complete the nasty construction
|
|
|
|
|
|
|
|
# next, convert the cleartext to the internal literal structure
|
2003-01-15 15:03:03 +00:00
|
|
|
unlink("$outfile.inter1") if (-e "$outfile.inter1");
|
2003-01-12 14:05:48 +00:00
|
|
|
my $res=0xffff
|
2003-01-15 15:03:03 +00:00
|
|
|
& system("$config{gpgpath} --batch -q --store --batch -z 0 -o $outfile.inter1 "
|
2003-01-12 14:05:48 +00:00
|
|
|
."$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: $!");
|
|
|
|
|
2003-01-15 15:03:03 +00:00
|
|
|
unlink("$outfile.inter2") if (-e "$outfile.inter2");;
|
|
|
|
open(H,"|$config{gpgpath} --no-literal --store --batch --compress-algo 1 "
|
2003-01-12 14:05:48 +00:00
|
|
|
."-o $outfile.inter2 >$config{tempdir}/subproc 2>&1")
|
2003-01-12 15:21:03 +00:00
|
|
|
|| bailout("cant open pipe to $config{gpgpath}: $!");
|
2003-01-12 14:05:48 +00:00
|
|
|
print H <F>;
|
|
|
|
print H <G>;
|
|
|
|
close F;
|
|
|
|
close G;
|
|
|
|
close H;
|
2003-01-15 15:03:03 +00:00
|
|
|
bailout("error running $config{gpgpath}: $?","$config{tempdir}/subproc") if ($?);
|
2003-01-12 14:05:48 +00:00
|
|
|
|
|
|
|
# and finally encrypt all this for the wanted recipients.
|
2003-01-15 15:03:03 +00:00
|
|
|
unlink($outfile);
|
|
|
|
$cmd="$config{gpgpath} --no-literal --batch --encrypt --rfc1991 --cipher-algo idea "
|
2003-01-12 14:05:48 +00:00
|
|
|
.($config{alwaystrust}?"--always-trust ":"")
|
|
|
|
."--armor -o $outfile -r "
|
|
|
|
.join(" -r ",@recips)
|
|
|
|
." $outfile.inter2 >$config{tempdir}/subproc 2>&1";
|
|
|
|
|
|
|
|
$res=0xffff & system($cmd);
|
2003-01-15 15:03:03 +00:00
|
|
|
bailout("error running $config{gpgpath}: $res","$config{tempdir}/subproc") if ($res);
|
2003-01-12 14:05:48 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2002-04-25 14:31:58 +00:00
|
|
|
|
|
|
|
# find the correct action for a given email address
|
2003-01-12 14:05:48 +00:00
|
|
|
# input: addresses and custom-header, bcc-addresses
|
2002-04-25 14:31:58 +00:00
|
|
|
# result: hash with address as key and action as value
|
|
|
|
# the fallback and -force options are expanded into atoms, ie.
|
2003-01-12 14:05:48 +00:00
|
|
|
# resulting actions are: ng, ngsign, std, stdsign, none,
|
|
|
|
# or bcc-{ng,std}.
|
2002-04-25 14:31:58 +00:00
|
|
|
# note: ng and std means encryption here, no check for keys necessary anymore
|
|
|
|
sub findaction
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
my ($custom,$allref,$bccref)=@_;
|
|
|
|
my(@affected,%actions,$addr);
|
2002-04-25 14:31:58 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# lookup addresses in configured overrides
|
|
|
|
foreach $addr (@{$allref},@{$bccref})
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
foreach (@overrides)
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
if ($addr =~ $_->{re})
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$actions{$addr}=$_->{action};
|
|
|
|
$debug && logit("found directive: $addr -> $actions{$addr}");
|
2002-04-25 14:31:58 +00:00
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# custom set? then override the config except where action=none
|
|
|
|
if ($custom && $actions{$addr} ne "none")
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$debug && logit("custom conf header: overrides $addr -> $custom");
|
2002-04-25 14:31:58 +00:00
|
|
|
$actions{$addr}=$custom;
|
|
|
|
next;
|
|
|
|
}
|
2003-01-15 15:03:03 +00:00
|
|
|
&bailout("internal error, no action found for $addr") if (!exists $actions{$addr});
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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})
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
|
|
|
next if ($actions{$addr} !~ /^(\S+)-force$/);
|
|
|
|
my $force=$1;
|
2003-01-12 14:05:48 +00:00
|
|
|
$debug && logit("found force directive: $addr -> $actions{$addr}");
|
2002-04-25 14:31:58 +00:00
|
|
|
|
|
|
|
# yuck, must find affected addresses: those with action=none
|
|
|
|
# have to be disregarded and unchanged.
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
@affected = grep($actions{$_} ne "none",@{$allref});
|
2002-04-25 14:31:58 +00:00
|
|
|
|
|
|
|
# (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
|
2003-01-12 14:05:48 +00:00
|
|
|
$force="stdsign" if (grep(!exists $keys{std}->{$_}, @affected));
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
elsif ($force eq "ng")
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
$force="ngsign" if (grep(!exists $keys{ng}->{$_}, @affected));
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
elsif ($force eq "fallback")
|
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
# fallback-logic: ng-crypt or std-crypt, otherwise ngsign or stdsign
|
|
|
|
# -force: ng- or std-crypt for all, otherwise ngsign or stdsign
|
2002-04-25 14:31:58 +00:00
|
|
|
$force="ngsign"
|
2003-01-12 14:05:48 +00:00
|
|
|
if (grep(!exists $keys{ng}->{$_}
|
|
|
|
&& !exists $keys{std}->{$_}, @affected));
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# apply forced action to the affected addresses
|
|
|
|
map { $actions{$_}=$force; } (@affected);
|
2003-01-12 14:05:48 +00:00
|
|
|
$debug && logit("final force directive: $force");
|
2002-04-25 14:31:58 +00:00
|
|
|
# the first force-option wins, naturally.
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# check the actions for fallback, ng or std and expand that
|
2003-01-21 12:27:01 +00:00
|
|
|
# also bail out if no suitable keys available!
|
2003-01-12 14:05:48 +00:00
|
|
|
foreach $addr (@{$allref},@{$bccref})
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
|
|
|
if ($actions{$addr} eq "fallback")
|
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
($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");
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
2003-01-21 12:27:01 +00:00
|
|
|
elsif ($actions{$addr} =~ /^ng(sign)?$/)
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
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});
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
2003-01-21 12:27:01 +00:00
|
|
|
elsif ($actions{$addr} =~ /^std(sign)?$/)
|
2002-04-25 14:31:58 +00:00
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
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});
|
|
|
|
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
$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/;
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
return %actions;
|
|
|
|
}
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
# logging and dying with a message
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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)
|
2003-01-12 14:05:48 +00:00
|
|
|
# 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
|
|
|
|
{
|
2003-01-12 14:05:48 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
logit($msg,$detailfn);
|
2003-01-12 15:21:03 +00:00
|
|
|
die($msg."\n");
|
2002-09-19 16:25:46 +00:00
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
|
2003-02-16 13:42:10 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
# log the msg(s) to syslog or the logfile
|
2003-01-21 12:27:01 +00:00
|
|
|
# the detailed info is put into @detailederror
|
2003-01-12 14:05:48 +00:00
|
|
|
# args: message, path to file with details
|
|
|
|
# no retval.
|
|
|
|
sub logit
|
|
|
|
{
|
|
|
|
my ($msg,$detailfn)=@_;
|
|
|
|
|
|
|
|
if ($detailfn)
|
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
@detailederror=();
|
2003-01-12 14:05:48 +00:00
|
|
|
if (open(DF,$detailfn))
|
|
|
|
{
|
2003-01-21 12:27:01 +00:00
|
|
|
push @detailederror,<DF>;
|
2003-01-15 15:03:03 +00:00
|
|
|
close DF;
|
|
|
|
}
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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...
|
2003-01-21 12:27:01 +00:00
|
|
|
print { $config{logfh} } scalar(localtime)." $progname\[$$\] $msg\n";
|
2003-01-12 14:05:48 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
setlogsock('unix');
|
|
|
|
openlog($progname,"pid,cons","mail");
|
2003-01-21 12:27:01 +00:00
|
|
|
syslog("notice",$msg);
|
2003-01-12 14:05:48 +00:00
|
|
|
closelog;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2003-01-12 14:05:48 +00:00
|
|
|
&main;
|