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-2001 Alexander Zangerl <az@snafu.priv.at>
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
2002-09-19 16:25:46 +00:00
|
|
|
# $Id: kuvert,v 1.23 2002/09/19 14:58: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;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
my %options;
|
2002-01-30 14:23:21 +00:00
|
|
|
if (!getopts("dkrnv",\%options) || @ARGV)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2002-01-30 14:23:21 +00:00
|
|
|
print "usage: $0 [-n] [-d] [-v] | [-k] | [-r] \n-k: kill running $0\n"
|
|
|
|
."-d: debug mode\n-r: reload keyrings and configfile\n-n don't fork\n-v: output version and exit\n";
|
2001-11-06 12:53:15 +00:00
|
|
|
exit 1;
|
|
|
|
}
|
|
|
|
|
2002-02-16 12:02:54 +00:00
|
|
|
# the version number is inserted by make install
|
|
|
|
my $version="INSERT_VERSION";
|
2002-01-30 14:23:21 +00:00
|
|
|
|
|
|
|
if ($options{'v'})
|
|
|
|
{
|
|
|
|
print STDERR "kuvert $version\n";
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
# who are we gonna pretend to be today?
|
|
|
|
my($name,$home)=(getpwuid($<))[0,7];
|
|
|
|
|
|
|
|
# where is our in-queue
|
|
|
|
my $queuedir="$home/.kuvert_queue";
|
|
|
|
|
|
|
|
# which mta to use
|
2001-11-11 10:28:53 +00:00
|
|
|
my $mta="/usr/lib/sendmail -om -oi -oem";
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# where to put temp files for parsing mime
|
2002-09-19 09:13:13 +00:00
|
|
|
my $tempdir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$<.$$";
|
2001-11-06 12:53:15 +00:00
|
|
|
# where to put pgp/gpg in- and output
|
|
|
|
my $tempfile_in="input.tmp";
|
|
|
|
my $tempfile_out="output.tmp";
|
|
|
|
|
|
|
|
# interval to check the queue
|
|
|
|
my $interval=60; # seconds
|
|
|
|
|
|
|
|
# where is the configuration file
|
|
|
|
my $config="$home/.kuvert";
|
|
|
|
|
|
|
|
# list of addresses and -regexps to be handles specially
|
|
|
|
my %config=();
|
|
|
|
my @configkeys=();
|
|
|
|
|
|
|
|
# adresses and keyids
|
|
|
|
my (%ngkeys,%stdkeys);
|
|
|
|
|
|
|
|
# the name of program for logging
|
2001-11-11 10:28:53 +00:00
|
|
|
my $progname="kuvert";
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# where to put the pid of the running process
|
|
|
|
my $pidf="/tmp/kuvert.pid.$<";
|
|
|
|
|
|
|
|
# header to check for bounce request
|
|
|
|
# bounces are not signed or encrypted but simply passed to $mta
|
|
|
|
my $resend_indicator="resent-to";
|
|
|
|
|
|
|
|
# with this header one can override the configuration options wrt.
|
|
|
|
# signing for all recipients of the current mail
|
|
|
|
my $conf_header="x-kuvert";
|
|
|
|
|
|
|
|
# pgp path
|
|
|
|
my $PGP='/usr/bin/pgp';
|
|
|
|
# gpg path
|
|
|
|
my $GPG='/usr/bin/gpg';
|
2002-09-19 09:51:25 +00:00
|
|
|
# cat, needed if !use_pgp
|
2001-11-06 12:53:15 +00:00
|
|
|
my $CAT="/bin/cat";
|
|
|
|
# quintuple-client path
|
|
|
|
my $client;
|
|
|
|
# quintuple-agent path and args
|
|
|
|
my $agent;
|
|
|
|
|
|
|
|
# the passphrases are stored here if agent support is switched off
|
|
|
|
my %secrets=();
|
|
|
|
|
|
|
|
# 0 if gpg should try to mimickry as pgp2
|
|
|
|
# 0 means, that both keys are assumed to reside in one keyring
|
|
|
|
my $use_pgp=0;
|
|
|
|
|
|
|
|
# set this to 1 if this module should store the secrets with
|
|
|
|
# secret-agent rather than storing them itself.
|
|
|
|
my $use_agent=0;
|
|
|
|
# whether we need a separate agent-process
|
|
|
|
my $private_agent=0;
|
|
|
|
|
|
|
|
# if use_agent:
|
|
|
|
# set this to 0 if the secret should be loaded on demand by
|
|
|
|
# client if possible: this demand asking works only if
|
|
|
|
# $DISPLAY is set, so this option is ignored if no $DISPLAY is a/v
|
|
|
|
# if not set, the secret is asked & stored when kuvert starts.
|
|
|
|
my $secret_on_demand=0;
|
|
|
|
|
|
|
|
# add --always-trust to the gpg-parameters: this makes gpg
|
|
|
|
# encrypt to non fully trusted keys, too.
|
|
|
|
my $alwaystrust=0;
|
|
|
|
|
|
|
|
# set this to 1 for more verbose debugging output to syslog
|
|
|
|
my $debug=0;
|
|
|
|
|
|
|
|
# default keyid(s) for std and ng
|
|
|
|
# not really needed if you run separate keyrings, but if you
|
|
|
|
# want to run only gpg (in normal and "compat" mode),
|
|
|
|
# you've got to specify your default key because you've got more than
|
|
|
|
# one secret key in your secret keyring...
|
|
|
|
my ($ng_defkey,$std_defkey);
|
|
|
|
|
2001-11-11 11:41:05 +00:00
|
|
|
# usually this program logs to syslog, but it can log to a file as well
|
|
|
|
my ($lf,$logfile);
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
$debug=1 if ($options{"d"});
|
|
|
|
|
|
|
|
# kill a already running process
|
|
|
|
# TERM for kill or HUP for rereading
|
|
|
|
if ($options{"k"} || $options{"r"})
|
|
|
|
{
|
|
|
|
my $pid;
|
2001-11-25 11:39:53 +00:00
|
|
|
my $sig=($options{"r"}?'USR1':'TERM');
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
open(PIDF,"$pidf") || &bailout("cant open $pidf: $!");
|
2001-11-06 12:53:15 +00:00
|
|
|
$pid=<PIDF>;
|
|
|
|
close(PIDF);
|
|
|
|
chomp $pid;
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("no valid pid found, cant kill any process.")
|
2001-11-06 12:53:15 +00:00
|
|
|
if (!$pid);
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant kill -$sig $pid: $!")
|
|
|
|
if (!kill $sig, $pid);
|
2001-11-06 12:53:15 +00:00
|
|
|
unlink $pidf if ($options{"k"});
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("no configuration file, can't start!")
|
|
|
|
if (! -r $config);
|
2002-09-19 09:13:13 +00:00
|
|
|
|
2002-01-30 14:23:21 +00:00
|
|
|
logit("version $version starting");
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
# and now for some real work...
|
|
|
|
if (-f "$pidf") # retain content of pidf, in case we cant lock it
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
open(PIDF,"+<$pidf") || &bailout("cant open <+$pidf: $!");
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
open(PIDF,">$pidf") || &bailout("cant open >$pidf: $!");
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant lock $pidf ($!), another process running?, exiting")
|
|
|
|
if (!flock(PIDF,LOCK_NB|LOCK_EX));
|
|
|
|
|
|
|
|
# read the config, setup the queuedir and tempdir
|
|
|
|
&read_config;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# cleanup tempdir
|
|
|
|
my $res;
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant clean $tempdir: $res")
|
|
|
|
if ($res=cleanup($tempdir,0));
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# get the passphrase(s) and setup secret-agent if wanted
|
|
|
|
# this has to be done before any fork, because the environment
|
|
|
|
# vars for secret-agent must be retained
|
|
|
|
$res=&get_verify_secrets;
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("secrets could not be initialized properly: $res") if ($res);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2001-11-25 11:39:53 +00:00
|
|
|
if (!$options{"d"} && !$options{"n"})
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
|
|
|
my $res=fork;
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("fork failed: $!")
|
2001-11-06 12:53:15 +00:00
|
|
|
if ($res == -1);
|
|
|
|
exit 0
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
|
|
|
|
# the lockfile is ours, lets write the current pid
|
|
|
|
print PIDF "$$\n";
|
|
|
|
PIDF->flush;
|
|
|
|
truncate PIDF,tell(PIDF); # and make sure there's nothing else in there...
|
2002-09-19 16:25:46 +00:00
|
|
|
# now read the keyrings
|
|
|
|
&read_keyrings;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2001-11-25 11:39:53 +00:00
|
|
|
# install the handler for conf reread
|
|
|
|
$SIG{'USR1'}=\&handle_reload;
|
2001-11-06 12:53:15 +00:00
|
|
|
# and the termination-handler
|
2001-11-25 11:39:53 +00:00
|
|
|
$SIG{'HUP'}=\&handle_term;
|
2001-11-06 12:53:15 +00:00
|
|
|
$SIG{'INT'}=\&handle_term;
|
|
|
|
$SIG{'QUIT'}=\&handle_term;
|
|
|
|
$SIG{'TERM'}=\&handle_term;
|
|
|
|
|
|
|
|
# the main loop, left only via signal handler handle_term
|
|
|
|
while (1)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant open $queuedir: $!")
|
|
|
|
if (!opendir(D,"$queuedir"));
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
my $file;
|
|
|
|
foreach $file (readdir(D))
|
|
|
|
{
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
# dont try to handle any files starting with "."
|
|
|
|
next if ($file =~ /^\./);
|
|
|
|
# open the file
|
|
|
|
next if (!open(FH,"$queuedir/$file"));
|
|
|
|
# lock it if possible
|
|
|
|
if (!flock(FH,LOCK_NB|LOCK_EX))
|
|
|
|
{
|
|
|
|
close(FH);
|
|
|
|
logit("$file is locked, skipping.");
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ok, open & locked, let's proceed
|
|
|
|
logit("processing $file for $name");
|
|
|
|
$res=process_file(*FH,"$queuedir/$file");
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
send_bounce($res,$file);
|
|
|
|
logit("problem \"$res\" processing $file,"
|
|
|
|
." leaving as \".$file\".\n");
|
|
|
|
$res=rename("$queuedir/$file","$queuedir/.$file");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
logit("done with file $file");
|
|
|
|
$res=unlink("$queuedir/$file");
|
|
|
|
logit("problem removing $queuedir/$file: $!")
|
|
|
|
if (!$res);
|
|
|
|
}
|
|
|
|
|
|
|
|
# and clean up the cruft left behind, please!
|
|
|
|
$res=&cleanup("$tempdir",0);
|
|
|
|
logit("problem cleaning $tempdir: $res")
|
|
|
|
if ($res);
|
|
|
|
|
|
|
|
# unlock the file
|
|
|
|
logit("problem unlocking $queuedir/$file: $!")
|
|
|
|
if (!flock(FH,LOCK_UN));
|
|
|
|
close(FH);
|
|
|
|
}
|
|
|
|
closedir(D);
|
2001-11-25 11:39:53 +00:00
|
|
|
&handle_term("debug mode") if ($options{"d"});
|
2001-11-06 12:53:15 +00:00
|
|
|
sleep($interval);
|
|
|
|
}
|
|
|
|
|
|
|
|
# returns 0 if ok
|
|
|
|
# stuff in the temp directory is removed by the main loop
|
|
|
|
sub process_file
|
|
|
|
{
|
|
|
|
my ($fh,$file)=@_;
|
|
|
|
my ($res);
|
|
|
|
|
|
|
|
my @sent;
|
|
|
|
|
|
|
|
my $parser = new MIME::Parser;
|
|
|
|
|
|
|
|
# set output to tempdir
|
|
|
|
$parser->output_dir($tempdir);
|
|
|
|
# everything less than 100k goes to core mem
|
|
|
|
$parser->output_to_core(100000);
|
|
|
|
# retain rfc1522-encoded headers, please
|
|
|
|
$parser->decode_headers(0);
|
|
|
|
|
|
|
|
my $in_ent = $parser->read(\$fh);
|
|
|
|
|
|
|
|
if (!$in_ent)
|
|
|
|
{
|
|
|
|
logit("could not parse MIME stream, last header was "
|
|
|
|
.$parser->last_head);
|
|
|
|
return ("mail was not sent anywhere: could not parse MIME stream, "
|
|
|
|
."last header was ".$parser->last_head);
|
|
|
|
}
|
|
|
|
|
|
|
|
# extract and delete instruction header
|
2002-04-25 14:31:58 +00:00
|
|
|
my $custom_conf=lc($in_ent->head->get($conf_header));
|
2001-11-06 12:53:15 +00:00
|
|
|
$in_ent->head->delete($conf_header);
|
|
|
|
|
|
|
|
# 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
|
|
|
# extract a possible resend-request-header
|
|
|
|
# if a/v, call $mta immediately
|
|
|
|
|
|
|
|
if ($custom_conf eq "none" || $in_ent->head->get($resend_indicator))
|
|
|
|
{
|
|
|
|
if ($custom_conf eq "none" )
|
|
|
|
{
|
|
|
|
logit("all sign/encrypt disabled for this mail, calling $mta -t");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
logit("resending mail, sign/encrypt disabled, calling $mta -t");
|
|
|
|
}
|
|
|
|
# we do not send the original file here because this file possibly
|
|
|
|
# holds the instruction header...
|
|
|
|
$res=&send_entity($in_ent,"-t");
|
|
|
|
$in_ent->purge;
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
return "mail was not sent to anybody: $res";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my (@recip_none,@recip_sign_std,@recip_sign_ng,
|
2002-01-02 06:39:34 +00:00
|
|
|
@recip_crypt_std,@recip_crypt_ng,@recip_all);
|
2002-04-25 14:31:58 +00:00
|
|
|
# get the recipients
|
|
|
|
# note: bcc handling is not implemented.
|
|
|
|
map { push @recip_all, lc($_->address); } Mail::Address->parse($in_ent->head->get("To"),
|
2002-01-02 06:39:34 +00:00
|
|
|
$in_ent->head->get("Cc"));
|
|
|
|
|
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...
|
2002-04-25 14:31:58 +00:00
|
|
|
if (!@recip_all)
|
2001-11-10 04:55:38 +00:00
|
|
|
{
|
2002-01-02 06:39:34 +00:00
|
|
|
return "no recipients found! the mail headers seem to be garbled.";
|
2001-11-10 04:55:38 +00:00
|
|
|
}
|
|
|
|
|
2002-04-25 14:31:58 +00:00
|
|
|
# figure out what to do for specific recipients
|
|
|
|
my %actions=findaction($custom_conf,@recip_all);
|
|
|
|
|
|
|
|
# translate that into arrays
|
|
|
|
@recip_none=grep($actions{$_} eq "none",keys %actions);
|
|
|
|
@recip_sign_std=grep($actions{$_} eq "stdsign",keys %actions);
|
|
|
|
@recip_sign_ng=grep($actions{$_} eq "ngsign",keys %actions);
|
|
|
|
@recip_crypt_std=grep($actions{$_} eq "std",keys %actions);
|
|
|
|
@recip_crypt_ng=grep($actions{$_} eq "ng",keys %actions);
|
|
|
|
|
2001-11-10 04:55:38 +00:00
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
# if there are recipients in recip_none, send the message to them
|
|
|
|
# without any further action
|
|
|
|
if (@recip_none)
|
|
|
|
{
|
|
|
|
logit("sending mail (raw) to ".join(",",@recip_none));
|
|
|
|
$res=&send_entity($in_ent,join(" ",@recip_none));
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
$in_ent->purge; # only if the sending went wrong
|
|
|
|
return ("mail was not sent to anybody: $res");
|
|
|
|
}
|
|
|
|
push @sent,@recip_none;
|
|
|
|
}
|
|
|
|
|
2001-11-10 04:55:38 +00:00
|
|
|
# shortcut if just recipients without crypt/sign
|
|
|
|
# and no other recipients are given
|
2001-11-06 12:53:15 +00:00
|
|
|
return 0
|
|
|
|
if (!@recip_sign_std && !@recip_sign_ng
|
|
|
|
&& !@recip_crypt_std && !@recip_crypt_ng);
|
|
|
|
|
|
|
|
# copy (mail)header, split header info
|
|
|
|
# in mime-related (remains with the entity) and non-mime
|
|
|
|
# (is saved in the new header-object)
|
|
|
|
my $orig_header=$in_ent->head->dup;
|
|
|
|
my $headername;
|
|
|
|
|
|
|
|
# content-* stays with the entity and the rest moves to orig_header
|
|
|
|
foreach $headername ($in_ent->head->tags)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
qp_fix_parts($in_ent);
|
|
|
|
|
|
|
|
# now we've got a $in_entity which is ready to be encrypted/signed
|
|
|
|
# and the mail-headers are saved in $orig_header
|
|
|
|
|
|
|
|
# since the 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 $tempfile_in
|
|
|
|
# which is then fed through the relevant signing/encryption and sent on.
|
|
|
|
|
|
|
|
if (!open(F,">$tempdir/$tempfile_in"))
|
|
|
|
{
|
|
|
|
logit("cant open >$tempdir/$tempfile_in: $!");
|
|
|
|
return ("mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to anybody else: ".
|
|
|
|
"cant open >$tempdir/$tempfile_in: $!");
|
|
|
|
}
|
|
|
|
$in_ent->print(\*F);
|
|
|
|
close(F);
|
|
|
|
|
|
|
|
if (@recip_sign_std)
|
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
return ("no std key known, can't sign! mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to anybody else")
|
|
|
|
if (!$std_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
logit("sending mail (sign,std) to ".join(",",@recip_sign_std));
|
|
|
|
$res=sign_send($in_ent,"$tempdir/$tempfile_in",\@recip_sign_std,
|
|
|
|
\&std_sign,
|
|
|
|
"md5",$orig_header,"std");
|
|
|
|
return ("mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to ".join(",",@recip_sign_std).": $res")
|
|
|
|
if ($res);
|
|
|
|
push @sent,@recip_sign_std;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (@recip_sign_ng)
|
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
return ("no ng key known, can't sign! mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to anybody else")
|
|
|
|
if (!$ng_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
logit("sending mail (sign,ng) to ".join(",",@recip_sign_ng));
|
|
|
|
$res=sign_send($in_ent,"$tempdir/$tempfile_in",\@recip_sign_ng,
|
|
|
|
\&ng_sign,
|
|
|
|
"sha1",$orig_header,"ng");
|
|
|
|
return ("mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to ".join(",",@recip_sign_ng).": $res")
|
|
|
|
if ($res);
|
|
|
|
push @sent,@recip_sign_ng;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (@recip_crypt_std)
|
|
|
|
{
|
|
|
|
my @keys;
|
|
|
|
|
2001-11-11 10:28:53 +00:00
|
|
|
return ("no std key known, can't encrypt! mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to anybody else")
|
|
|
|
if (!$std_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
logit("sending mail (crypt,std) to ".join(",",@recip_crypt_std));
|
|
|
|
map { push @keys,$stdkeys{$_}; } @recip_crypt_std;
|
|
|
|
$res=crypt_send($in_ent,"$tempdir/$tempfile_in",\@recip_crypt_std,
|
|
|
|
\@keys,\&std_crypt,
|
|
|
|
$orig_header);
|
|
|
|
return ("mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to ".join(",",@recip_crypt_std).": $res")
|
|
|
|
if ($res);
|
|
|
|
push @sent,@recip_crypt_std;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (@recip_crypt_ng)
|
|
|
|
{
|
|
|
|
my @keys;
|
|
|
|
|
2001-11-11 10:28:53 +00:00
|
|
|
return ("no ng key known, can't encrypt! mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to anybody else")
|
|
|
|
if (!$ng_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
logit("sending mail (crypt,ng) to ".join(",",@recip_crypt_ng));
|
|
|
|
map { push @keys,$ngkeys{$_}; } @recip_crypt_ng;
|
|
|
|
$res=crypt_send($in_ent,"$tempdir/$tempfile_in",\@recip_crypt_ng,
|
|
|
|
\@keys,\&ng_crypt,$orig_header);
|
|
|
|
return ("mail was sent to ".
|
|
|
|
(@sent?join(",",@sent):"nobody")
|
|
|
|
.",\nnot to ".join(",",@recip_crypt_ng).": $res")
|
|
|
|
if ($res);
|
|
|
|
push @sent,@recip_crypt_ng;
|
|
|
|
}
|
|
|
|
|
|
|
|
# done, return
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# return 0 if ok, errortext otherwise
|
|
|
|
sub sign_send
|
|
|
|
{
|
|
|
|
my ($ent,$ent_file,$rec,$cmd,$micalg,$header,$type)=@_;
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
# generate a new top-entity to be mailed
|
|
|
|
my $newent=new MIME::Entity;
|
|
|
|
# make a private copy of the passed header and set this one
|
|
|
|
$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" => "pgp-$micalg");
|
|
|
|
|
|
|
|
$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'll 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);
|
|
|
|
|
|
|
|
# make sure outfile is not existing
|
|
|
|
unlink("$tempdir/$tempfile_out");
|
|
|
|
|
|
|
|
# generate the signature
|
|
|
|
$res=&$cmd($ent_file,"$tempdir/$tempfile_out");
|
|
|
|
return $res if ($res);
|
|
|
|
|
|
|
|
# attach the signature
|
|
|
|
$newent->attach(Type => "application/pgp-signature",
|
|
|
|
Path => "$tempdir/$tempfile_out",
|
|
|
|
Filename => "signature.$type",
|
|
|
|
Disposition => "inline",
|
|
|
|
Encoding => "7bit");
|
|
|
|
|
|
|
|
# and send the resulting thing, not cleaning up
|
|
|
|
return &send_entity($newent,@{$rec});
|
|
|
|
}
|
|
|
|
|
|
|
|
# return 0 if ok, errortext otherwise
|
|
|
|
sub crypt_send
|
|
|
|
{
|
|
|
|
my ($ent,$ent_file,$rec,$rec_keys,$cmd,$header)=@_;
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
# generate a new top-entity to be mailed
|
|
|
|
my $newent=new MIME::Entity;
|
|
|
|
# make a private copy of the passed header and set this one
|
|
|
|
$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 RFC2015.\n",
|
|
|
|
"You'll need PGP or GPG to view the content.\n"]);
|
|
|
|
|
|
|
|
# attach the needed dummy-part
|
|
|
|
$newent->attach(Type=>"application/pgp-encrypted",
|
|
|
|
Data=>"Version: 1\n",
|
|
|
|
Encoding=>"7bit");
|
|
|
|
|
|
|
|
# make sure tempfile is not existing
|
|
|
|
unlink("$tempdir/$tempfile_out");
|
|
|
|
|
|
|
|
# generate the encrypted data
|
|
|
|
$res=&$cmd($ent_file,"$tempdir/$tempfile_out",@{$rec_keys});
|
|
|
|
return $res if ($res);
|
|
|
|
|
|
|
|
# attach the encrypted data
|
|
|
|
$newent->attach(Type => "application/octet-stream",
|
|
|
|
Path => "$tempdir/$tempfile_out",
|
|
|
|
Filename => undef,
|
|
|
|
Disposition => "inline",
|
|
|
|
Encoding=>"7bit");
|
|
|
|
|
|
|
|
# and send the resulting thing
|
|
|
|
return &send_entity($newent,@{$rec});
|
|
|
|
}
|
|
|
|
|
2001-11-11 11:41:05 +00:00
|
|
|
# log the msg(s) to syslog or the logfile
|
2001-11-06 12:53:15 +00:00
|
|
|
sub logit
|
|
|
|
{
|
|
|
|
my $msg = shift(@_);
|
|
|
|
|
2001-11-11 11:41:05 +00:00
|
|
|
if ($lf)
|
|
|
|
{
|
|
|
|
# logfile is opened with autoflush set to 1,
|
|
|
|
# so no extra flushing needed
|
|
|
|
# we're more or less emulating the syslog format here...
|
|
|
|
print $lf scalar(localtime)." $0\[$$\] $msg\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
setlogsock('unix');
|
|
|
|
openlog($progname,"pid,cons","mail");
|
|
|
|
syslog("notice","$msg");
|
|
|
|
closelog;
|
|
|
|
}
|
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
|
|
|
|
# returns 0 if ok or an errortext
|
|
|
|
sub send_entity
|
|
|
|
{
|
|
|
|
my ($ent,@args)=@_;
|
|
|
|
|
|
|
|
open(TOMTA,("|$mta ".join(" ",@args)))
|
|
|
|
|| return "cant open pipe to $mta: $!";
|
|
|
|
$ent->print(\*TOMTA);
|
|
|
|
close(TOMTA);
|
|
|
|
return "error when calling $mta: $!"
|
|
|
|
if ($?);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
# remove temporary stuff left behind in directory $what
|
|
|
|
# remove_what set: remove the dir, too.
|
|
|
|
# returns: "" or errormsg
|
|
|
|
sub cleanup
|
|
|
|
{
|
|
|
|
my ($what,$remove_what)=@_;
|
|
|
|
my ($name,$res);
|
|
|
|
|
|
|
|
opendir(F,$what) || return "cant opendir $what: $!";
|
|
|
|
foreach $name (readdir(F))
|
|
|
|
{
|
|
|
|
next if ($name =~ /^\.{1,2}$/); # dont touch the dir-entries...
|
|
|
|
if (-d "$what/$name")
|
|
|
|
{
|
|
|
|
$res=&cleanup("$what/$name");
|
|
|
|
return $res if ($res);
|
|
|
|
rmdir ("$what/$name") || return "cant rmdir $what/$name: $!";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unlink("$what/$name") || return "cant unlink $what/$name: $!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(F);
|
|
|
|
if ($remove_what)
|
|
|
|
{
|
|
|
|
rmdir("$what") || return "cant rmdir $what: $!";
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# log termination, cleanup, exit
|
|
|
|
sub handle_term
|
|
|
|
{
|
|
|
|
my ($sig)=@_;
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
logit("got termination signal SIG$sig, cleaning up");
|
|
|
|
$res=&cleanup($tempdir,1);
|
|
|
|
logit("problem cleaning up $tempdir: $res")
|
|
|
|
if ($res);
|
|
|
|
$res=&wipe_keys;
|
|
|
|
logit("problem doing the module cleanup routine: $res")
|
|
|
|
if ($res);
|
2001-11-11 11:41:05 +00:00
|
|
|
close $lf if ($lf);
|
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
|
|
|
|
sub read_keyrings
|
|
|
|
{
|
|
|
|
logit("reading std keyring.");
|
|
|
|
%stdkeys=&std_listkeys;
|
|
|
|
logit("reading ng keyring.");
|
|
|
|
%ngkeys=&ng_listkeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub read_config
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2002-09-19 14:58:21 +00:00
|
|
|
my (@tmp,$lastkey,$mtaopt);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# get the list of special adresses and adress-regexps
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant open $config: $!\n")
|
|
|
|
if (!open (F,$config));
|
|
|
|
|
|
|
|
logit("reading config file");
|
|
|
|
%config=();
|
|
|
|
@configkeys=();
|
|
|
|
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 the keyid given is 0, don't do ng pgp at all
|
|
|
|
if (/^NGKEY\s+(\S.*)$/)
|
|
|
|
{
|
|
|
|
$ng_defkey=$1;
|
|
|
|
logit("set default ng key ng to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
# if the keyid given is 0, don't do std pgp at all
|
|
|
|
if (/^STDKEY\s+(\S.*)$/)
|
|
|
|
{
|
|
|
|
$std_defkey=$1;
|
|
|
|
logit("set default std key to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^PGPPATH\s+(\S.+)\s*$/)
|
|
|
|
{
|
|
|
|
$PGP=$1;
|
|
|
|
logit("set pgppath to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^GPGPATH\s+(\S.+)\s*$/)
|
|
|
|
{
|
|
|
|
$GPG=$1;
|
|
|
|
logit("set gpgpath to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^USEPGP\s+(\d)/)
|
|
|
|
{
|
|
|
|
$use_pgp=$1;
|
|
|
|
logit("set use_pgp to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^AGENTPATH\s+(\S.+)\s*$/) #
|
|
|
|
{
|
|
|
|
$agent=$1;
|
|
|
|
logit("set agent to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^CLIENTPATH\s+(\S.+)\s*$/)
|
|
|
|
{
|
|
|
|
$client=$1;
|
|
|
|
logit("set client to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^MTA\s+(\S.+)\s*$/)
|
|
|
|
{
|
|
|
|
$mtaopt=$1;
|
|
|
|
logit("set mta to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^SECRETONDEMAND\s+(\d)/)
|
|
|
|
{
|
|
|
|
$secret_on_demand=$1;
|
|
|
|
logit("set secret_on_demand to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if (/^ALWAYSTRUST\s+(\d)/)
|
|
|
|
{
|
|
|
|
$alwaystrust=$1;
|
|
|
|
logit("set alwaystrust to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^QUEUEDIR\s+(\S+)\s*$/)
|
|
|
|
{
|
|
|
|
logit("set queuedir to $1") if ($options{"d"});
|
|
|
|
$queuedir=$1;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/^INTERVAL\s+(\d+)\s*$/)
|
|
|
|
{
|
|
|
|
logit("set interval to $1") if ($options{"d"});
|
|
|
|
$interval=$1;
|
|
|
|
next;
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2002-01-30 13:36:38 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
if (/^TEMPDIR\s+(\S+)\s*$/)
|
|
|
|
{
|
|
|
|
logit("set tempdir to $1") if ($options{"d"});
|
|
|
|
$tempdir=$1;
|
|
|
|
next;
|
|
|
|
}
|
2002-01-30 13:36:38 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
if (/^LOGFILE\s+(\S+)\s*$/)
|
|
|
|
{
|
|
|
|
# close old logfile if there is one
|
|
|
|
close $lf
|
|
|
|
if ($logfile && $logfile ne $1);
|
|
|
|
$logfile=$1;
|
|
|
|
# we append to the logfile
|
|
|
|
&bailout("cant open logfile $logfile: $!")
|
|
|
|
if (!open($lf,">>$logfile"));
|
|
|
|
$lf->autoflush(1);
|
|
|
|
logit("set logfile to $1") if ($options{"d"});
|
|
|
|
next;
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
if (/^(\S+)\s+(\S+)\s*$/)
|
|
|
|
{
|
|
|
|
my ($key,$action)=(lc($1),lc($2));
|
|
|
|
if ($action=~/^(none|std(sign)?|ng(sign)?|fallback)(-force)?$/)
|
2001-11-11 11:41:05 +00:00
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
$config{$key}=$action;
|
|
|
|
push @configkeys, $key;
|
|
|
|
logit("got conf $action for $key") if ($options{"d"});
|
2001-11-11 11:41:05 +00:00
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
else
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
logit("ignoring bad action \"$action\" for $key");
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
close F;
|
2001-11-11 10:28:53 +00:00
|
|
|
|
|
|
|
# generate queuedir if not existing
|
|
|
|
if (!-d $queuedir)
|
|
|
|
{
|
|
|
|
unlink "$queuedir";
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant mkdir $queuedir: $!")
|
|
|
|
if (!mkdir($queuedir,0700));
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
|
|
|
# check queuedir owner & perm
|
|
|
|
elsif ((stat($queuedir))[4] != $<)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("$queuedir is not owned by you - refusing to run");
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
|
|
|
elsif ((stat($queuedir))[2]&0777 != 0700)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("$queuedir does not have mode 0700 - refusing to run");
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# gen tempdir for storing mime-stuff
|
|
|
|
if (!-d $tempdir)
|
|
|
|
{
|
|
|
|
unlink "$tempdir";
|
|
|
|
if (!mkdir($tempdir,0700))
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("cant mkdir $tempdir: $!");
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif ((stat($tempdir))[4] != $<)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("$tempdir is not owned by you - refusing to run");
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
|
|
|
elsif ((stat($tempdir))[2]&0777 != 0700)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("$tempdir does not have mode 0700 - refusing to run");
|
2001-11-11 10:28:53 +00:00
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# consistency checks
|
|
|
|
$use_agent=$client && $agent;
|
|
|
|
$secret_on_demand=0 if (!$use_agent);
|
|
|
|
|
2002-09-19 09:51:25 +00:00
|
|
|
# sanity checks
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("bad ng executable '$GPG' -- exiting")
|
|
|
|
if (! -x $GPG);
|
2002-09-19 14:58:21 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("bad std executable '$PGP' -- exiting")
|
|
|
|
if ($use_pgp && ! -x $PGP);
|
2002-09-19 14:58:21 +00:00
|
|
|
|
|
|
|
if ($mtaopt && $mtaopt =~ /^(\S+)/)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("bad MTA '$mtaopt' -- exiting")
|
|
|
|
if (! -x $1);
|
|
|
|
$mta=$mtaopt;
|
2002-09-19 14:58:21 +00:00
|
|
|
}
|
2002-09-19 09:51:25 +00:00
|
|
|
|
2002-09-19 14:58:21 +00:00
|
|
|
if ($use_agent)
|
2002-09-19 09:51:25 +00:00
|
|
|
{
|
|
|
|
foreach my $x ($client,$agent)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
&bailout("bad agent executable '$x' -- exiting")
|
|
|
|
if (! -x $x);
|
2002-09-19 09:51:25 +00:00
|
|
|
}
|
|
|
|
}
|
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.
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
$entity->head->mime_attr("content-transfer-encoding"
|
|
|
|
=> "quoted-printable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# notify the sender of the problem
|
|
|
|
sub send_bounce
|
|
|
|
{
|
|
|
|
my ($res,$file)=@_;
|
|
|
|
|
|
|
|
open(F,"|$mta -t") || return;
|
2002-03-05 13:18:49 +00:00
|
|
|
print F "From: $name\nTo: $name\nSubject: $progname Mail Send Failure\n\n";
|
2001-11-06 12:53:15 +00:00
|
|
|
print F "your mail $queuedir/$file could not be sent to some or all"
|
|
|
|
." recipients.\nthe detailed error message was:\n\n";
|
|
|
|
print F "$res\n";
|
|
|
|
print F "please remove the backup file $queuedir/.$file\n"
|
|
|
|
."or rename it back to $queuedir/$file if you want me to try again for all recipients.\n";
|
|
|
|
close F;
|
|
|
|
}
|
|
|
|
|
|
|
|
# list the public keys in the usual keyrings
|
|
|
|
# returns: hash of (address,keyid)
|
|
|
|
sub std_listkeys { if ($use_pgp) { return &pgp_listkeys; }
|
|
|
|
else { return &gpg_listkeys_rsa; } }
|
|
|
|
sub ng_listkeys { return &gpg_listkeys_norsa; }
|
|
|
|
|
|
|
|
# sign a infile and write it to outfile
|
|
|
|
# args: infile,outfile
|
|
|
|
sub std_sign
|
|
|
|
{
|
|
|
|
if ($use_pgp)
|
|
|
|
{
|
|
|
|
return &pgp_sign(@_,"");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return &gpg_sign(@_,$std_defkey,
|
|
|
|
"--rfc1991 --cipher-algo idea --digest-algo md5"
|
|
|
|
." --compress-algo 1");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub ng_sign { return &gpg_sign(@_,$ng_defkey,undef); }
|
|
|
|
|
|
|
|
# crypt+sign a infile with keys, write it to outfile
|
|
|
|
# args: infile,outfile,recipients
|
|
|
|
sub std_crypt
|
|
|
|
{
|
|
|
|
if ($use_pgp)
|
|
|
|
{
|
|
|
|
return &pgp_crypt("",@_);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return &gpg_crypt($std_defkey,@_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub ng_crypt { return &gpg_crypt($ng_defkey,@_); }
|
|
|
|
|
|
|
|
|
|
|
|
# setup for std pgp (rsa/idea, 2.6.*)
|
|
|
|
# returns: hash of address,key
|
|
|
|
sub pgp_listkeys
|
|
|
|
{
|
|
|
|
my (%stdkeys,$lastkey,@tmp);
|
|
|
|
|
|
|
|
#get the keys and dump the trailer and header lines
|
|
|
|
%stdkeys=();
|
2001-11-11 10:28:53 +00:00
|
|
|
# this does not care if pgp is not existent...but then, we're not
|
|
|
|
# needing the pgp keyring
|
2001-11-06 12:53:15 +00:00
|
|
|
@tmp=`$PGP -kv 2>$tempdir/subprocess`;
|
|
|
|
foreach (@tmp)
|
|
|
|
{
|
|
|
|
my $name;
|
|
|
|
|
|
|
|
if (/^pub\s+\d+\/(\S+)\s+(.+)$/)
|
|
|
|
{
|
|
|
|
my $userspec=$2;
|
|
|
|
my $key=$1;
|
|
|
|
|
|
|
|
if ($userspec =~ /<(.+)>/)
|
|
|
|
{
|
|
|
|
$name=lc($1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
undef $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($name)
|
|
|
|
{
|
|
|
|
$stdkeys{$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;
|
|
|
|
}
|
|
|
|
if (/^\s+.*<(\S+)>\s*$/)
|
|
|
|
{
|
|
|
|
my $name=lc($1);
|
|
|
|
$stdkeys{$name}="0x$lastkey";
|
|
|
|
&logit("got stdkey (uid) 0x$lastkey for $name") if ($debug);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return %stdkeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
# generate detached signature
|
|
|
|
# input: filename_in,filename_out,extra_args
|
|
|
|
# output: errormsg or ""
|
|
|
|
sub pgp_sign
|
|
|
|
{
|
|
|
|
my ($infile,$outfile,$extra_args)=@_;
|
|
|
|
my ($passphrase,$passphrase_cmd);
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
$passphrase_cmd="|$client get $std_defkey";
|
|
|
|
$passphrase="";
|
|
|
|
|
|
|
|
# check the passphrase for correctness
|
|
|
|
# only if actual work is requested
|
|
|
|
&verify_passphrase($std_defkey) if ($infile || $outfile);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$passphrase_cmd="";
|
|
|
|
$passphrase=$secrets{$std_defkey};
|
|
|
|
return "no passphrase known for key $std_defkey"
|
|
|
|
if (!$passphrase);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$infile && !$outfile) # only check the passphrase
|
|
|
|
{
|
|
|
|
open(F,"$passphrase_cmd|PGPPASSFD=0 $PGP +batchmode "
|
|
|
|
."$extra_args -u $std_defkey -sbatf >$tempdir/subprocess 2>&1")
|
|
|
|
|| return "cant open |pgp: $!";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
open(F,"$passphrase_cmd|PGPPASSFD=0 $PGP +batchmode $extra_args "
|
|
|
|
."-u $std_defkey -sbat $infile -o $outfile >$tempdir/subprocess 2>&1")
|
|
|
|
|| return "cant open |pgp: $!";
|
|
|
|
}
|
|
|
|
print F "$passphrase\n"
|
|
|
|
if ($passphrase);
|
|
|
|
close(F);
|
|
|
|
$passphrase="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; # does this overwrite?
|
|
|
|
return "" if (!$?);
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running pgp: $!\n".join("\n",@result) if ($? == 0xff00);
|
|
|
|
return "pgp died from signal" . ($? & 0x7f)."\n".join("\n",@result) if ($? <= 0x80);
|
|
|
|
$? >>= 8;
|
|
|
|
return "bad passphrase\n".join("\n",@result) if ($? == 20);
|
|
|
|
return "pgp returned $?\n".join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# sign and encrypt
|
|
|
|
# input: extra_args,filename_in,filename_out,recipients
|
|
|
|
# output: errormsg or ""
|
|
|
|
sub pgp_crypt
|
|
|
|
{
|
|
|
|
my ($extra_args,$infile,$outfile,@recipients)=@_;
|
|
|
|
my ($passphrase,$cmd);
|
|
|
|
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
$passphrase="";
|
|
|
|
$cmd="$client get $std_defkey|";
|
|
|
|
|
|
|
|
&verify_passphrase($std_defkey);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$passphrase=$secrets{$std_defkey};
|
|
|
|
return "no passphrase known for key $std_defkey"
|
|
|
|
if (!$passphrase);
|
|
|
|
}
|
|
|
|
|
|
|
|
$cmd.="PGPPASSFD=0 $PGP +batchmode $extra_args -u $std_defkey -esat "
|
|
|
|
."$infile -o $outfile " . join(" ",@recipients) ." >$tempdir/subprocess 2>&1";
|
|
|
|
|
|
|
|
open(F,"|$cmd") || return "cant open |pgp: $!";
|
|
|
|
print F "$passphrase\n"
|
|
|
|
if ($passphrase);
|
|
|
|
close(F);
|
|
|
|
$passphrase="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; # does this overwrite?
|
|
|
|
return "" if (!$?);
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running pgp: $!\n".join("\n",@result) if ($? == 0xff00);
|
|
|
|
return "pgp died from signal" . ($? & 0x7f)
|
|
|
|
."\n".join("\n",@result) if ($? <= 0x80);
|
|
|
|
$? >>= 8;
|
|
|
|
return "bad passphrase\n".join("\n",@result) if ($? == 20);
|
|
|
|
return "pgp returned $?\n".join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# generate detached signature
|
|
|
|
# input: filename_in,filename_out,key,extra_args
|
|
|
|
# key is the key that's used for signing & secret retrieval
|
|
|
|
# output: errormsg or ""
|
|
|
|
sub gpg_sign
|
|
|
|
{
|
|
|
|
my ($infile,$outfile,$key,$extra_args)=@_;
|
|
|
|
my ($passphrase_cmd,$passphrase);
|
|
|
|
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
$passphrase_cmd="|$client get $key";
|
|
|
|
$passphrase="";
|
|
|
|
|
|
|
|
&verify_passphrase($key) if ($infile || $outfile);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$passphrase_cmd="";
|
|
|
|
$passphrase=$secrets{$key};
|
|
|
|
return "no passphrase known for key $key"
|
|
|
|
if (!$passphrase);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$infile && !$outfile) # only check passphrase
|
|
|
|
{
|
|
|
|
open(F,"$passphrase_cmd|$GPG -q -t --batch --armor "
|
|
|
|
."--passphrase-fd 0 --default-key $key $extra_args --detach-sign "
|
|
|
|
.">$tempdir/subprocess 2>&1") || return "cant open |gpg: $!";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
open(F,"$passphrase_cmd|$GPG -q -t --batch --armor --passphrase-fd 0 "
|
|
|
|
."--default-key $key $extra_args --detach-sign -o $outfile $infile "
|
|
|
|
.">$tempdir/subprocess 2>&1")
|
|
|
|
|| return "cant open |gpg: $!";
|
|
|
|
}
|
|
|
|
print F "$passphrase\n"
|
|
|
|
if ($passphrase);
|
|
|
|
close(F);
|
|
|
|
$passphrase="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; # does this overwrite?
|
|
|
|
return "" if (!$?);
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg: $!\n".join("\n",@result) if ($? == 0xff00);
|
|
|
|
return "gpg died from signal" . ($? & 0x7f)
|
|
|
|
."\n".join("\n",@result) if ($? <= 0x80);
|
|
|
|
$? >>= 8;
|
|
|
|
return "gpg returned $?\n".join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# sign and encrypt
|
|
|
|
# input: key,filename_in,filename_out,recipients
|
|
|
|
# key is used for signing & secret retrieval
|
|
|
|
# if key is an rsa-key, do all the
|
|
|
|
# stuff thats needed to generate rsa-stuff that pgp2 can successfully
|
|
|
|
# decrypt (this means to care for some bugs in pgp2 and emulate
|
|
|
|
# its behaviour...
|
|
|
|
# output: errormsg or ""
|
|
|
|
sub gpg_crypt
|
|
|
|
{
|
|
|
|
my ($key,$infile,$outfile,@recipients)=@_;
|
|
|
|
my ($cmd,$passphrase);
|
|
|
|
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
$passphrase="";
|
|
|
|
$cmd="$client get $key|";
|
|
|
|
|
|
|
|
&verify_passphrase($key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$passphrase=$secrets{$key};
|
|
|
|
return "no passphrase known for key $key"
|
|
|
|
if (!$passphrase);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($key eq $std_defkey) # means: compat mode!
|
|
|
|
{
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
# 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.="$GPG --batch -q --detach-sign --default-key $key "
|
|
|
|
."--passphrase-fd 0 -o $outfile.inter1 $infile >$tempdir/subprocess 2>&1";
|
|
|
|
open(F,"|$cmd") || return "cant open |gpg: $!";
|
|
|
|
print F "$passphrase\n"
|
|
|
|
if ($passphrase);
|
|
|
|
close(F);
|
|
|
|
$passphrase="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
|
|
if ($?)
|
|
|
|
{
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg: $!\n"
|
|
|
|
.join("\n",@result) if ($? == 0xff00);
|
|
|
|
return "gpg died from signal" . ($? & 0x7f)
|
|
|
|
."\n".join("\n",@result)if ($? <= 0x80);
|
|
|
|
$? >>= 8;
|
|
|
|
return "gpg returned $?\n".join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# then, convert the cleartext to the internal literal structure
|
|
|
|
$res=0xffff
|
|
|
|
& system("$GPG --batch -q --store -z 0 -o $outfile.inter2 "
|
|
|
|
."$infile >$tempdir/subprocess 2>&1");
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg literal conversion: $res\n"
|
|
|
|
.join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# compress signature and literal in the required order
|
|
|
|
$res=0xffff & system("$CAT $outfile.inter1 $outfile.inter2"
|
|
|
|
."|$GPG --no-literal --store --compress-algo 1 "
|
|
|
|
."-o $outfile.inter3 >$tempdir/subprocess 2>&1");
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg sig+data compression: $res\n"
|
|
|
|
.join("\n",@result);
|
|
|
|
}
|
|
|
|
|
|
|
|
# and finally encrypt all this for the wanted recipients.
|
|
|
|
$cmd="$GPG --no-options --load-extension idea "
|
|
|
|
."--no-literal --encrypt --rfc1991 --cipher-algo idea "
|
|
|
|
.($alwaystrust?"--always-trust ":"")
|
|
|
|
."--armor -o $outfile -r "
|
|
|
|
.join(" -r ",@recipients)
|
|
|
|
." $outfile.inter3 >$tempdir/subprocess 2>&1";
|
|
|
|
$res= 0xffff & system($cmd);
|
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg encryption: $res\n"
|
|
|
|
.join("\n",@result);
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
# the usual variant: ng-keys only, no backwards compatibility for
|
|
|
|
# pgp2
|
|
|
|
{
|
|
|
|
$cmd.="$GPG --batch -q -t --armor --passphrase-fd 0 "
|
|
|
|
.($alwaystrust?"--always-trust ":"")
|
|
|
|
."-o $outfile --default-key $key -r "
|
|
|
|
. join(" -r ",@recipients)
|
|
|
|
." --encrypt --sign $infile >$tempdir/subprocess 2>&1";
|
|
|
|
|
|
|
|
open(F,"|$cmd") || return "cant open |gpg: $!";
|
|
|
|
print F "$passphrase\n"
|
|
|
|
if ($passphrase);
|
|
|
|
close(F);
|
|
|
|
$passphrase="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
|
|
return "" if (!$?);
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
|
|
|
return "error running gpg: $!\n".join("\n",@result) if ($? == 0xff00);
|
|
|
|
return "gpg died from signal" . ($? & 0x7f).
|
|
|
|
"\n".join("\n",@result)if ($? <= 0x80);
|
|
|
|
$? >>= 8;
|
|
|
|
return "gpg returned $?\n".join("\n",@result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# list keys
|
|
|
|
# returns: hash of address,key
|
|
|
|
sub gpg_listkeys_norsa
|
|
|
|
{
|
|
|
|
my (%ngkeys,$lastkey,@tmp,@info,$now);
|
2001-12-12 13:31:02 +00:00
|
|
|
my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled',
|
|
|
|
'r'=>'revoked','e'=>'expired');
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
$now=time;
|
|
|
|
|
2001-11-11 10:28:53 +00:00
|
|
|
# this does not care if gpg is not existent...but then, we're not
|
|
|
|
# needing the gpg keyring
|
|
|
|
@tmp=`$GPG -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tempdir/subprocess`;
|
2001-11-06 12:53:15 +00:00
|
|
|
foreach (@tmp)
|
|
|
|
{
|
|
|
|
my $name;
|
|
|
|
|
|
|
|
@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
|
|
|
|
|
|
|
|
# no rsa-keys, please
|
|
|
|
# and be sure to skip these uid's, too
|
|
|
|
if ($info[3] eq "1")
|
|
|
|
{
|
|
|
|
&logit("ignoring rsa key 0x$info[4]") if ($debug);
|
|
|
|
undef $lastkey;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# fixme lowprio: more general unquote
|
|
|
|
$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] =~ /<(.+)>/)
|
|
|
|
{
|
|
|
|
$name=lc($1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
undef $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
# check the key: public part or uid?
|
|
|
|
if ($info[0] eq "pub")
|
|
|
|
{
|
|
|
|
# lets associate this key with the current email address
|
|
|
|
# if an address is known
|
|
|
|
$lastkey=$info[4];
|
|
|
|
|
|
|
|
if ($name)
|
|
|
|
{
|
2002-01-02 06:59:22 +00:00
|
|
|
# ignore expired, revoked and other bad keys
|
|
|
|
if (defined $badcauses{$info[1]})
|
|
|
|
{
|
|
|
|
&logit("ignoring DSA key 0x$info[4], reason: "
|
|
|
|
.$badcauses{$info[1]});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
$ngkeys{$name}="0x$lastkey";
|
|
|
|
|
|
|
|
&logit("got ngkey 0x$lastkey for $name")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
&logit("saved ngkey 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 rsa key")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ($name)
|
|
|
|
{
|
2002-01-02 06:59:22 +00:00
|
|
|
# ignore expired, revoked and other bad keys
|
|
|
|
if (defined $badcauses{$info[1]})
|
|
|
|
{
|
|
|
|
&logit("ignoring DSA uid $name for 0x$lastkey, "
|
|
|
|
."reason: ".$badcauses{$info[1]});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
$ngkeys{$name}="0x$lastkey";
|
|
|
|
&logit("got ngkey (uid) 0x$lastkey for $name")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
&logit("ignoring uid without valid address")
|
|
|
|
if ($debug);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return %ngkeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
# list keys
|
|
|
|
# returns: hash of address,key
|
|
|
|
sub gpg_listkeys_rsa
|
|
|
|
{
|
|
|
|
my (%stdkeys,$lastkey,@tmp,@info,$now);
|
2001-12-12 13:31:02 +00:00
|
|
|
my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled',
|
|
|
|
'r'=>'revoked','e'=>'expired');
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
$now=time;
|
|
|
|
|
2001-11-11 10:28:53 +00:00
|
|
|
# this does not care if gpg is not existent...but then, we're not
|
|
|
|
# needing the gpg keyring
|
|
|
|
@tmp=`$GPG -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tempdir/subprocess`;
|
2001-11-06 12:53:15 +00:00
|
|
|
foreach (@tmp)
|
|
|
|
{
|
|
|
|
my $name;
|
|
|
|
|
|
|
|
@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
|
|
|
|
|
|
|
|
# no dsa/elg-keys, please
|
|
|
|
# and be sure to skip these uid's, too
|
|
|
|
if ($info[3] > 1)
|
|
|
|
{
|
|
|
|
&logit("ignoring dsa/elg key 0x$info[4]") if ($debug);
|
|
|
|
undef $lastkey;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# fixme lowprio: general unquote
|
|
|
|
$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] =~ /<(.+)>/)
|
|
|
|
{
|
|
|
|
$name=lc($1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
undef $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($info[0] eq "pub")
|
|
|
|
{
|
|
|
|
$lastkey=$info[4];
|
2002-01-02 06:59:22 +00:00
|
|
|
|
|
|
|
# ignore expired, revoked and other bad keys
|
|
|
|
if (defined $badcauses{$info[1]})
|
|
|
|
{
|
|
|
|
&logit("ignoring RSA key 0x$info[4], reason: "
|
|
|
|
.$badcauses{$info[1]});
|
|
|
|
next;
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
if ($name)
|
|
|
|
{
|
|
|
|
$stdkeys{$name}="0x$lastkey";
|
|
|
|
|
|
|
|
&logit("got stdkey 0x$lastkey for $name")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
&logit("saved stdkey 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 dsa key &
|
|
|
|
# we're set to ignore those
|
|
|
|
if (!$lastkey)
|
|
|
|
{
|
|
|
|
$name="<no valid address>" if (!$name);
|
|
|
|
&logit("ignoring uid $name, belongs to dsa/elg key")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ($name)
|
|
|
|
{
|
2002-01-02 06:59:22 +00:00
|
|
|
|
|
|
|
# ignore expired, revoked and other bad keys
|
|
|
|
if (defined $badcauses{$info[1]})
|
|
|
|
{
|
|
|
|
&logit("ignoring RSA uid $name for 0x$lastkey, "
|
|
|
|
."reason: ".$badcauses{$info[1]});
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
$stdkeys{$name}="0x$lastkey";
|
|
|
|
&logit("got stdkey (uid) 0x$lastkey for $name")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
&logit("ignoring uid without valid address")
|
|
|
|
if ($debug);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return %stdkeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
# get and store a secret
|
|
|
|
# if agent support activated: check if agent running
|
|
|
|
# and let client ask for the secret and store it
|
|
|
|
# otherwise, ask and store the secret yourself
|
|
|
|
# returns error text or ""
|
|
|
|
sub askput_secret
|
|
|
|
{
|
|
|
|
my ($id)=@_;
|
|
|
|
my ($res,$phrase);
|
2002-09-19 16:25:46 +00:00
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
2002-09-19 16:25:46 +00:00
|
|
|
# if x11 is running and get is used, then the agent will
|
|
|
|
# run a graphical query program. otherwise things use the command line
|
|
|
|
my $cmd="$client put $id 2>$tempdir/subprocess";
|
|
|
|
$cmd="$client get $id >$tempdir/subprocess 2>&1" if ($ENV{DISPLAY});
|
|
|
|
|
2001-11-06 12:53:15 +00:00
|
|
|
# now let the secret client handle the situation:
|
|
|
|
# it asks for the secret and stores it
|
2002-09-19 16:25:46 +00:00
|
|
|
$res = 0xffff & system "$cmd";
|
2001-11-06 12:53:15 +00:00
|
|
|
if ($res)
|
|
|
|
{
|
|
|
|
open F,"$tempdir/subprocess";
|
|
|
|
my @result=<F>;
|
|
|
|
close F;
|
2002-09-19 16:25:46 +00:00
|
|
|
return "$client returned error code $res\n"
|
2001-11-06 12:53:15 +00:00
|
|
|
.join("\n",@result);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
print "enter secret for key $id:\n";
|
|
|
|
system "stty -echo";
|
|
|
|
chomp ($phrase=<>);
|
|
|
|
system "stty echo";
|
|
|
|
print "\n";
|
|
|
|
$secrets{$id}=$phrase;
|
|
|
|
$phrase="xxxxxxxxxxxxxxxxxxxxxxxxxxx"; # does this overwrite
|
|
|
|
# the previous content? lets hope so...
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# lookup the usual default key, if none is given
|
|
|
|
# pgp: use the first key in the secret keyring
|
|
|
|
# gpg/norsa: use the first dsa-key in the secret keyring
|
|
|
|
# gpg/rsa: similar, the first rsa-key is used
|
|
|
|
# returns keyid (std,ng)
|
|
|
|
sub lookup_defkeys
|
|
|
|
{
|
|
|
|
my (@list,@tmp,$stdkey,$ngkey);
|
|
|
|
|
|
|
|
# first, get the std key as this is more work
|
|
|
|
$stdkey="";
|
|
|
|
|
|
|
|
# if we use pgp, ask pgp to show the contents of the secret keyring
|
|
|
|
# (ugly)
|
|
|
|
if ($use_pgp)
|
|
|
|
{
|
|
|
|
# fixme lowprio: is there a neater way to do this?
|
|
|
|
@list=`$PGP -kv $ENV{HOME}/.pgp/secring.pgp 2>$tempdir/subprocess`;
|
|
|
|
foreach (@list)
|
|
|
|
{
|
|
|
|
if (/^sec\s+\d+\/(\S+)\s+/)
|
|
|
|
{
|
|
|
|
$stdkey="0x$1";
|
|
|
|
&logit("defaultkey for std is $stdkey") if ($debug);
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# else we ask gpg to show the secring and use the first rsa key
|
|
|
|
else
|
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
@tmp=`$GPG -q --batch --list-secret-keys --with-colons 2>$tempdir/subprocess`;
|
2001-11-06 12:53:15 +00:00
|
|
|
foreach (@tmp)
|
|
|
|
{
|
|
|
|
@list=split(/:/);
|
|
|
|
next if ($list[0] ne "sec"); # only check secret keys
|
|
|
|
$list[4] =~ s/^.{8}//; # truncate key-id
|
|
|
|
|
|
|
|
if ($list[3] eq "1") # this is a rsa key
|
|
|
|
{
|
|
|
|
$stdkey="0x$list[4]";
|
|
|
|
&logit("defaultkey for std is $stdkey") if ($debug);
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# now, get the ng key
|
2001-11-11 10:28:53 +00:00
|
|
|
@tmp=`$GPG -q --batch --list-secret-keys --with-colons 2>$tempdir/subprocess`;
|
2001-11-06 12:53:15 +00:00
|
|
|
foreach (@tmp)
|
|
|
|
{
|
|
|
|
@list=split(/:/);
|
|
|
|
next if ($list[0] ne "sec"); # only check secret keys
|
|
|
|
$list[4] =~ s/^.{8}//; # truncate key-id
|
|
|
|
|
|
|
|
if ($list[3] ne "1") # this is not a rsa key, therefore dsa/elg
|
|
|
|
{
|
|
|
|
$ngkey="0x$list[4]";
|
|
|
|
&logit("defaultkey for ng is $ngkey") if ($debug);
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ($stdkey,$ngkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
# sets the default default keys if none specified yet
|
|
|
|
# does the setup for the agent-process if needed
|
|
|
|
# asks, verifies and stores the secrets if secret_on_demand is not set
|
|
|
|
# returns "" or error
|
|
|
|
sub get_verify_secrets
|
|
|
|
{
|
|
|
|
my ($stdkey,$ngkey)=&lookup_defkeys;
|
|
|
|
my $res;
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
# set the std keys if no overrides given and keys were found
|
2001-11-11 10:28:53 +00:00
|
|
|
$std_defkey=$stdkey if (!defined($std_defkey) && $stdkey);
|
|
|
|
$ng_defkey=$ngkey if (!defined($ng_defkey) && $ngkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
return "no default key for std known"
|
2001-11-11 10:28:53 +00:00
|
|
|
if (!defined $std_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
return "no default key for ng known"
|
2001-11-11 10:28:53 +00:00
|
|
|
if (!defined $ng_defkey);
|
2001-11-06 12:53:15 +00:00
|
|
|
|
|
|
|
# if use_agent is set, check if the agent is running and start one
|
|
|
|
# if needed.
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
# check if agent properly active
|
|
|
|
# not running? start a personal instance
|
|
|
|
# and remember its pid
|
|
|
|
if (!$ENV{"AGENT_SOCKET"})
|
|
|
|
{
|
|
|
|
# start your own agent process
|
|
|
|
# and remember its pid
|
|
|
|
$private_agent=open(SOCKETNAME,"-|");
|
|
|
|
return "cant fork: $!" if (!defined($private_agent));
|
|
|
|
if ($private_agent) # original process
|
|
|
|
{
|
|
|
|
# get the socketname
|
|
|
|
$res=<SOCKETNAME>;
|
|
|
|
# and set the correct env variable for client
|
|
|
|
$res=~/^AGENT_SOCKET=\'(.+)\';/;
|
|
|
|
$ENV{"AGENT_SOCKET"}=$1;
|
|
|
|
# do not close the pipe, because then the
|
|
|
|
# parent process tries to wait() on the child,
|
|
|
|
# which wont work here
|
|
|
|
&logit("forked secret-agent pid $private_agent,"
|
|
|
|
."socket is $1")
|
|
|
|
if ($options{"d"});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
# the child that should exec the quintuple-agent
|
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
exec "$agent"
|
2002-09-19 16:25:46 +00:00
|
|
|
|| &bailout("cant exec $agent: $!");
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
|
|
|
|
if (!$secret_on_demand)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
# get the std passphrase and verify it,
|
|
|
|
# but only if we're doing std pgp at all
|
|
|
|
# i.e. keyid!=0
|
|
|
|
if ($std_defkey)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
$res=&askput_secret($std_defkey);
|
2002-09-19 16:25:46 +00:00
|
|
|
bailout("could not read passphrase for $std_defkey: $res")
|
|
|
|
if ($res);
|
2001-11-11 10:28:53 +00:00
|
|
|
$res=std_sign(undef,undef);
|
|
|
|
print "wrong passphrase, try again.\n"
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
while ($res);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# get the ng passphrase and verify it
|
2001-11-11 10:28:53 +00:00
|
|
|
# again, only if ng pgp/gpg requested/possible
|
|
|
|
if ($ng_defkey)
|
2001-11-06 12:53:15 +00:00
|
|
|
{
|
2001-11-11 10:28:53 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
$res=&askput_secret($ng_defkey);
|
2002-09-19 16:25:46 +00:00
|
|
|
bailout("could not read passphrase for $ng_defkey: $res")
|
|
|
|
if ($res);
|
2001-11-11 10:28:53 +00:00
|
|
|
$res=ng_sign(undef,undef);
|
|
|
|
print "wrong passphrase, try again.\n"
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
while ($res);
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
# if secret-agent support is active:
|
|
|
|
# removes the keys from the secret agent's store and
|
|
|
|
# terminates the agent if wanted
|
|
|
|
sub wipe_keys
|
|
|
|
{
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
if ($use_agent)
|
|
|
|
{
|
|
|
|
if ($private_agent)
|
|
|
|
{
|
|
|
|
# kill the private agent process
|
|
|
|
$res = kill('TERM',$private_agent);
|
|
|
|
&logit("problem killing $private_agent: $!") if (!$res);
|
|
|
|
wait;
|
|
|
|
}
|
2002-09-19 16:25:46 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if ($std_defkey)
|
|
|
|
{
|
|
|
|
$res = 0xffff & system "$client delete $std_defkey";
|
|
|
|
&logit("problem deleting secret for $std_defkey: $res")
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
if ($ng_defkey)
|
|
|
|
{
|
|
|
|
$res = 0xffff & system "$client delete $ng_defkey";
|
|
|
|
&logit("problem deleting secret for $ng_defkey: $res")
|
|
|
|
if ($res);
|
|
|
|
}
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
# requests the passphrase from the agent and runs it
|
2001-11-06 12:53:15 +00:00
|
|
|
# through the usual verification process.
|
|
|
|
# does not stop until the passphrase passes the test.
|
|
|
|
# does assume that secret agent is running (will not be called
|
|
|
|
# otherwise...)
|
|
|
|
sub verify_passphrase
|
|
|
|
{
|
|
|
|
my ($key)=@_;
|
|
|
|
my $res;
|
|
|
|
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
# let the sign subroutine check for validity
|
|
|
|
if ($key eq $std_defkey)
|
|
|
|
{
|
|
|
|
$res=std_sign(undef,undef);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$res=ng_sign(undef,undef);
|
|
|
|
}
|
|
|
|
|
|
|
|
# ok? then exit
|
|
|
|
return 0 if (!$res);
|
2002-09-19 16:25:46 +00:00
|
|
|
# otherwise nuke the key and redo this
|
2001-11-06 12:53:15 +00:00
|
|
|
system("$client delete $key");
|
|
|
|
}
|
|
|
|
exit 1; # must not reach here
|
|
|
|
}
|
|
|
|
|
2002-04-25 14:31:58 +00:00
|
|
|
|
|
|
|
# find the correct action for a given email address
|
|
|
|
# input: addresses and custom-header
|
|
|
|
# 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.
|
|
|
|
# note: ng and std means encryption here, no check for keys necessary anymore
|
|
|
|
# fixme: uses globals stdkeys, ngkeys, options
|
|
|
|
sub findaction
|
|
|
|
{
|
|
|
|
my ($custom,@addrs,@affected)=@_;
|
|
|
|
my (%actions,$addr);
|
|
|
|
|
|
|
|
# lookup addresses in config
|
|
|
|
foreach $addr (@addrs)
|
|
|
|
{
|
|
|
|
# go through the configkeys
|
|
|
|
foreach (@configkeys)
|
|
|
|
{
|
|
|
|
if ($addr =~ /$_/i)
|
|
|
|
{
|
|
|
|
$actions{$addr}=$config{$_};
|
|
|
|
logit("found directive: $addr -> $actions{$addr}")
|
|
|
|
if ($options{"d"});
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# custom set? then override the config except where action=none
|
|
|
|
if ($custom && $actions{$addr} ne "none")
|
|
|
|
{
|
|
|
|
logit("custom conf header: overrides $addr -> $custom")
|
|
|
|
if ($options{"d"});
|
|
|
|
$actions{$addr}=$custom;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
# apply default if necessary
|
|
|
|
$actions{$addr}=$config{"default"} if (! exists $actions{$addr});
|
|
|
|
}
|
|
|
|
|
|
|
|
# now check the found actions: anyone with -force options?
|
|
|
|
foreach $addr (@addrs)
|
|
|
|
{
|
|
|
|
next if ($actions{$addr} !~ /^(\S+)-force$/);
|
|
|
|
my $force=$1;
|
|
|
|
logit("found force directive: $addr -> $actions{$addr}")
|
|
|
|
if ($options{"d"});
|
|
|
|
|
|
|
|
# yuck, must find affected addresses: those with action=none
|
|
|
|
# have to be disregarded and unchanged.
|
|
|
|
|
|
|
|
@affected = grep($actions{$_} ne "none",@addrs);
|
|
|
|
|
|
|
|
# (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
|
2002-04-26 02:11:33 +00:00
|
|
|
$force="stdsign" if (grep(!exists $stdkeys{$_}, @affected));
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
elsif ($force eq "ng")
|
|
|
|
{
|
2002-04-26 02:11:33 +00:00
|
|
|
$force="ngsign" if (grep(!exists $ngkeys{$_}, @affected));
|
2002-04-25 14:31:58 +00:00
|
|
|
}
|
|
|
|
elsif ($force eq "fallback")
|
|
|
|
{
|
2002-04-26 02:11:33 +00:00
|
|
|
# fallback-logic: ng-crypt or std-crypt, otherwise ngsign
|
|
|
|
# -force: ng- or std-crypt for all, otherwise ngsign
|
2002-04-25 14:31:58 +00:00
|
|
|
$force="ngsign"
|
|
|
|
if (grep(!exists $ngkeys{$_}
|
|
|
|
&& !exists $stdkeys{$_}, @affected));
|
|
|
|
}
|
|
|
|
|
|
|
|
# apply forced action to the affected addresses
|
|
|
|
map { $actions{$_}=$force; } (@affected);
|
|
|
|
logit("final force directive: $force")
|
|
|
|
if ($options{"d"});
|
|
|
|
# the first force-option wins, naturally.
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
|
|
|
|
# finally check the actions for fallback, ng or std and expand that
|
|
|
|
foreach $addr (@addrs)
|
|
|
|
{
|
|
|
|
if ($actions{$addr} eq "fallback")
|
|
|
|
{
|
|
|
|
($ngkeys{$addr} && ($actions{$addr}="ng"))
|
2002-04-27 15:49:50 +00:00
|
|
|
|| ($stdkeys{$addr} && ($actions{$addr}="std"))
|
2002-04-25 14:31:58 +00:00
|
|
|
|| ($actions{$addr}="ngsign");
|
|
|
|
}
|
|
|
|
elsif ($actions{$addr} eq "ng")
|
|
|
|
{
|
|
|
|
$actions{$addr}="ngsign" if (!$ngkeys{$addr});
|
|
|
|
}
|
|
|
|
elsif ($actions{$addr} eq "std")
|
|
|
|
{
|
|
|
|
$actions{$addr}="stdsign" if (!$stdkeys{$addr});
|
|
|
|
}
|
|
|
|
logit("final action: $addr -> $actions{$addr}") if ($options{"d"});
|
|
|
|
}
|
|
|
|
return %actions;
|
|
|
|
}
|
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
# logging and dying with a message
|
|
|
|
# does not return. one arg: the message to spit out
|
|
|
|
sub bailout
|
|
|
|
{
|
|
|
|
my ($msg)=@_;
|
2001-11-06 12:53:15 +00:00
|
|
|
|
2002-09-19 16:25:46 +00:00
|
|
|
logit($msg);
|
|
|
|
die($msg.'\n');
|
|
|
|
}
|
2001-11-06 12:53:15 +00:00
|
|
|
|