kuvert/0000755000000000000000000000000012272163721007267 5ustar kuvert/dot-kuvert0000444000000000000000000000306012244634626011321 0ustar # ~/.kuvert: example configuration file for kuvert v2 # options are given without leading whitespace # which key to sign with by default defaultkey 0x1234abcd # logging to syslog, which facility? defaults to no syslog syslog mail # no separate logfile logfile "" # who gets error reports mail-on-error you@some.domain # where to spool mails and temporary files queuedir /home/az/kuvert_queue tempdir /tmp/kuvert_temp # how often to check the queue, in seconds interval 60 # add an x-mailer header? identify f # add the explanatory mime preamble? preamble f # how to submit outbound mail: # # 1. via smtp # settings: msserver, msport, ssl, # ssl-cert, ssl-key, ssl-ca; # authenticating as msuser, mspass # # msserver some.server.com # msport 587 # ssl starttls # ssl-key mycerts/my.key.pem # ssl-cert mycerts/my.cert.pem # msuser smtp-username # mspass smtp-password # mspass-from-query-secret f # # 2. by using the msp program # msp /usr/sbin/sendmail -om -oi -oem can-detach t # maport 2587 # ma-user yourname # ma-pass somethingSECRET defaultaction fallback-all alwaystrust t use-agent t query-secret /usr/bin/q-agent get %s flush-secret /usr/bin/q-agent delete %s # action specifications for recipients # are given with some leading whitespace # multiple keys for somebody and you want a specific one? somebody@with.many.keys fallback,0x1234abcd # those don't want gpg-signed stuff @somewhere.com none # signed but not encrypted (he|they|others)@there.com signonly # majordomo and similar mailinglist systems get plain mail (majordomo|-request)@ none kuvert/README0000444000000000000000000000720512244634526010156 0ustar this is kuvert, a wrapper around sendmail or other MTAs that does gpg signing/signing+encrypting transparently, based on the content of your public keyring(s) and your preferences. how it works: ------------- you need to configure your MUA to submit mails to kuvert instead of directly. you configure kuvert either to present an SMTP server to your MUA, or you make your MUA to use kuvert_submit instead of executing /usr/sbin/sendmail. kuvert_submit will spool the mail in kuvert's queue iff there is a suitable configuration file. kuvert is the tool that takes care of mangling the email. it reads the queue periodically and handles emails in the queue: signing or encrypting the mail, then handing it over to /usr/lib/sendmail or an external SMTP server for transport. (why a queue? because i thought it might be useful to make sure that none of your emails leaves your system without kuvert handing it. you might be very paranoid, and kill kuvert whenever you leave your box (and remove the keyrings as well).) installation: ------------- on debian systems you simply install the kuvert package, construct a suitable .kuvert configuration file and off you go. an example config file is provided at /usr/share/doc/kuvert/examples/dot-kuvert. on other systems you need to do the following: you need perl perl 5.004+, gpg and a raft of perl modules: MIME::Parser, Mail::Address, Net::SMTPS, Sys::Hostname, Net::Server::Mail, Authen::SASL, IO::Socket::INET, Filehandle, File::Slurp, File::Temp, Fcntl and Time::HiRes. some of those are part of a standard perl intall, others you'll have to get from your nearest CPAN archive and install. optional: get linux-kernel keyutils package, the gpg-agent or some other passphrase cache of your choice. run make, make install DESTDIR=/ as root -> kuvert, kuvert_submit, the manpages and one helper module will be installed in /usr/bin, /usr/share/man/man1 and /usr/share/perl5/Net/Server/Mail/ESMTP/, respectively. configuration: -------------- read the manpages for kuvert(1) and kuvert_submit(1) and consult the example config file "dot-kuvert". you will need to create your own config file as ~/.kuvert. sorry, no autoconfig here: this step is too crucial for a mere robot to perform. then start kuvert and inject a testmail, look at the logs to check if everything works correctly. (historical note: kuvert came into existence in 1996 as pgpmail and was used only privately until 99, when it was extended and renamed to guard. some of my friends started using this software, and in 2001 it was finally re-christened kuvert, extended even further and debianized. in 2008 it received a major overhaul to also provide inbound smtp as submission mechanism, outbound smtp transport and better controllability via email addresses. until 2008 kuvert supported pgp2.x.) please report bugs to me, Alexander Zangerl, . The original source can always be found at: http://www.snafu.priv.at/kuvert/ Copyright (C) 1999-2013 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA kuvert/THANKS0000644000000000000000000000032507542317515010210 0ustar My thanks go to Robert Bihlmeyer Norbert Preining Robert Waldner for valuable hints and suggestions regarding this piece of software.kuvert/plainAUTH.pm0000644000000000000000000000775511031654332011423 0ustar package Net::Server::Mail::ESMTP::plainAUTH; use strict; use base qw(Net::Server::Mail::ESMTP::Extension); use MIME::Base64; use vars qw( $VERSION ); $VERSION = '1.0'; # the following are required by nsme::extension # but not documented :( sub init { my ($self,$parent)=@_; $self->{AUTH}=(); return $self; } # the smtp operations we add sub verb { return ( [ 'AUTH' => \&handle_auth, ],); } # what to add to the esmtp capabilities response sub keyword { return 'AUTH LOGIN PLAIN'; } # what options to allow for mail from: auth sub option { return (['MAIL', 'AUTH' => sub { return; }]); } # and the actual auth handler sub handle_auth { my ($self,$args)=@_; my ($method,$param); $args=~/^(LOGIN|PLAIN)\s*(.*)$/ && (($method,$param)=($1,$2)); if ($self->{AUTH}->{active}) { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication phases mixed up."); return undef; # if rv given, server shuts conn! } elsif ($self->{AUTH}->{completed}) { $self->reply(504,"Already authenticated."); return undef; } elsif (!$method) { $self->reply(501,"Unknown authentication method."); return undef; } $self->{AUTH}->{active}=$method; if ($param eq '*') { delete $self->{AUTH}->{active}; $self->reply(501, "Authentication cancelled."); return undef; } if ($method eq 'PLAIN') { if ($param) # plain: immediate with args { my (undef,$user,$pwd)=split(/\0/,decode_base64($param),3); if (!$user) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } return run_callback($self,$user,$pwd); } else # plain: or empty challenge and then response { $self->reply(334," "); # undocumented but crucial: direct stuff to this method $self->next_input_to(\&process_response); return undef; } } elsif ($method eq 'LOGIN') { # login is always two challenges $self->reply(334, "VXNlcm5hbWU6"); # username $self->next_input_to(\&process_response); return undef; } } # runs user-supplied callback on username and password # responds success if callback succeeds # sets complete if ok, clears active either way sub run_callback { my ($self,$user,$pass)=@_; my $ok; my $ref=$self->{callback}->{AUTH}; if (ref $ref eq 'ARRAY' && ref $ref->[0] eq 'CODE') { my $c=$ref->[0]; $ok=&$c($self,$user,$pass); } if ($ok) { $self->reply(235, "Authentication successful"); $self->{AUTH}->{completed}=1; } else { $self->reply(535,"Authentication failed."); } delete $self->{AUTH}->{active}; return undef; } # deals with any response, based on active method sub process_response { my ($self,$args)=@_; if (!$self->{AUTH}->{active} || $self->{AUTH}->{completed}) { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication phases mixed up."); return undef; } if (!$args) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } if ($self->{AUTH}->{active} eq "PLAIN") { # plain is easy: only one response containing everything my (undef,$user,$pwd)=split(/\0/,decode_base64($args),3); if (!$user) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } return run_callback($self,$user,$pwd); } elsif ($self->{AUTH}->{active} eq "LOGIN") { # uglier: two challenges for username+password my ($input)=split(/\0/,decode_base64($args)); # is this the second time round? if ($self->{AUTH}->{user}) { return run_callback($self,$self->{AUTH}->{user},$input); } else { # nope, first time: save username and challenge # for password $self->{AUTH}->{user}=$input; $self->reply(334, "UGFzc3dvcmQ6"); # password $self->next_input_to(\&process_response); return undef; } } else { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication mixed up."); return undef; } } 1; kuvert/Makefile0000644000000000000000000000202212220203670010712 0ustar # well, a simpler makefile is hardly imaginable... DESTDIR= # the version number of the package VERSION=$(shell sed -n '1s/^.*(\(.*\)).*$$/\1/p' debian/changelog) CPPFLAGS:=$(shell dpkg-buildflags --get CPPFLAGS) CFLAGS:=$(shell dpkg-buildflags --get CFLAGS) CXXFLAGS:=$(shell dpkg-buildflags --get CXXFLAGS) LDFLAGS:=$(shell dpkg-buildflags --get LDFLAGS) all: kuvert_submit clean: -rm -f kuvert_submit kuvert.tmp install: kuvert_submit kuvert install -d $(DESTDIR)/usr/bin $(DESTDIR)/usr/share/man/man1 \ $(DESTDIR)/usr/share/perl5/Net/Server/Mail/ESMTP/ install kuvert_submit $(DESTDIR)/usr/bin # fix the version number sed 's/INSERT_VERSION/$(VERSION)/' kuvert > kuvert.tmp install kuvert.tmp $(DESTDIR)/usr/bin/kuvert -rm kuvert.tmp install plainAUTH.pm $(DESTDIR)/usr/share/perl5/Net/Server/Mail/ESMTP/ pod2man --center="User Commands" -r Mail kuvert $(DESTDIR)/usr/share/man/man1/kuvert.1 pod2man --center="User Commands" -r Mail kuvert_submit.pod $(DESTDIR)/usr/share/man/man1/kuvert_submit.1 test: echo $(VERSION) kuvert/kuvert0000555000000000000000000017530512272163227010547 0ustar #!/usr/bin/perl # # this file is part of kuvert, a mailer wrapper that # does gpg signing/signing+encrypting transparently, based # on the content of your public keyring(s) and your preferences. # # copyright (c) 1999-2013 Alexander Zangerl # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # $Id: kuvert,v 2.30 2014/01/29 11:31:35 az Exp $ #-- 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 use Net::SMTPS; # for sending via smtp, which ssl use Sys::Hostname; # ditto use Net::Server::Mail::ESMTP; # for receiving via smtp use IO::Socket::INET; # ditto use FileHandle; use File::Slurp; use File::Temp qw(:mktemp); use Fcntl qw(:flock); use Time::HiRes; # some global stuff # the version number is inserted by make install my $version="INSERT_VERSION"; my $progname="kuvert"; $0=$progname; my $listenername="$progname-smtp"; # who are we gonna pretend to be today? my($username,$home)=(getpwuid($<))[0,7]; # where is the configuration file my $rcfile="$home/.kuvert"; my $timeout=600; # seconds to wait for gpg # configuration directives my (%config,$debug,%email2key); my %options; if (!getopts("dork",\%options) || @ARGV) { die "usage: $progname [-d] [-o] [-r|-k] -k: kill running $progname daemon -d: debug mode -r: reload keyrings and configfile -o: one-shot mode, run queue once and exit This is: $progname $version.\n"; } $debug=$options{"d"}; # now handle the kill/reload stuff my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp"); my $pidname="$progname.$<"; # kill a already running process # TERM for kill or HUP for rereading my $pidf="$piddir/$pidname.pid"; if ($options{"k"} || $options{"r"}) { my $sig=($options{"r"}?'USR1':'TERM'); my $ssig='TERM'; # the smtp listener must die my $pidf="$piddir/$pidname.pid"; die("no pid file found, can't signal any $progname\n") if (!-r $pidf); my @pids=read_file($pidf); for my $p (@pids) { chomp $p; $p=~s/[^0-9]//g; # only numbers # fixme: this is linux-centric, should be replaced # with proc::processtable my $fn="/proc/$p/cmdline"; if (-r $fn && (my $n=read_file($fn))=~/^$progname/) { my $s=($n=~/^$listenername/?$ssig:$sig); dlogit("sending sig $s to $p"); logit("can't send signal to process $p: $!\n") if (!kill($s,$p)); } } unlink($pidf) if ($options{k}); # remove the pidfile on kills exit 0; } chdir("/"); # now do the pidfile checking dance if (-f "$pidf") { open(PIDF,"+<$pidf") || &die("can't rw-open $pidf: $!\n"); } else { open(PIDF,">$pidf") || &die("can't w-open $pidf: $!\n"); } die("can't lock $pidf: $!\n") if (!flock(PIDF,LOCK_NB|LOCK_EX)); my @others=; my @badones; for my $p (@others) { chomp $p; $p=~s/[^0-9]//g; # only numbers # fixme: this is linux-centric, should be replaced # with proc::processtable if (-r "/proc/$p/cmdline" && (my $n=read_file("/proc/$p/cmdline"))=~/^$progname/) { push @badones,$p; } } die("other instance(s) with pids ".join(", ",@badones)." are running\n") if (@badones); # rewind to ready it for writing seek(PIDF,0,'SEEK_SET'); die("no configuration file exists. See $progname(1) for details.\n") if (!-e $rcfile); dlogit("reading config file"); # read in the config, setup dirs, logging, defaultkey etc. %config=&read_config; # log startup after config is read and logging prefs are known logit("$progname version $version starting"); # fire up smtp server, iff not oneshot if (!$options{o} && $config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off the smtp-to-queue daemon my $pid=&start_mailserver; # we, parent, update the pidfile with mailserver pid print PIDF "$pid\n"; } # install the handlers for conf reread $SIG{'USR1'}=\&handle_reload; # and the termination-handler map { $SIG{$_}=\&handle_term; } qw(HUP INT QUIT TERM); if (!$options{o} && $config{"can-detach"}) { my $pid=fork; if (!defined $pid) { &bailout("fork failed: $!"); } elsif ($pid) { exit 0; # parent is done } } print PIDF "$$\n"; close PIDF; # clears the lock # make things clean and ready. we're in sole command now. cleanup($config{tempdir},0); %email2key=&read_keyring; # let's use one parser object only; my $parser = MIME::Parser->new() || bailout("can't create mime parser object: $!"); # dump mime object to tempdir $parser->output_dir($config{tempdir}); # retain rfc1522-encoded headers, please $parser->decode_headers(0); # make the parser ignore all filename info and just invent filenames. $parser->filer->ignore_filename(1); # the main loop, left only via signal handler handle_term while (1) { &bailout("cant open $config{queuedir}: $!") if (!opendir(D,"$config{queuedir}")); my $file; foreach $file (sort grep(/^\d+$/,readdir(D))) { if (!open(FH,"$config{queuedir}/$file")) { logit("huh? $file suddenly disappeared? $!"); next; } # 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 $username"); my @res=process_file(*FH,"$config{queuedir}/$file"); if (@res) { rename("$config{queuedir}/$file","$config{queuedir}/.$file") || &bailout("cant rename $config{queuedir}/$file: $!"); alert("Problem with $config{queuedir}/$file", "Your mail \"$config{queuedir}/$file\" could not be processed and $progname has given up on it. Please review the following error details to determine what went wrong:\n\n", @res, "\n$progname has renamed the problematic mail to \"$config{queuedir}/.$file\"; if you want $progname to retry, rename it to an all-numeric filename. Otherwise you should delete the file.\n Please note that processing may have worked for SOME recipients already!\n"); } else { logit("done handling file $file"); unlink("$config{queuedir}/$file") || &bailout("cant unlink $config{queuedir}/$file: $!"); } # and clean up the cruft left behind, please! cleanup("$config{tempdir}",0); # unlock the file bailout("problem closing $config{queuedir}/$file: $!") if (!close(FH)); } closedir(D); &handle_term("oneshot mode") if ($options{o}); sleep($config{interval}); } # sign an entity and send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, envelope from, # signkey and recipients # returns nothing if fine, @error msgs otherwise sub sign_send { my ($ent,$dumpfile,$header,$from,$signkey,@recips)=@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $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-sha1"); # set/suppress the preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been signed conforming to RFC3156.\n", "You need GPG to check the signature.\n"]:[]); # add the passed entity as part $newent->add_part($ent); # generate the signature, repeat until proper passphrase given # or until gpg gives up with a different error indication my @res; while (1) { @res=&sign_encrypt($signkey,$dumpfile,$output,()); last if (!@res || $res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } return @res[1..$#res] if (@res || $res[0]); # fatal error: give up # attach the signature $newent->attach(Type => "application/pgp-signature", Path => $output, Filename => "signature.asc", Disposition => "inline", Description=> "Digital Signature", Encoding => "7bit"); # and send the resulting thing, not cleaning up return &send_entity($newent,$from,@recips); } # encrypt and sign an entity, send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, # envelope from address, recipient keys arrayref, recipient addresses # returns nothing if fine, @error msgs otherwise sub crypt_send { my ($ent,$dumpfile,$header,$from,$signkey,$rec_keys,@recips)=@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $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/suppress the new preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been encrypted conforming to RFC3156.\n", "You need GPG to view the content.\n"]:[]); # attach the needed dummy-part $newent->attach(Type=>"application/pgp-encrypted", Data=>"Version: 1\n", Encoding=>"7bit"); # generate the encrypted data, repeat until proper passphrase given my @res; while (1) { @res=&sign_encrypt($signkey,$dumpfile,$output,@{$rec_keys}); last if (!@res || $res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } return @res[1..$#res] if (@res || $res[0]); # fatal error: give up # attach the encrypted data $newent->attach(Type => "application/octet-stream", Path => $output, Filename => undef, Disposition => "inline", Encoding=>"7bit"); # and send the resulting thing return &send_entity($newent,$from,@recips); } # processes a file in the queue, # leaves the file in the queue # returns nothing if ok or @error msgs sub process_file { my ($fh,$file)=@_; my $in_ent; eval { $in_ent=$parser->parse(\$fh); }; return ("parsing $file failed","parser errors: $@",$parser->last_error) if ($@); # extract and clean envelope x-kuvert-from and -to my @erecips=extract_addresses($in_ent->head->get("x-kuvert-to")); my @efrom=extract_addresses($in_ent->head->get("x-kuvert-from")); $in_ent->head->delete("x-kuvert-to"); $in_ent->head->delete("x-kuvert-from"); # extract the from my @froms=extract_addresses($in_ent->head->get("from")); return "could not parse From: header!" if (!@froms); # envelope from is: x-kuvert-from if present or from my $fromaddr=@efrom?$efrom[0]->[0]:$froms[0]->[3]; my $signkey=$config{defaultkey}; # do we have a key override if ($froms[0]->[4]=~/key=([0-9a-fA-FxX]+)/) { $signkey=$1; dlogit("local signkey override: $signkey"); $in_ent->head->replace("from",$froms[0]->[3]); } # add version header $in_ent->head->add('X-Mailer',"$progname $version") if ($config{identify}); # extract and delete blanket instruction header my $override; if (lc($in_ent->head->get("x-kuvert"))=~ /^\s*(none|fallback|fallback-all|signonly)\s*$/) { $override=$1; } $in_ent->head->delete("x-kuvert"); # resend-request-header present and no more specific recipients given? # then send this as-it-is if (!@erecips && (my $rsto=$in_ent->head->get("resent-to"))) { logit("resending requested, doing so."); my @prstos=Mail::Address->parse($rsto); return "could not parse Resent-To: header!" if (!@prstos); my @rstos=map { $_->address } (@prstos); return send_entity($in_ent,$fromaddr,@rstos); } # extract and analyze normal and bcc recipients my @tos=extract_addresses($in_ent->head->get("to")); my @ccs=extract_addresses($in_ent->head->get("cc")); my @recips=(@tos,@ccs); my @recip_bcc=extract_addresses($in_ent->head->get("bcc")); # and don't leak Bcc... $in_ent->head->delete("bcc"); # replace to and cc with cleaned headers: we don't want to # leak directives my $newto=join(", ",map { $_->[3] } (@tos)); my $newcc=join(", ",map { $_->[3] } (@ccs)); $in_ent->head->replace("to",$newto); $in_ent->head->replace("cc",$newcc) if ($newcc); # 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... return("No recipients found!","The mail headers seem to be garbled.") if (!@erecips && !@recips && !@recip_bcc); # remember the addresses' nature my (%is_bcc); map { $is_bcc{$_->[0]}=1; } (@recip_bcc); # now deal with envelope-vs-mailheader recipients: # whatever the envelope says, wins. if (@erecips) { # no need to distinguish these otherwise my (%is_normal,%is_envelope); map { $is_normal{$_->[0]}=1; } (@recips); map { $is_envelope{$_->[0]}=1; } (@erecips); for my $e (@erecips) { # in the envelope but not the headers -> fake bcc if (!$is_normal{$e->[0]} && !$is_bcc{$e->[0]}) { push @recip_bcc,$e; $is_bcc{$e->[0]}=1; } } # in the headers but not the envelope -> ignore it my @reallyr; for my $n (@recips) { push @reallyr,$n if ($is_envelope{$n->[0]}); } @recips=@reallyr; } # figure out what to do for specific recipients my %actions=findaction($override,\@recips,\@recip_bcc); # send out unsigned mails first my @rawrecips=grep($actions{$_} eq "none",keys %actions); if (@rawrecips) { logit("sending mail (unchanged) to ".join(", ",@rawrecips)); my @res=send_entity($in_ent,$fromaddr,@rawrecips); return @res if (@res); } my ($orig_header,$cryptoin); # prepare various stuff we need only when encrypting or signing if(grep($_ ne "none",values(%actions))) { # 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; # content-* stays with the entity and the rest moves to orig_header foreach my $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. my $res=qp_fix_parts($in_ent); return $res if ($res); # now we've got a in entity which is ready to be encrypted/signed # and the mail-headers are saved in $orig_header # next we dump this entity into a file for crypto ops my $fh; ($fh,$cryptoin)=mkstemp($config{tempdir}."/cryptoin.XXXX"); return("can't create file $cryptoin: $!") if (!$fh); $in_ent->print($fh); close($fh); } # send the mail signed to the appropriate recips my @signto=grep($actions{$_} eq "signonly",keys %actions); if (@signto) { logit("sending mail (signed with $signkey) to ".join(", ",@signto)); my @res=&sign_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, @signto); return @res if (@res); } # send mail encrypted+signed to appropriate recips. # note: bcc's must be handled separately! my @encto=grep($actions{$_}!~/^(none|signonly)$/ && !$is_bcc{$_}, keys %actions); if (@encto) { logit("sending mail (encrypted+signed with $signkey) to " .join(", ",@encto)); my @enckeys = map { $actions{$_} } (@encto); my @res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, \@enckeys,@encto); return @res if (@res); } for my $bcc (grep($actions{$_}!~/^(none|signonly)$/ && $is_bcc{$_}, keys %actions)) { logit("sending mail (bcc,encrypted+signed with $signkey) to $bcc"); my @res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, [$actions{$bcc}],$bcc); return @res if (@res); } return; } # find the correct action for the given email addresses # input: override header, normal and bcc-addresses # returns hash with address as key, value is "none", "signonly" or key id sub findaction { my ($override,$normalref,$bccref)=@_; my(%actions,%specialkeys,$groupfallback); # address lookup in configured overrides foreach my $a (@{$normalref},@{$bccref}) { my $addr=$a->[0]; foreach (@{$config{overrides}}) { if ($addr =~ $_->{re}) { $actions{$addr}=$_->{action}; # remember config-file key overrides $specialkeys{$addr}=$_->{key} if ($_->{key}); last; } } # nothing configured? then default action $actions{$addr}||=($config{defaultaction}||"none"); dlogit("action $actions{$addr} for $addr"); # blanket override? then override the config but not where # "none" is specified if ($override && $actions{$addr} ne "none") { dlogit("override header: $override for $addr"); $actions{$addr}=$override; } # next: check individual action=x directives if ($a->[4] =~/action=(none|fallback-all|fallback|signonly)/) { my $thisaction=$1; $actions{$addr}=$thisaction; dlogit("local override: action $thisaction for $addr"); } if ($a->[4] =~/key=([0-9a-fA-FxX]+)/) { $specialkeys{$addr}=$1; dlogit("local key override: $specialkeys{$addr} for $addr"); } # now test for key existence and downgrade action to signonly # where necessary. if ($actions{$addr}=~/^fallback/) { # group fallback is relevant for normal recipients only $groupfallback||=($actions{$addr} eq "fallback-all") if (!grep($_->[0] eq $addr,@{$bccref})); $actions{$addr}=$specialkeys{$addr}||$email2key{$addr}||"signonly"; } } # were there any fallback-all? if so and also none or signonly present, # then all recips are downgraded. my @allactions=values %actions; if ($groupfallback && grep(/^(none|signonly)$/,@allactions)) { # time to downgrade everybody to signing... for my $a (@{$normalref}) { my $addr=$a->[0]; if ($actions{$addr} ne "none") { $actions{$addr}="signonly"; dlogit("downgrading to signonly for $addr"); } } } return %actions; } # parses an address-line, extracts all addresses from it # and splits them into address, phrase, comment, full and directive # returns array of arrays sub extract_addresses { my (@lines)=@_; my @details; for my $a (Mail::Address->parse(@lines)) { my ($addr,$comment,$phrase)=(lc($a->address),$a->comment,$a->phrase); # some name "directive,directive..." if ($phrase=~s/\s*\"([^\"]+)\"\s*//) { my $directive=$1; # clean the phrase up my $newa=Mail::Address->new($phrase,$addr,$comment); push @details,[$addr,$phrase,$comment,$newa->format,$directive]; } else { push @details,[$addr,$phrase,$comment,$a->format,undef]; } } return @details; } # traverses a mime entity and changes all parts with # type == text/plain, charset != us-ascii, transfer-encoding 8bit # to transfer-encoding qp. # input: entity, retval: undef if ok, error message otherwise sub qp_fix_parts { my ($entity)=@_; if ($entity->is_multipart) { foreach ($entity->parts) { my $res=&qp_fix_parts($_); return $res if ($res); } } 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") { return("changing Content-Transfer-Encoding failed") if ($entity->head->mime_attr("content-transfer-encoding" => "quoted-printable") !="quoted-printable"); } } return; } # log termination, cleanup, exit sub handle_term { my ($sig)=@_; $sig="SIG$sig" if (!$options{o}); logit("Termination requested ($sig), cleaning up"); &cleanup($config{tempdir},1); close $config{logfh} if ($config{logfh}); exit 0; } # reread configuration file and keyrings # no args or return value; intended as a sighandler. sub handle_reload { my ($sig)=@_; logit("received SIG$sig, reloading"); %config=&read_config; %email2key=&read_keyring; # restart mailserver if required # also update pidfile if ($config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off the smtp-to-queue daemon my $pid=&start_mailserver; open(PIDF,">$pidf") || &bailout("can't w-open $pidf: $!\n"); print PIDF "$$\n$pid\n"; close(PIDF); } } # remove temporary stuff left behind in directory $what # remove_what set: remove the dir, too. # exception on error, no retval sub cleanup { my ($what,$remove_what)=@_; my ($name,$res); opendir(F,$what) || bailout("cant opendir $what: $!"); foreach $name (readdir(F)) { next if ($name =~ /^\.{1,2}$/o); (-d "$what/$name")?&cleanup("$what/$name",1): (unlink("$what/$name") || bailout("cant unlink $what/$name: $!")); } closedir(F); $remove_what && (rmdir("$what") || bailout("cant rmdir $what: $!")); return; } # (re)reads the configuration file # calls bailout on problems # needs user-specific vars to be setup # returns %options on success, bailout on error sub read_config { my %options= ( defaultkey=>undef, identify=>undef, defaultaction=>"none", msserver=>undef, msuser=>undef, mspass=>undef, ssl=>undef, "ssl-cert"=>undef, "ssl-key"=>undef, "ssl-ca"=>undef, 'mspass-from-query-secret'=>undef, msport=>587, msp=>"/usr/sbin/sendmail -om -oi -oem", "use-agent"=>undef, syslog=>undef, logfile=>undef, queuedir=>"$home/.kuvert_queue", tempdir=>($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$username.$$", alwaystrust=>undef, interval=>60, "query-secret"=>"/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; stty echo; echo \$X'", "flush-secret"=>undef, "mail-on-error"=>undef, "can-detach"=>0, maport=>2587, "ma-user"=>undef, "ma-pass"=>undef, preamble=>1, ); my @over; &bailout("cant open $rcfile: $!") if (!open (F,$rcfile)); logit("reading config file"); my @stuff=; close F; for (@stuff) { chomp; next if (/^\s*\#/ || /^\s*$/); # strip comments and empty lines # trigger on old config-file style if (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my $nf=rewrite_conf(@stuff); die("Can't work with old config, terminating! $progname has found an old config file and attempted a ROUGH auto-conversion. The result has been left in $nf and likely needs to be adjusted for ${progname}'s new features. Please do so and restart $progname with the new config file in place.\n"); } if (/^\s+(\S+)\s+(fallback(-all)?(,(0x)?[a-fA-F0-9]+)?|signonly|none)\s*(\#.*)?$/) { my ($who,$action)=($1,$2); my $key; if ($action=~s/^(fallback(-all)?),((0x)?[a-fA-F0-9]+)/$1/) { $key=$3; } push @over,{"who"=>$who, "re"=>qr/$who/, "action"=>$action, "key"=>$key}; dlogit("got override $action " .($key?"key $key ":"")."for $who"); next; } if (/^\S/) { my ($key,$value)=split(/\s+/,$_,2); $key=lc($key); $value=~s/^(\"|\')(.*)\1$/$2/; bailout("unknown config key \"$key\"") if (!exists $options{$key}); # booleans if ($key =~ /^(identify|use-agent|alwaystrust|can-detach|mspass-from-query-secret|preamble)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0|1|t|f|on|off)$/i); $options{$key}=($value=~/^(1|on|t)$/); } # numbers elsif ($key =~ /^(msport|interval|maport)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^\d+$/); $options{$key}=$value; } # nothing or string elsif ($key =~ /^(ma-pass|ma-user|mail-on-error|msserver|ssl(-cert|-key|-ca)?|msuser|mspass)$/) { $options{$key}=$value; } # nothing or program and args elsif ($key eq "msp") { bailout("bad value \"$value\" for key \"$key\"") if ($value && !-x (split(/\s+/,$value))[0]); $options{$key}=$value; } # program with %s escape elsif ($key =~ /^(query-secret|flush-secret)$/) { my ($cmd,$args)=split(/\s+/,$value,2); bailout("bad value \"$value\" for key \"$key\"") if (!-x $cmd || $args!~/%s/); $options{$key}=$value; } # dirs to create elsif ($key=~/^(queuedir|tempdir)$/) { $options{$key}=$value; } # the rest are special cases elsif ($key eq "defaultkey") { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0x)?[a-f0-9]+$/i); $options{$key}=$value; } elsif ($key eq "defaultaction") { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^(fallback|fallback-all|signonly|none)$/); $options{$key}=$value; } elsif ($key eq "syslog") { # syslog: nothing or a facility bailout("bad value \"$value\" for key \"$key\"") if ($value && $value!~/^(authpriv|cron|daemon|ftp|kern|local[0-7]|lpr|mail|news|syslog|user|uucp)$/); $options{$key}=$value; } elsif ($key eq "logfile") { bailout("bad value \"$value\" for key \"$key\"") if (-e $value && !-w $value); if ($config{$key} ne $value) # deal with changing logfiles { close($config{logfh}) if (defined $config{logfh}); delete $config{logfh}; } $options{$key}=$value; } dlogit("got config $key=$value"); } } close F; # post-config-reading sanity checking if ($options{msserver} && $options{msuser}) { bailout("smtp auth requires mspass or mspass-from-query-secret options") if (!$options{mspass} && !$options{"mspass-from-query-secret"}); } # post-config-reading directory fixes for my $v ($options{queuedir},$options{tempdir}) { if (!-d $v) { mkdir($v,0700) or bailout("cannot create directory $v: $!\n"); } my @stat=stat($v); if ($stat[4] != $< or ($stat[2]&0777) != 0700) { bailout("directory $v does not belong to you or has bad mode."); } } $options{overrides}=\@over; return %options; } sub rewrite_conf { my @old=@_; my ($fh,$fn)=mkstemp(($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/config.XXXX"); my %xlat=qw(NGKEY defaultkey GETSECRET query-secret DELSECRET flush-secret MTA msp ALWAYSTRUST alwaystrust INTERVAL interval TEMPDIR tempdir QUEUEDIR queuedir LOGFILE logfile IDENTIFY identify); for (@old) { chomp; next if (/^\#/ || /^\s*$/); # strip comments and empty lines if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/) { my ($k,$v)=($1,$2); $v=~s/(std|ng)sign/signonly/; $v=~s/(std|ng)/fallback/; $v=~s/fallback-force/fallback-all/; print $fh ($k eq "DEFAULT"?"defaultaction":" $k")." $v\n\n"; } elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my ($k,$v)=($1,$2); if ($xlat{$k}) { $k=$xlat{$k}; print $fh "$k $v\n\n"; } } } close $fh; return $fn; } # read keyring # needs global %config,$debug # returns id-to-key hash, bailout on error sub read_keyring { my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); my %id2key; logit("reading keyring..."); my $tf="$config{tempdir}/subproc"; my @tmp=`gpg -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tf`; bailout("keyring reading failed: $?",(-r $tf && readfile($tf))) if ($?); logit("finished reading keyring"); my ($lastkey,$lasttype); foreach (@tmp) { my @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 $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 my $name; if ($info[9] =~ /(\s|<)([^\s<]+\@[^\s>]+)>?/) { $name=lc($2); } # 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) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } $id2key{$name}="0x$lastkey"; dlogit("got key 0x$lastkey type $info[3] for $name"); } else { dlogit("saved key 0x$lastkey, no address known yet"); } next; } else { # uid: associate the current address with the key # given in the most recent public key line if ($name) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring uid $name for 0x$lastkey, " ."reason: ".$badcauses{$info[1]}); next; } $id2key{$name}="0x$lastkey"; dlogit("got key (uid) 0x$lastkey for $name"); } else { dlogit("ignoring uid without valid address"); } } } return %id2key; } # send this mime entity out # if msserver+port known: use smtp, envelope from is $from # otherwise use local msp program with @recips # uses global %config # returns nothing if ok, @error messages otherwise sub send_entity { my ($ent,$from,@recips,)=@_; if ($config{msserver} && $config{msport}) { my $dom=hostname; my $s=Net::SMTPS->new( $config{msserver}, Port => $config{msport}, Hello => $dom, doSSL => $config{ssl}, SSL_key_file => $config{"ssl-key"}, SSL_cert_file => $config{"ssl-cert"}, SSL_ca_file => $config{"ssl-ca"} ); return("cannot connect to mail server ".$config{msserver}.": $!") if (!$s); # do smtp auth if asked to if ($config{msuser}) { my $authed; while (!$authed) { if (!$config{mspass} && $config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"query-secret"},"smtp-password"); $config{mspass}=`$cmd`; return("couldn't get smtp password via query-secret: $!") if (!$config{mspass}); chomp($config{mspass}); } $authed=$s->auth($config{msuser},$config{mspass}); # bailout if we can't requery if (!$authed) { # get rid of the apparently dud password and try again delete $config{mspass}; if ($config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"flush-secret"},"smtp-password"); system($cmd); # ignore the flushing result; best effort only } else { return("smtp auth failed: ".$s->code." ".$s->message); } } } } $s->mail($from) or return("mailserver rejected our from address \"$from\": ".$s->code." ".$s->message); my @okrecips=$s->to(@recips, { SkipBad => 1 }); if (@okrecips != @recips) { my %seen; map { $seen{$_}=1; } (@recips); map { ++$seen{$_}; } (@okrecips); my @missed=grep $seen{$_}==1, keys %seen; return ("mailserver rejected some recipients!", "rejected: ".join(", ",@missed), "info: ".$s->code." ".$s->message); } $s->data($ent->as_string) or return("mailserver rejected our data: ".$s->code." ".$s->message); $s->quit; } else { # pipeline to msp, but we do it ourselves: safe cmd handling my $pid=open(TOMTA,"|-"); return("cant open pipe to msp: $!") if (!defined $pid); if ($pid) { $ent->print(\*TOMTA); close(TOMTA) || return("error talking to msp: $?"); } else { my @cmd=split(/\s+/,$config{msp}); push @cmd,'-f',$from; push @cmd,@recips; exec(@cmd) || return("error executing msp: $!"); } } return; } # sign/encrypt a file # input: sign key, infile and outfile path, recipient keys # if encryption wanted. # input must be existing filename, outfile must not exist. # signkey overrides config-defaultkey, and is optional. # uses global %config # returns: undef if ok, 1 if bad passphrase, (2,errorinfo) otherwise sub sign_encrypt { my ($signkey,$infile,$outfile,@recips)=@_; my @cmd=qw(gpg -q -t -a --batch --status-fd 2); my ($precmd,$pid); push @cmd,"--always-trust" if ($config{alwaystrust}); $signkey=$config{defaultkey} if ($config{defaultkey} && !$signkey); push @cmd,"--default-key",$signkey if ($signkey); # should we leave the passphrase handling to gpg/gpg-agent? # otherwise, we run a query program in a pipeline # after determining what passphrase gpg is looking for if ($config{"use-agent"}) { push @cmd,"--use-agent"; } if (@recips) { push @cmd, qw(--encrypt --sign), map { ("-r",$_) } (@recips); } else { push @cmd,"--detach-sign"; } if (!$config{"use-agent"}) { # now determine which passphrase to query for: # run gpg once without data, and analyze the status text $pid=open(F,"-|"); if (!defined $pid) { return (2,"Error: could not run gpg: $!"); } elsif (!$pid) { # child: dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); exec(@cmd); bailout("exec gpg failed: $!\n"); } # read the status stuff in and determine the passphrase required for my $l () { if ($l=~/^\[GNUPG:\] NEED_PASSPHRASE ([a-fA-F0-9]+) ([a-fA-F0-9]+) \d+ \d+$/) { $precmd=sprintf($config{"query-secret"},"0x".substr($2,8)); push @cmd,"--passphrase-fd",0; last; } } close(F); } push @cmd,"-o",$outfile,$infile; # now run gpg, read back stdout/stderr $pid=open(F,"-|"); if (!defined $pid) { return (2, "Error: could not run gpg: $!"); } elsif (!$pid) { # with agent: simply run gpg if ($config{"use-agent"}) { # collapse stderr and stdout open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); exec(@cmd); bailout("exec gpg failed: $!\n"); } else { # without agent: run the query program # in yet another pipeline to gpg # read from child: query prog in child # whereas we run gpg my $pidc=open(G,"-|"); if (!defined($pidc)) { bailout("Error: couldn't fork: $!\n"); } elsif (!$pidc) { # child: run query prog with stderr separated exec($precmd); die("exec $precmd failed: $!\n"); } # parent: we run gpg # dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); open STDIN, ">&", \*G or bailout("can't dup stdin onto child-pipe: $!\n"); exec(@cmd); bailout("exec gpg failed: $!\n"); } } # outermost parent: read gpg status info my @output; eval { local $SIG{ALRM}=sub { die "alarm\n"; }; alarm $timeout; @output=; alarm 0; close(F); }; if ($@) { logit("gpg timeout!"); kill("TERM",$pid); return 1; } elsif ($?) { # no complaints if gpg just dislikes the passphrase return 1 if (grep(/(MISSING|BAD)_PASSPHRASE/,@output)); return (2,"Error: gpg terminated with $?", "Detailed error messages:",@output); } return; } # logs the argument strings to syslog and/or the logfile # uses global %config # returns nothing sub logit { my (@msgs)=@_; if ($config{logfile}) # our own logfile? { if (!$config{logfh}) # not open yet? { $config{logfh}=FileHandle->new(">>$config{logfile}"); die "can't open logfile $config{logfile}: $!\n" if (!$config{logfh}); $config{logfh}->autoflush(1); } print { $config{logfh} } scalar(localtime)." ".join("\n\t",@msgs)."\n"; } if ($config{syslog}) { setlogsock('unix'); openlog($progname,"pid,cons",$config{syslog}); syslog("notice",join("\n",@msgs)); closelog; } } # debug log to stderr sub dlogit { print STDERR join("\n",@_)."\n" if ($debug); } # alerts the user of some problem # this is done via the normal logging channels, # plus: stderr if can-detach is not set # plus: email if mail-on-error is set to some email addy # for email the program name plus first message line are used as subject # sender and recipient are set to mail-on-error config entry sub alert { my (@msgs)=@_; logit(@msgs); if (!$config{"can-detach"}) { print STDERR join("\n\t",@msgs)."\n"; } if ($config{"mail-on-error"}) { my $heading=shift @msgs; my $out=join("\n",@msgs); my $ent=MIME::Entity->build(From=>$config{"mail-on-error"}, To=>$config{"mail-on-error"}, Subject=>($progname.": $heading"), Data=>\$out); send_entity($ent,$config{"mail-on-error"},$config{"mail-on-error"}); } } # alert of a problem and die sub bailout { my (@msgs)=@_; $msgs[0]="Fatal: ".$msgs[0]; alert(@msgs); # don't bother writing to stderr if alert already took care of that exit(1) if (!$config{"can-detach"}); die(scalar(localtime).join("\n\t",@msgs)."\n"); } # returns pid of new mailserver process # dies if unsuccessful sub start_mailserver { # fork off the smtp-to-queue daemon my $pid=fork; if (!defined($pid)) { bailout("cannot fork: $!\n"); } elsif (!$pid) { # run mailserver, which does never reload the config $0=$listenername; close STDIN; close PIDF; # clears the inherited lock map { $SIG{$_}='DEFAULT'; } qw(USR1 HUP INT QUIT TERM); &accept_mail; } # parent return $pid; } # run a receive-only mailserver on localhost and spool to queue # does not terminate except signalled sub accept_mail { my $server = IO::Socket::INET->new(Listen=>1, ReuseAddr=>1, LocalAddr=>"127.0.0.1", LocalPort=>$config{"maport"},); bailout("setting up listening port failed: $!") if (!$server); while(my $conn = $server->accept) { my $esmtp = Net::Server::Mail::ESMTP->new(socket=>$conn); $esmtp->register('Net::Server::Mail::ESMTP::plainAUTH'); $esmtp->set_callback(MAIL=>\&req_auth); $esmtp->set_callback(RCPT=>\&req_auth); $esmtp->set_callback(AUTH=>\&check_auth); $esmtp->set_callback("DATA-INIT"=>\&start_mail); $esmtp->set_callback("DATA-PART"=>\&cont_mail); $esmtp->set_callback(DATA => \&finish_mail); $esmtp->process(); $conn->close(); } } sub check_auth { my ($session,$user,$pwd)=@_; return ($user eq $config{'ma-user'} and $pwd eq $config{'ma-pass'}); } sub req_auth { my ($session,$input)=@_; if (!$session->{AUTH}->{completed}) { return(0,530,"5.7.0 Authentication Required"); } return(0,550,"Invalid Address.") if (!extract_addresses($input)); return 1; } sub start_mail { my($session,$data) = @_; my @recipients = $session->get_recipients(); my $sender = $session->get_sender(); return(0,554,'No recipients given.') if (!@recipients); return(0,554,'No sender given.') if (!$sender); my $qid=join("",Time::HiRes::gettimeofday); my $fn=$config{queuedir}."/".$qid; if (!open(F,">$fn")) { alert("can't open new queuefile $fn: $!"); return(0,450,"can't create queuefile. please try again later."); } if (!flock(F,LOCK_NB|LOCK_EX)) { alert("can't lock queuefile $qid: $!"); return(0,450,"can't lock queuefile. please try again later."); } print F "X-Kuvert-From: $sender\nX-Kuvert-To: " .join(", ",@recipients)."\n"; logit("queueing email from $sender to ".join(", ",@recipients)); $session->{DATA}->{qfh}=\*F; $session->{DATA}->{qid}=$qid; return 1; } sub cont_mail { my ($session,$dr)=@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; return 1; } sub finish_mail { my ($session,$dr)=@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; my $qid=$session->{DATA}->{qid}; if (!close($session->{DATA}->{qfh})) { alert("could not close queuefile $qid: $!"); return(0,450,"could not close queuefile"); } logit("finished enqueueing mail $qid"); return(1,250,"Mail enqueued as $qid"); } __END__ =pod =head1 NAME kuvert - Automatically sign and/or encrypt emails based on the recipients =head1 SYNOPSIS kuvert [-d] [-o] [-r|-k] =head1 DESCRIPTION Kuvert is a tool to protect the integrity and secrecy of your outgoing email independent of your mail client and with minimal user interaction. It reads mails from its queue (or accepts SMTP submissions), analyzes the recipients and decides to whom it should encrypt and/or sign the mail. The resulting mail is coerced into the PGP-MIME framework defined in RFC3156 and finally sent to your outbound mail server. Kuvert uses GnuPG for all cryptographic tasks and is designed to interface cleanly with external secret caching tools. =head1 OPTIONS After startup kuvert periodically scans its queue directory and processes mails from there; depending on your GnuPG passphrase setup kuvert may daemonize itself. In either case, kuvert runs forever until actively terminated. Kuvert's behaviour is configured primarily using a configuration file, with exception of the following commandline options: =over =item -d Enables debugging mode: extra debugging information is written to STDERR. (This is independent of normal logging.) =item -o Enables one-shot mode: kuvert does not loop forever but processes only the current queue contents and then exits. Kuvert does also not start an SMTP listener in this mode. =item -r Tells a running kuvert daemon to reload the configuration file and the gpg keyring. This is equivalent to sending a SIGUSR1 to the respective process. =item -k Tells a running kuvert daemon to terminate cleanly. This is equivalent to sending a SIGTERM to the respective process. =back =head1 OPERATION At startup kuvert reads its configuration file and your gnugp keyring and remembers the association of email addresses to keys. Kuvert then works as a wrapper around your mail transfer agent (MTA): you author your emails like always but instead of sending them out directly you submit them to kuvert. Periodically kuvert scans its queue and processes any email therein. If your keyring contains a key for a recipient, kuvert will encrypt and sign the email to that recipient. If no key is available, kuvert will only (clear/detached-)sign the email. Subsequently, the email is sent onwards using your MTA program or SMTP. Emails to be processed can have any valid MIME structure; kuvert unpacks the MIME structure losslessly and repacks the (encrypted/signed) mail into a PGP/MIME object as described in RFC3156. The mail's structure is preserved. Signature and encryption cover all of the mail content with the exception of the top-level headers: for example the "Subject" header will be passed in clear, whereas any body or attached MIME object will be signed/encrypted. The encrypt-or-sign decision can be overridden on a per-address basis using the configuration file or, even more fine-grainedly, by using directives in the actual email. Kuvert can also be told not to modify an email at all. =head2 Submitting Emails to Kuvert Kuvert primarily relies on mails being dumped into its queue directory. Kuvert operates on files with numeric file names only. Anything that you store in its queue directory with such a filename will be treated as containing a single RFC2822-formatted email. However, no mainstream MUA supports such a drop-your-files-somewhere scheme, and therefore kuvert comes with a helper program called kuvert_submit (see L) which mimics sendmail's mail submission behaviour but feeds to the kuvert queue. If your MUA can be instructed to run a program for mail submission, kuvert_submit can be used. Alternatively, you can send your email to kuvert via SMTP. Kuvert comes with a built-in receive-only mail server, which feeds to the queue directory. As allowing others to submit emails for your signature would be silly and dangerous, kuvert's mail server only listens on the localhost IP address and requires that your MUA uses SMTP Authentication to ensure that only your submissions are accepted. If your MUA supports SMTP AUTH PLAIN or LOGIN and can be told to use localhost and a specific port for outbound email, then you can use this mechanism. =head2 Transporting Emails Onwards Kuvert can send outbound emails either by running a local MTA program or by speaking SMTP to some (fixed) outbound mail server of your choice. =head2 Recipients, Identities and the SMTP Envelope In general kuvert identifies recipients using the To, Cc, Bcc and Resent-To headers of the queued email. If the mechanism you used to submit the mail to kuvert did explicitely set recipients, then these B the headers within the email. This is the case if kuvert_submit is called with a list of recipients and no -t option and for SMTP submission. If kuvert enqueues email via inbound SMTP, the SMTP envelope B the email headers: recipients that are present in the envelope but not the headers are treated as Bcc'd, and recipients listed in the headers but not the envelope are B. Any Resent-To header is ignored for SMTP-submitted email. Only if no overriding recipients are given, kuvert checks the mail for a Resent-To header. If present, the email is sent out immediately to the Resent-To addresses I. (This is the standard "bounce" behaviour for MUAs that don't pass recipients on to an MSP/MTA directly.) When sending outbound email, kuvert usually uses the From header from the queued email as identity. If the email was queued via SMTP, the envelope again B the mail headers. Note that kuvert sets the envelope sender using "-f" if sending email via a local MTA program; if you are not sufficiently trusted by your MTA to do such, your mail may get an X-Authentication-Warning header tacked on that indicates your username and the fact that the envelope was set explicitely. =head2 Passphrase Handling Kuvert does not handle your precious keys' passphrases. You can either elect to use gpg-agent as an (on-demand or caching) passphrase store, or you can tell kuvert what program it should run to query for a passphrase when required. Such a query program will be run in a pipeline to GnuPG, and kuvert will not access, store or cache the passphrases themselves: there are better programs available for secret caching, eg. quintuple-agent or the Linux in-kernel keystorage (L). Kuvert interfaces cleanly with these. =head2 How Kuvert Decides What (Not) To Do For each recipient, kuvert can be told to apply one of four different actions: =over =item none The email is sent as-is (except for configuration directive removal). =item signonly The email is (clear/detached-) signed. =item fallback The email is encrypted and signed if there is a key available for this recipient or only signed. =item fallback-all The email is encrypted and signed if keys are available for B recipients, or only signed otherwise. Recipients whose action is set to "none" and Bcc'd recipients are not affected by this action. The fallback-all action is an "all-or-nothing" action as far as encryption is concerned and ensures that no mix of encrypted or unencrypted versions of this email are sent out: if we can we use encryption for everybody, or otherwise everybody gets it signed (or even unsigned). (Bcc'd recipients are the exception.) =back =head2 Specifying Actions Kuvert uses four sources for action specifications: directives in the individual email addresses, action directives in the configuration file, an X-Kuvert header in your email, and finally the default action given in the configuration file. =over =item 1. First kuvert looks for action directives in your configuration file. Such directives are given as action plus regular expression to be matched against an address, and the first matching directive is used. =item 2. If no matching directive is found, the default action given in the configuration file is applied. =item 3. Kuvert now checks for the presence of an X-Kuvert header: its content must be an action keyword, which is applied to all recipients of this email except the ones whose action at this stage is "none". (In other words: if you specify "no encryption/signing" for some addresses, then this cannot be overridden in a blanket fashion.) =item 4. Kuvert then analyzes each recipient email address. If an address has the format Some Text "action=someaction" ", kuvert strips the quoted part and overrides the addressee's action with someaction. =item 5. Finally kuvert checks if any recipient has action "fallback-all". If so, kuvert =over =item a) checks if any recipients (except Bcc'd) have action "signonly" or "none". If this is the case, all "fallback" and "fallback-all" actions are downgraded to "signonly". =item b) checks if keys for all recipients (except Bcc'd) are available. If not, all "fallback" and "fallback-all" actions are downgraded to "signonly". =back =item 6. Recipients which are given in a Bcc: header are always treated independently and separately from all others: any "fallback-all" action is downgraded to "fallback" for Bcc'd addresses, and if encryption is used, the email is encrypted separately so that no record of the Bcc'd recipient is visible in the email as sent out to the "normal" recipients. Also, any Bcc: header is removed before sending an email onwards. =back =head2 Key Selection Kuvert depends on the order of keys in your keyring to determine which key (of potentially many) with a given address should be used for encryption. By default kuvert uses the B key that it encounters for a given address. For people who have multiple keys for a single address this can cause problems, and therefore kuvert has override mechanisms for encryption key selection: You can specify a key to encrypt to for an address in the configuration file (see below), or you can override the key selection for and within a single mail: If the recipient address is given in the format Some Name "key=keyid" Kuvert will strip the double-quoted part and use this particular key for this recipient and for this single email. The keyid must be given as the hex key identifier. This mechanism overrides whatever associations your keyring contains and should be used with caution. Note that both key and action overrides can be given concurrently as a single comma-separated entry like this: Some Name "action=fallback,key=0x12345" The signing key can be overridden in a similar fashion: if the From address contains a "key=B" stanza, kuvert will use this key for signing this single email. =head1 CONFIGURATION The kuvert configuration file is plain text, blank lines and lines that start with "#" are ignored. The configuration has of two categories: options and address/action specifications. =head2 Address and Action Address+action specifications are given one per line. Such lines must start with some whitespace, followed by an address regexp, followed by some whitespace and the action keyword. For actions "fallback" and "fallback-all" kuvert also allows you to specify a single key identifier like this: "fallback,0x42BD645D". The remainder of the line is ignored. The address regexp is a full Perl regular expression and will be applied to the raw SMTP address (i.e. not to the comment or name in the email address), case-insensitively. The regular expression may need to be anchored with ^ and $; kuvert does not do that for you. You must give just the core of the regexp (no m// or //), like in this example: # don't confuse mailing list robots ^.*-request@.*$ none The action keyword must be one of "none", "signonly", "fallback" or "fallback-all"; see section L for semantics. Order of action specifications in the config file is significant: the search terminates on first match. =head2 Options Options are given one per line, and option lines must start with the option name followed by some whitespace. All options are case-sensitive. Depending on the option content, some or all of the remainder of the option line will be assigned as option value. Inline comments are not supported. In the following list of options angle brackets denote required arguments like this: defaultkey Options that have boolean arguments recognize "1", "on" and "t" as true and "0", "off", "f" as false (plus their upper-case versions). Other options have more restricted argument types; kuvert generally sanity-checks options at startup. =head2 Known Options =over =item syslog Whether kuvert should use syslog for logging, and if so, what facility to use. Default: nothing. This is independent of the logfile option below. =item logfile Whether kuvert should write log messages to a file, appending to it. Default: not set. This is independent of the syslog option above. =item mail-on-error If kuvert encounters serious or fatal errors, an email is sent back to this address if set. Default: undef. This email is sent in addition to the normal logging via syslog or logfile. =item queuedir Where kuvert and its helper programs store mails to be processed. Default: ~/.kuvert_queue. The directory is created if necessary. The directory must be owned by the user running kuvert and have mode 0700. =item tempdir Where kuvert stores temporary files. Default: a directory called kuvert.. in $TMPDIR or /tmp. The directory is created if necessary, and must be owned by the user running kuvert and have mode 0700. This directory is completely emptied after processing an email. =item identify Whether kuvert should add an X-Mailer header to outbound emails. Default: false. The X-Mailer header consists of the program name and version. =item preamble Whether kuvert should include an explanatory preamble in the generated MIME mail. Default: true =item interval This sets the queue checking interval in seconds. Default: 60 seconds. =item msserver Mail Submission Server for outbound email. Default: unset. If this is set, kuvert will use SMTP to send outbound emails. If not set, kuvert uses the mail submission program on the local machine. See msp below. =item msport The TCP port on which the Mail Submission Server listens. Default: 587. Ignored if msserver is not set. =item ssl Whether SSL or STARTTLS are to be used for outbound SMTP submission. The value must be either "starttls" to use STARTTLS or "ssl" for raw SSL. SSL encryption is not used if this option is unset. =item ssl-cert =item ssl-key =item ssl-ca If an SSL client certificate is to be presented to the SMTP server, set both ssl-cert and ssl-key. If your system-wide CA certificate setup doesn't include the certificate your SMTP server uses, set ssl-ca to point to a PEM file containing all the relevant CA certificates. All these are ignored if the ssl option isn't set. =item msuser The username to use for SMTP authentication at the Mail Submission Server. SMTP Auth is not attempted if msuser isn't set. Ignored if msserver is not set. =item mspass The password for SMTP authentication. Ignored if msserver or msuser are not set. =item mspass-from-query-secret Whether the mspass should be retrieved using the query-secret program instead of giving the mspass in the config file. Ignored if msserver or msuser are not set. If this option is set, the query-secret program will be used to ask for the "smtp-password" when the first mail is processed. The password will be cached if authentication succeeds or you will be asked again, until authentication succeeds. =item msp Defines the program kuvert should use to deliver email. Default: "/usr/sbin/sendmail -om -oi -oem". This is ignored if msserver is set. The argument must include the full path to the program, and the program must accept the common mail transfer agent arguments as defined in the Linux Standards Base (see L). =item can-detach Indicates to kuvert that it can background itself on startup, detaching from the terminal. Default: false. This is possible only if you either delegate passphrase handling to gpg-agent, or if your secret-query program does not require interaction via the original terminal (e.g. if it is an X11 program with its own window). =item maport Kuvert can accept email for processing via SMTP. This option sets the TCP port kuvert listens on (localhost only). Default: 2587. Ignored if ma-user and ma-pass are not both set. If you want to use this mechanism, tell your mail program to use localhost or 127.0.0.1 as outgoing mail server and enable SMTP Authentication (see below). =item ma-user This option sets the required SMTP authentication username for accepting mails via SMTP. Default: undef. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. Kuvert does not accept emails for processing via SMTP unless you prove your identity with SMTP Authentication (or anybody on your local machine could use kuvert to send emails signed by you!). Kuvert currently supports only AUTH PLAIN and LOGIN (which is not a major problem as we listen on the loopback interface only). This option sets the username kuvert recognizes as yours. This can be anything and doesn't have to be a real account name. =item ma-pass This option sets the password your mail user agent must use for SMTP Authentication if submitting mails via SMTP. Default: unset. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. This password does not have to be (actually shouldn't be) your real account's password. Note that using SMTP submission requires that you protect your kuvert configuration file with strict permissions (0600 is suggested). =item defaultkey Specifies a default key to use as signing key. Default: unset, which means GnuPG gets to choose (usually the first available secret key). Can be overridden in the From: address, see section L. =item defaultaction Which action is to be taken if no overrides are found for a recipient. Default: none. See section L for recognized actions. =item alwaystrust Whether gpg should be told to trust all keys for encryption or not. Default: false. =item use-agent Whether kuvert should delegate all passphrase handling to the gpg-agent and call gpg with appropriate options. Default: false. If not set, kuvert will ask the user (or some nominated passphrase store) for passphrases on demand. =item query-secret Tells kuvert which program to use for passphrase retrieval. Default: "/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; \ stty echo; echo $X'" Ignored if use-agent is set. Kuvert does not store passphrases internally but rather runs the indicated program in a pipeline with gpg when signing. If you use a passphrase store (like the Linux-kernel keyutils or secret-agent or the like), enter your retrieval program here. The program is run with kuvert's environment, the first %s in the argument spec is replaced with the hex keyid and the passphrase is expected on stdout. The exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. Note that the default query program prohibits kuvert from backgrounding itself. =item flush-secret This program is called to invalidate an external passphrase cache if kuvert is notified by gpg of the passphrase being invalid. Default: undef. Ignored if use-agent is set. The program is run with kuvert's environment and with the first %s of its argument spec being replaced by the hex keyid in question. Its exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. =back =head1 DIAGNOSTICS Kuvert usually logs informational messages to syslog and/or its own logfile, both of which can be disabled and adjusted. If kuvert detects a fault that makes successful processing of a particular email impossible, kuvert will report that on STDERR (if not detached) and also email an error report if the option mail-on-error is enabled. Such partially or completely unprocessed mails are left in the queue but are renamed (the name is prefixed with "failed."); it is up to you to either remove such leftovers or rename them to something all-numeric once the problem has been resolved. The behaviour is similar if fatal problems are encountered; after alerting kuvert will terminate with exit code 1. =head1 ENVIRONMENT AND SIGNALS Kuvert itself uses only on environment variable: $TMPDIR provides the fallback location for kuvert's temporary directory. Kuvert passes its complete environment to child processes, namely gpg and any passphrase-query programs. On reception of SIGUSR1, kuvert reloads its configuration file and keyring. Any one of SIGHUP, SIGINT, SIGQUIT and SIGTERM causes kuvert to terminate cleanly, invalidating the passphrases if a query program is used. All other signals are ignored. =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. =item ~/.kuvert_queue The default queue directory. =item /tmp/kuvert.pid.EuidE holds the pid of a running kuvert daemon. =back =head1 SEE ALSO L, L, RFC3156, RFC2440, RFC2015 =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2008 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut kuvert/GPL0000644000000000000000000004307007364543003007641 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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 (at your option) 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. kuvert/kuvert_submit.c0000444000000000000000000001535411157374151012346 0ustar /* * $Id: kuvert_submit.c,v 2.1 2009/03/16 06:57:45 az Exp $ * * this file is part of kuvert, a wrapper around your mta that * does pgp/gpg signing/signing+encrypting transparently, based * on the content of your public keyring(s) and your preferences. * * copyright (c) 1999-2008 Alexander Zangerl * * 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. * */ #include #include #include #include #include #include #include #include #include #include #define CONFFILE "/.kuvert" #define DEFAULT_QUEUEDIR "/.kuvert_queue" #define BUFLEN 65536 #define FALLBACKMTA "/usr/sbin/sendmail" #define BAILOUT(a,...) {fprintf(stderr,"%s: ",argv[0]); fprintf(stderr, a "\n",##__VA_ARGS__);syslog(LOG_ERR,a,##__VA_ARGS__); exit(1);} int main(int argc,char **argv) { struct passwd *pwentry; /* fixme sizes */ char filen[256],buffer[BUFLEN],dirn[256]; int res,c,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; int direct=1,norecips=0,testmode=0,i; /* determine whether to queue stuff or to call sendmail directly: if there is no proper config file for kuvert in $HOME or if given -bv go direct, otherwise we enqueue. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); for(i=1;ipw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no "CONFFILE" config file, running sendmail"); } else { direct=0; /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) { p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncasecmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } } fclose(cf); } } /* direct to sendmail requested? */ if (direct) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); cpw_dir,DEFAULT_QUEUEDIR) ==-1) BAILOUT("overlong dirname, suspicous.",NULL); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* scan the arguments for the LSB-mandated options: we ignore any options but -f, -t. /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:t"))!=-1) { if (c=='?') continue; /* we simply ignore uninteresting options */ else if (c=='f') { /* pass the intended envelope sender */ fprintf(out,"X-Kuvert-From: %s\n",optarg); } else if (c=='t') { /* no recipients given, so we don't need to pass any recips */ norecips=1; } } if (!norecips && optind # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # $Id: kuvert,v 2.29 2013/11/25 11:50:08 az Exp az $ #-- 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 use Net::SMTPS; # for sending via smtp, which ssl use Sys::Hostname; # ditto use Net::Server::Mail::ESMTP; # for receiving via smtp use IO::Socket::INET; # ditto use FileHandle; use File::Slurp; use File::Temp qw(:mktemp); use Fcntl qw(:flock); use Time::HiRes; # some global stuff # the version number is inserted by make install my $version="INSERT_VERSION"; my $progname="kuvert"; $0=$progname; my $listenername="$progname-smtp"; # who are we gonna pretend to be today? my($username,$home)=(getpwuid($<))[0,7]; # where is the configuration file my $rcfile="$home/.kuvert"; my $timeout=600; # seconds to wait for gpg # configuration directives my (%config,$debug,%email2key); my %options; if (!getopts("dork",\%options) || @@ARGV) { die "usage: $progname [-d] [-o] [-r|-k] -k: kill running $progname daemon -d: debug mode -r: reload keyrings and configfile -o: one-shot mode, run queue once and exit This is: $progname $version.\n"; } $debug=$options{"d"}; # now handle the kill/reload stuff my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp"); my $pidname="$progname.$<"; # kill a already running process # TERM for kill or HUP for rereading my $pidf="$piddir/$pidname.pid"; if ($options{"k"} || $options{"r"}) { my $sig=($options{"r"}?'USR1':'TERM'); my $ssig='TERM'; # the smtp listener must die my $pidf="$piddir/$pidname.pid"; die("no pid file found, can't signal any $progname\n") if (!-r $pidf); my @@pids=read_file($pidf); for my $p (@@pids) { chomp $p; $p=~s/[^0-9]//g; # only numbers # fixme: this is linux-centric, should be replaced # with proc::processtable my $fn="/proc/$p/cmdline"; if (-r $fn && (my $n=read_file($fn))=~/^$progname/) { my $s=($n=~/^$listenername/?$ssig:$sig); dlogit("sending sig $s to $p"); logit("can't send signal to process $p: $!\n") if (!kill($s,$p)); } } unlink($pidf) if ($options{k}); # remove the pidfile on kills exit 0; } chdir("/"); # now do the pidfile checking dance if (-f "$pidf") { open(PIDF,"+<$pidf") || &die("can't rw-open $pidf: $!\n"); } else { open(PIDF,">$pidf") || &die("can't w-open $pidf: $!\n"); } die("can't lock $pidf: $!\n") if (!flock(PIDF,LOCK_NB|LOCK_EX)); my @@others=; my @@badones; for my $p (@@others) { chomp $p; $p=~s/[^0-9]//g; # only numbers # fixme: this is linux-centric, should be replaced # with proc::processtable if (-r "/proc/$p/cmdline" && (my $n=read_file("/proc/$p/cmdline"))=~/^$progname/) { push @@badones,$p; } } die("other instance(s) with pids ".join(", ",@@badones)." are running\n") if (@@badones); # rewind to ready it for writing seek(PIDF,0,'SEEK_SET'); die("no configuration file exists. See $progname(1) for details.\n") if (!-e $rcfile); dlogit("reading config file"); # read in the config, setup dirs, logging, defaultkey etc. %config=&read_config; # log startup after config is read and logging prefs are known logit("$progname version $version starting"); # fire up smtp server, iff not oneshot if (!$options{o} && $config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off the smtp-to-queue daemon my $pid=&start_mailserver; # we, parent, update the pidfile with mailserver pid print PIDF "$pid\n"; } # install the handlers for conf reread $SIG{'USR1'}=\&handle_reload; # and the termination-handler map { $SIG{$_}=\&handle_term; } qw(HUP INT QUIT TERM); if (!$options{o} && $config{"can-detach"}) { my $pid=fork; if (!defined $pid) { &bailout("fork failed: $!"); } elsif ($pid) { exit 0; # parent is done } } print PIDF "$$\n"; close PIDF; # clears the lock # make things clean and ready. we're in sole command now. cleanup($config{tempdir},0); %email2key=&read_keyring; # let's use one parser object only; my $parser = MIME::Parser->new() || bailout("can't create mime parser object: $!"); # dump mime object to tempdir $parser->output_dir($config{tempdir}); # retain rfc1522-encoded headers, please $parser->decode_headers(0); # make the parser ignore all filename info and just invent filenames. $parser->filer->ignore_filename(1); # the main loop, left only via signal handler handle_term while (1) { &bailout("cant open $config{queuedir}: $!") if (!opendir(D,"$config{queuedir}")); my $file; foreach $file (sort grep(/^\d+$/,readdir(D))) { if (!open(FH,"$config{queuedir}/$file")) { logit("huh? $file suddenly disappeared? $!"); next; } # 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 $username"); my @@res=process_file(*FH,"$config{queuedir}/$file"); if (@@res) { rename("$config{queuedir}/$file","$config{queuedir}/.$file") || &bailout("cant rename $config{queuedir}/$file: $!"); alert("Problem with $config{queuedir}/$file", "Your mail \"$config{queuedir}/$file\" could not be processed and $progname has given up on it. Please review the following error details to determine what went wrong:\n\n", @@res, "\n$progname has renamed the problematic mail to \"$config{queuedir}/.$file\"; if you want $progname to retry, rename it to an all-numeric filename. Otherwise you should delete the file.\n Please note that processing may have worked for SOME recipients already!\n"); } else { logit("done handling file $file"); unlink("$config{queuedir}/$file") || &bailout("cant unlink $config{queuedir}/$file: $!"); } # and clean up the cruft left behind, please! cleanup("$config{tempdir}",0); # unlock the file bailout("problem closing $config{queuedir}/$file: $!") if (!close(FH)); } closedir(D); &handle_term("oneshot mode") if ($options{o}); sleep($config{interval}); } # sign an entity and send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, envelope from, # signkey and recipients # returns nothing if fine, @@error msgs otherwise sub sign_send { my ($ent,$dumpfile,$header,$from,$signkey,@@recips)=@@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $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-sha1"); # set/suppress the preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been signed conforming to RFC3156.\n", "You need GPG to check the signature.\n"]:[]); # add the passed entity as part $newent->add_part($ent); # generate the signature, repeat until proper passphrase given # or until gpg gives up with a different error indication my @@res; while (1) { @@res=&sign_encrypt($signkey,$dumpfile,$output,()); last if (!@@res || $res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } return @@res[1..$#res] if (@@res || $res[0]); # fatal error: give up # attach the signature $newent->attach(Type => "application/pgp-signature", Path => $output, Filename => "signature.asc", Disposition => "inline", Description=> "Digital Signature", Encoding => "7bit"); # and send the resulting thing, not cleaning up return &send_entity($newent,$from,@@recips); } # encrypt and sign an entity, send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, # envelope from address, recipient keys arrayref, recipient addresses # returns nothing if fine, @@error msgs otherwise sub crypt_send { my ($ent,$dumpfile,$header,$from,$signkey,$rec_keys,@@recips)=@@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $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/suppress the new preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been encrypted conforming to RFC3156.\n", "You need GPG to view the content.\n"]:[]); # attach the needed dummy-part $newent->attach(Type=>"application/pgp-encrypted", Data=>"Version: 1\n", Encoding=>"7bit"); # generate the encrypted data, repeat until proper passphrase given my @@res; while (1) { @@res=&sign_encrypt($signkey,$dumpfile,$output,@@{$rec_keys}); last if (!@@res || $res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } return @@res[1..$#res] if (@@res || $res[0]); # fatal error: give up # attach the encrypted data $newent->attach(Type => "application/octet-stream", Path => $output, Filename => undef, Disposition => "inline", Encoding=>"7bit"); # and send the resulting thing return &send_entity($newent,$from,@@recips); } # processes a file in the queue, # leaves the file in the queue # returns nothing if ok or @@error msgs sub process_file { my ($fh,$file)=@@_; my $in_ent; eval { $in_ent=$parser->parse(\$fh); }; return ("parsing $file failed","parser errors: $@@",$parser->last_error) if ($@@); # extract and clean envelope x-kuvert-from and -to my @@erecips=extract_addresses($in_ent->head->get("x-kuvert-to")); my @@efrom=extract_addresses($in_ent->head->get("x-kuvert-from")); $in_ent->head->delete("x-kuvert-to"); $in_ent->head->delete("x-kuvert-from"); # extract the from my @@froms=extract_addresses($in_ent->head->get("from")); return "could not parse From: header!" if (!@@froms); # envelope from is: x-kuvert-from if present or from my $fromaddr=@@efrom?$efrom[0]->[0]:$froms[0]->[3]; my $signkey=$config{defaultkey}; # do we have a key override if ($froms[0]->[4]=~/key=([0-9a-fA-FxX]+)/) { $signkey=$1; dlogit("local signkey override: $signkey"); $in_ent->head->replace("from",$froms[0]->[3]); } # add version header $in_ent->head->add('X-Mailer',"$progname $version") if ($config{identify}); # extract and delete blanket instruction header my $override; if (lc($in_ent->head->get("x-kuvert"))=~ /^\s*(none|fallback|fallback-all|signonly)\s*$/) { $override=$1; } $in_ent->head->delete("x-kuvert"); # resend-request-header present and no more specific recipients given? # then send this as-it-is if (!@@erecips && (my $rsto=$in_ent->head->get("resent-to"))) { logit("resending requested, doing so."); my @@prstos=Mail::Address->parse($rsto); return "could not parse Resent-To: header!" if (!@@prstos); my @@rstos=map { $_->address } (@@prstos); return send_entity($in_ent,$fromaddr,@@rstos); } # extract and analyze normal and bcc recipients my @@tos=extract_addresses($in_ent->head->get("to")); my @@ccs=extract_addresses($in_ent->head->get("cc")); my @@recips=(@@tos,@@ccs); my @@recip_bcc=extract_addresses($in_ent->head->get("bcc")); # and don't leak Bcc... $in_ent->head->delete("bcc"); # replace to and cc with cleaned headers: we don't want to # leak directives my $newto=join(", ",map { $_->[3] } (@@tos)); my $newcc=join(", ",map { $_->[3] } (@@ccs)); $in_ent->head->replace("to",$newto); $in_ent->head->replace("cc",$newcc) if ($newcc); # 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... return("No recipients found!","The mail headers seem to be garbled.") if (!@@erecips && !@@recips && !@@recip_bcc); # remember the addresses' nature my (%is_bcc); map { $is_bcc{$_->[0]}=1; } (@@recip_bcc); # now deal with envelope-vs-mailheader recipients: # whatever the envelope says, wins. if (@@erecips) { # no need to distinguish these otherwise my (%is_normal,%is_envelope); map { $is_normal{$_->[0]}=1; } (@@recips); map { $is_envelope{$_->[0]}=1; } (@@erecips); for my $e (@@erecips) { # in the envelope but not the headers -> fake bcc if (!$is_normal{$e->[0]} && !$is_bcc{$e->[0]}) { push @@recip_bcc,$e; $is_bcc{$e->[0]}=1; } } # in the headers but not the envelope -> ignore it my @@reallyr; for my $n (@@recips) { push @@reallyr,$n if ($is_envelope{$n->[0]}); } @@recips=@@reallyr; } # figure out what to do for specific recipients my %actions=findaction($override,\@@recips,\@@recip_bcc); # send out unsigned mails first my @@rawrecips=grep($actions{$_} eq "none",keys %actions); if (@@rawrecips) { logit("sending mail (unchanged) to ".join(", ",@@rawrecips)); my @@res=send_entity($in_ent,$fromaddr,@@rawrecips); return @@res if (@@res); } my ($orig_header,$cryptoin); # prepare various stuff we need only when encrypting or signing if(grep($_ ne "none",values(%actions))) { # 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; # content-* stays with the entity and the rest moves to orig_header foreach my $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. my $res=qp_fix_parts($in_ent); return $res if ($res); # now we've got a in entity which is ready to be encrypted/signed # and the mail-headers are saved in $orig_header # next we dump this entity into a file for crypto ops my $fh; ($fh,$cryptoin)=mkstemp($config{tempdir}."/cryptoin.XXXX"); return("can't create file $cryptoin: $!") if (!$fh); $in_ent->print($fh); close($fh); } # send the mail signed to the appropriate recips my @@signto=grep($actions{$_} eq "signonly",keys %actions); if (@@signto) { logit("sending mail (signed with $signkey) to ".join(", ",@@signto)); my @@res=&sign_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, @@signto); return @@res if (@@res); } # send mail encrypted+signed to appropriate recips. # note: bcc's must be handled separately! my @@encto=grep($actions{$_}!~/^(none|signonly)$/ && !$is_bcc{$_}, keys %actions); if (@@encto) { logit("sending mail (encrypted+signed with $signkey) to " .join(", ",@@encto)); my @@enckeys = map { $actions{$_} } (@@encto); my @@res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, \@@enckeys,@@encto); return @@res if (@@res); } for my $bcc (grep($actions{$_}!~/^(none|signonly)$/ && $is_bcc{$_}, keys %actions)) { logit("sending mail (bcc,encrypted+signed with $signkey) to $bcc"); my @@res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, [$actions{$bcc}],$bcc); return @@res if (@@res); } return; } # find the correct action for the given email addresses # input: override header, normal and bcc-addresses # returns hash with address as key, value is "none", "signonly" or key id sub findaction { my ($override,$normalref,$bccref)=@@_; my(%actions,%specialkeys,$groupfallback); # address lookup in configured overrides foreach my $a (@@{$normalref},@@{$bccref}) { my $addr=$a->[0]; foreach (@@{$config{overrides}}) { if ($addr =~ $_->{re}) { $actions{$addr}=$_->{action}; # remember config-file key overrides $specialkeys{$addr}=$_->{key} if ($_->{key}); last; } } # nothing configured? then default action $actions{$addr}||=($config{defaultaction}||"none"); dlogit("action $actions{$addr} for $addr"); # blanket override? then override the config but not where # "none" is specified if ($override && $actions{$addr} ne "none") { dlogit("override header: $override for $addr"); $actions{$addr}=$override; } # next: check individual action=x directives if ($a->[4] =~/action=(none|fallback-all|fallback|signonly)/) { my $thisaction=$1; $actions{$addr}=$thisaction; dlogit("local override: action $thisaction for $addr"); } if ($a->[4] =~/key=([0-9a-fA-FxX]+)/) { $specialkeys{$addr}=$1; dlogit("local key override: $specialkeys{$addr} for $addr"); } # now test for key existence and downgrade action to signonly # where necessary. if ($actions{$addr}=~/^fallback/) { # group fallback is relevant for normal recipients only $groupfallback||=($actions{$addr} eq "fallback-all") if (!grep($_->[0] eq $addr,@@{$bccref})); $actions{$addr}=$specialkeys{$addr}||$email2key{$addr}||"signonly"; } } # were there any fallback-all? if so and also none or signonly present, # then all recips are downgraded. my @@allactions=values %actions; if ($groupfallback && grep(/^(none|signonly)$/,@@allactions)) { # time to downgrade everybody to signing... for my $a (@@{$normalref}) { my $addr=$a->[0]; if ($actions{$addr} ne "none") { $actions{$addr}="signonly"; dlogit("downgrading to signonly for $addr"); } } } return %actions; } # parses an address-line, extracts all addresses from it # and splits them into address, phrase, comment, full and directive # returns array of arrays sub extract_addresses { my (@@lines)=@@_; my @@details; for my $a (Mail::Address->parse(@@lines)) { my ($addr,$comment,$phrase)=(lc($a->address),$a->comment,$a->phrase); # some name "directive,directive..." if ($phrase=~s/\s*\"([^\"]+)\"\s*//) { my $directive=$1; # clean the phrase up my $newa=Mail::Address->new($phrase,$addr,$comment); push @@details,[$addr,$phrase,$comment,$newa->format,$directive]; } else { push @@details,[$addr,$phrase,$comment,$a->format,undef]; } } return @@details; } # traverses a mime entity and changes all parts with # type == text/plain, charset != us-ascii, transfer-encoding 8bit # to transfer-encoding qp. # input: entity, retval: undef if ok, error message otherwise sub qp_fix_parts { my ($entity)=@@_; if ($entity->is_multipart) { foreach ($entity->parts) { my $res=&qp_fix_parts($_); return $res if ($res); } } 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") { return("changing Content-Transfer-Encoding failed") if ($entity->head->mime_attr("content-transfer-encoding" => "quoted-printable") !="quoted-printable"); } } return; } # log termination, cleanup, exit sub handle_term { my ($sig)=@@_; $sig="SIG$sig" if (!$options{o}); logit("Termination requested ($sig), cleaning up"); &cleanup($config{tempdir},1); close $config{logfh} if ($config{logfh}); exit 0; } # reread configuration file and keyrings # no args or return value; intended as a sighandler. sub handle_reload { my ($sig)=@@_; logit("received SIG$sig, reloading"); %config=&read_config; %email2key=&read_keyring; # restart mailserver if required # also update pidfile if ($config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off the smtp-to-queue daemon my $pid=&start_mailserver; open(PIDF,">$pidf") || &bailout("can't w-open $pidf: $!\n"); print PIDF "$$\n$pid\n"; close(PIDF); } } # remove temporary stuff left behind in directory $what # remove_what set: remove the dir, too. # exception on error, no retval sub cleanup { my ($what,$remove_what)=@@_; my ($name,$res); opendir(F,$what) || bailout("cant opendir $what: $!"); foreach $name (readdir(F)) { next if ($name =~ /^\.{1,2}$/o); (-d "$what/$name")?&cleanup("$what/$name",1): (unlink("$what/$name") || bailout("cant unlink $what/$name: $!")); } closedir(F); $remove_what && (rmdir("$what") || bailout("cant rmdir $what: $!")); return; } # (re)reads the configuration file # calls bailout on problems # needs user-specific vars to be setup # returns %options on success, bailout on error sub read_config { my %options= ( defaultkey=>undef, identify=>undef, defaultaction=>"none", msserver=>undef, msuser=>undef, mspass=>undef, ssl=>undef, "ssl-cert"=>undef, "ssl-key"=>undef, "ssl-ca"=>undef, 'mspass-from-query-secret'=>undef, msport=>587, msp=>"/usr/sbin/sendmail -om -oi -oem", "use-agent"=>undef, syslog=>undef, logfile=>undef, queuedir=>"$home/.kuvert_queue", tempdir=>($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$username.$$", alwaystrust=>undef, interval=>60, "query-secret"=>"/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; stty echo; echo \$X'", "flush-secret"=>undef, "mail-on-error"=>undef, "can-detach"=>0, maport=>2587, "ma-user"=>undef, "ma-pass"=>undef, preamble=>1, ); my @@over; &bailout("cant open $rcfile: $!") if (!open (F,$rcfile)); logit("reading config file"); my @@stuff=; close F; for (@@stuff) { chomp; next if (/^\s*\#/ || /^\s*$/); # strip comments and empty lines # trigger on old config-file style if (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my $nf=rewrite_conf(@@stuff); die("Can't work with old config, terminating! $progname has found an old config file and attempted a ROUGH auto-conversion. The result has been left in $nf and likely needs to be adjusted for ${progname}'s new features. Please do so and restart $progname with the new config file in place.\n"); } if (/^\s+(\S+)\s+(fallback(-all)?(,(0x)?[a-fA-F0-9]+)?|signonly|none)\s*(\#.*)?$/) { my ($who,$action)=($1,$2); my $key; if ($action=~s/^(fallback(-all)?),((0x)?[a-fA-F0-9]+)/$1/) { $key=$3; } push @@over,{"who"=>$who, "re"=>qr/$who/, "action"=>$action, "key"=>$key}; dlogit("got override $action " .($key?"key $key ":"")."for $who"); next; } if (/^\S/) { my ($key,$value)=split(/\s+/,$_,2); $key=lc($key); $value=~s/^(\"|\')(.*)\1$/$2/; bailout("unknown config key \"$key\"") if (!exists $options{$key}); # booleans if ($key =~ /^(identify|use-agent|alwaystrust|can-detach|mspass-from-query-secret|preamble)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0|1|t|f|on|off)$/i); $options{$key}=($value=~/^(1|on|t)$/); } # numbers elsif ($key =~ /^(msport|interval|maport)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^\d+$/); $options{$key}=$value; } # nothing or string elsif ($key =~ /^(ma-pass|ma-user|mail-on-error|msserver|ssl(-cert|-key|-ca)?|msuser|mspass)$/) { $options{$key}=$value; } # nothing or program and args elsif ($key eq "msp") { bailout("bad value \"$value\" for key \"$key\"") if ($value && !-x (split(/\s+/,$value))[0]); $options{$key}=$value; } # program with %s escape elsif ($key =~ /^(query-secret|flush-secret)$/) { my ($cmd,$args)=split(/\s+/,$value,2); bailout("bad value \"$value\" for key \"$key\"") if (!-x $cmd || $args!~/%s/); $options{$key}=$value; } # dirs to create elsif ($key=~/^(queuedir|tempdir)$/) { $options{$key}=$value; } # the rest are special cases elsif ($key eq "defaultkey") { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0x)?[a-f0-9]+$/i); $options{$key}=$value; } elsif ($key eq "defaultaction") { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^(fallback|fallback-all|signonly|none)$/); $options{$key}=$value; } elsif ($key eq "syslog") { # syslog: nothing or a facility bailout("bad value \"$value\" for key \"$key\"") if ($value && $value!~/^(authpriv|cron|daemon|ftp|kern|local[0-7]|lpr|mail|news|syslog|user|uucp)$/); $options{$key}=$value; } elsif ($key eq "logfile") { bailout("bad value \"$value\" for key \"$key\"") if (-e $value && !-w $value); if ($config{$key} ne $value) # deal with changing logfiles { close($config{logfh}) if (defined $config{logfh}); delete $config{logfh}; } $options{$key}=$value; } dlogit("got config $key=$value"); } } close F; # post-config-reading sanity checking if ($options{msserver} && $options{msuser}) { bailout("smtp auth requires mspass or mspass-from-query-secret options") if (!$options{mspass} && !$options{"mspass-from-query-secret"}); } # post-config-reading directory fixes for my $v ($options{queuedir},$options{tempdir}) { if (!-d $v) { mkdir($v,0700) or bailout("cannot create directory $v: $!\n"); } my @@stat=stat($v); if ($stat[4] != $< or ($stat[2]&0777) != 0700) { bailout("directory $v does not belong to you or has bad mode."); } } $options{overrides}=\@@over; return %options; } sub rewrite_conf { my @@old=@@_; my ($fh,$fn)=mkstemp(($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/config.XXXX"); my %xlat=qw(NGKEY defaultkey GETSECRET query-secret DELSECRET flush-secret MTA msp ALWAYSTRUST alwaystrust INTERVAL interval TEMPDIR tempdir QUEUEDIR queuedir LOGFILE logfile IDENTIFY identify); for (@@old) { chomp; next if (/^\#/ || /^\s*$/); # strip comments and empty lines if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/) { my ($k,$v)=($1,$2); $v=~s/(std|ng)sign/signonly/; $v=~s/(std|ng)/fallback/; $v=~s/fallback-force/fallback-all/; print $fh ($k eq "DEFAULT"?"defaultaction":" $k")." $v\n\n"; } elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my ($k,$v)=($1,$2); if ($xlat{$k}) { $k=$xlat{$k}; print $fh "$k $v\n\n"; } } } close $fh; return $fn; } # read keyring # needs global %config,$debug # returns id-to-key hash, bailout on error sub read_keyring { my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); my %id2key; logit("reading keyring..."); my $tf="$config{tempdir}/subproc"; my @@tmp=`gpg -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tf`; bailout("keyring reading failed: $?",(-r $tf && readfile($tf))) if ($?); logit("finished reading keyring"); my ($lastkey,$lasttype); foreach (@@tmp) { my @@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 $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 my $name; if ($info[9] =~ /(\s|<)([^\s<]+\@@[^\s>]+)>?/) { $name=lc($2); } # 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) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } $id2key{$name}="0x$lastkey"; dlogit("got key 0x$lastkey type $info[3] for $name"); } else { dlogit("saved key 0x$lastkey, no address known yet"); } next; } else { # uid: associate the current address with the key # given in the most recent public key line if ($name) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring uid $name for 0x$lastkey, " ."reason: ".$badcauses{$info[1]}); next; } $id2key{$name}="0x$lastkey"; dlogit("got key (uid) 0x$lastkey for $name"); } else { dlogit("ignoring uid without valid address"); } } } return %id2key; } # send this mime entity out # if msserver+port known: use smtp, envelope from is $from # otherwise use local msp program with @@recips # uses global %config # returns nothing if ok, @@error messages otherwise sub send_entity { my ($ent,$from,@@recips,)=@@_; if ($config{msserver} && $config{msport}) { my $dom=hostname; my $s=Net::SMTPS->new( $config{msserver}, Port => $config{msport}, Hello => $dom, doSSL => $config{ssl}, SSL_key_file => $config{"ssl-key"}, SSL_cert_file => $config{"ssl-cert"}, SSL_ca_file => $config{"ssl-ca"} ); return("cannot connect to mail server ".$config{msserver}.": $!") if (!$s); # do smtp auth if asked to if ($config{msuser}) { my $authed; while (!$authed) { if (!$config{mspass} && $config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"query-secret"},"smtp-password"); $config{mspass}=`$cmd`; return("couldn't get smtp password via query-secret: $!") if (!$config{mspass}); chomp($config{mspass}); } $authed=$s->auth($config{msuser},$config{mspass}); # bailout if we can't requery if (!$authed) { # get rid of the apparently dud password and try again delete $config{mspass}; if ($config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"flush-secret"},"smtp-password"); system($cmd); # ignore the flushing result; best effort only } else { return("smtp auth failed: ".$s->code." ".$s->message); } } } } $s->mail($from) or return("mailserver rejected our from address \"$from\": ".$s->code." ".$s->message); my @@okrecips=$s->to(@@recips, { SkipBad => 1 }); if (@@okrecips != @@recips) { my %seen; map { $seen{$_}=1; } (@@recips); map { ++$seen{$_}; } (@@okrecips); my @@missed=grep $seen{$_}==1, keys %seen; return ("mailserver rejected some recipients!", "rejected: ".join(", ",@@missed), "info: ".$s->code." ".$s->message); } $s->data($ent->as_string) or return("mailserver rejected our data: ".$s->code." ".$s->message); $s->quit; } else { # pipeline to msp, but we do it ourselves: safe cmd handling my $pid=open(TOMTA,"|-"); return("cant open pipe to msp: $!") if (!defined $pid); if ($pid) { $ent->print(\*TOMTA); close(TOMTA) || return("error talking to msp: $?"); } else { my @@cmd=split(/\s+/,$config{msp}); push @@cmd,'-f',$from; push @@cmd,@@recips; exec(@@cmd) || return("error executing msp: $!"); } } return; } # sign/encrypt a file # input: sign key, infile and outfile path, recipient keys # if encryption wanted. # input must be existing filename, outfile must not exist. # signkey overrides config-defaultkey, and is optional. # uses global %config # returns: undef if ok, 1 if bad passphrase, (2,errorinfo) otherwise sub sign_encrypt { my ($signkey,$infile,$outfile,@@recips)=@@_; my @@cmd=qw(gpg -q -t -a --batch --status-fd 2); my ($precmd,$pid); push @@cmd,"--always-trust" if ($config{alwaystrust}); $signkey=$config{defaultkey} if ($config{defaultkey} && !$signkey); push @@cmd,"--default-key",$signkey if ($signkey); # should we leave the passphrase handling to gpg/gpg-agent? # otherwise, we run a query program in a pipeline # after determining what passphrase gpg is looking for if ($config{"use-agent"}) { push @@cmd,"--use-agent"; } if (@@recips) { push @@cmd, qw(--encrypt --sign), map { ("-r",$_) } (@@recips); } else { push @@cmd,"--detach-sign"; } if (!$config{"use-agent"}) { # now determine which passphrase to query for: # run gpg once without data, and analyze the status text $pid=open(F,"-|"); if (!defined $pid) { return (2,"Error: could not run gpg: $!"); } elsif (!$pid) { # child: dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); exec(@@cmd); bailout("exec gpg failed: $!\n"); } # read the status stuff in and determine the passphrase required for my $l () { if ($l=~/^\[GNUPG:\] NEED_PASSPHRASE ([a-fA-F0-9]+) ([a-fA-F0-9]+) \d+ \d+$/) { $precmd=sprintf($config{"query-secret"},"0x".substr($2,8)); push @@cmd,"--passphrase-fd",0; last; } } close(F); } push @@cmd,"-o",$outfile,$infile; # now run gpg, read back stdout/stderr $pid=open(F,"-|"); if (!defined $pid) { return (2, "Error: could not run gpg: $!"); } elsif (!$pid) { # with agent: simply run gpg if ($config{"use-agent"}) { # collapse stderr and stdout open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); exec(@@cmd); bailout("exec gpg failed: $!\n"); } else { # without agent: run the query program # in yet another pipeline to gpg # read from child: query prog in child # whereas we run gpg my $pidc=open(G,"-|"); if (!defined($pidc)) { bailout("Error: couldn't fork: $!\n"); } elsif (!$pidc) { # child: run query prog with stderr separated exec($precmd); die("exec $precmd failed: $!\n"); } # parent: we run gpg # dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); open STDIN, ">&", \*G or bailout("can't dup stdin onto child-pipe: $!\n"); exec(@@cmd); bailout("exec gpg failed: $!\n"); } } # outermost parent: read gpg status info my @@output; eval { local $SIG{ALRM}=sub { die "alarm\n"; }; alarm $timeout; @@output=; alarm 0; close(F); }; if ($@@) { logit("gpg timeout!"); kill("TERM",$pid); return 1; } elsif ($?) { # no complaints if gpg just dislikes the passphrase return 1 if (grep(/(MISSING|BAD)_PASSPHRASE/,@@output)); return (2,"Error: gpg terminated with $?", "Detailed error messages:",@@output); } return; } # logs the argument strings to syslog and/or the logfile # uses global %config # returns nothing sub logit { my (@@msgs)=@@_; if ($config{logfile}) # our own logfile? { if (!$config{logfh}) # not open yet? { $config{logfh}=FileHandle->new(">>$config{logfile}"); die "can't open logfile $config{logfile}: $!\n" if (!$config{logfh}); $config{logfh}->autoflush(1); } print { $config{logfh} } scalar(localtime)." ".join("\n\t",@@msgs)."\n"; } if ($config{syslog}) { setlogsock('unix'); openlog($progname,"pid,cons",$config{syslog}); syslog("notice",join("\n",@@msgs)); closelog; } } # debug log to stderr sub dlogit { print STDERR join("\n",@@_)."\n" if ($debug); } # alerts the user of some problem # this is done via the normal logging channels, # plus: stderr if can-detach is not set # plus: email if mail-on-error is set to some email addy # for email the program name plus first message line are used as subject # sender and recipient are set to mail-on-error config entry sub alert { my (@@msgs)=@@_; logit(@@msgs); if (!$config{"can-detach"}) { print STDERR join("\n\t",@@msgs)."\n"; } if ($config{"mail-on-error"}) { my $heading=shift @@msgs; my $out=join("\n",@@msgs); my $ent=MIME::Entity->build(From=>$config{"mail-on-error"}, To=>$config{"mail-on-error"}, Subject=>($progname.": $heading"), Data=>\$out); send_entity($ent,$config{"mail-on-error"},$config{"mail-on-error"}); } } # alert of a problem and die sub bailout { my (@@msgs)=@@_; $msgs[0]="Fatal: ".$msgs[0]; alert(@@msgs); # don't bother writing to stderr if alert already took care of that exit(1) if (!$config{"can-detach"}); die(scalar(localtime).join("\n\t",@@msgs)."\n"); } # returns pid of new mailserver process # dies if unsuccessful sub start_mailserver { # fork off the smtp-to-queue daemon my $pid=fork; if (!defined($pid)) { bailout("cannot fork: $!\n"); } elsif (!$pid) { # run mailserver, which does never reload the config $0=$listenername; close STDIN; close PIDF; # clears the inherited lock map { $SIG{$_}='DEFAULT'; } qw(USR1 HUP INT QUIT TERM); &accept_mail; } # parent return $pid; } # run a receive-only mailserver on localhost and spool to queue # does not terminate except signalled sub accept_mail { my $server = IO::Socket::INET->new(Listen=>1, ReuseAddr=>1, LocalAddr=>"127.0.0.1", LocalPort=>$config{"maport"},); bailout("setting up listening port failed: $!") if (!$server); while(my $conn = $server->accept) { my $esmtp = Net::Server::Mail::ESMTP->new(socket=>$conn); $esmtp->register('Net::Server::Mail::ESMTP::plainAUTH'); $esmtp->set_callback(MAIL=>\&req_auth); $esmtp->set_callback(RCPT=>\&req_auth); $esmtp->set_callback(AUTH=>\&check_auth); $esmtp->set_callback("DATA-INIT"=>\&start_mail); $esmtp->set_callback("DATA-PART"=>\&cont_mail); $esmtp->set_callback(DATA => \&finish_mail); $esmtp->process(); $conn->close(); } } sub check_auth { my ($session,$user,$pwd)=@@_; return ($user eq $config{'ma-user'} and $pwd eq $config{'ma-pass'}); } sub req_auth { my ($session,$input)=@@_; if (!$session->{AUTH}->{completed}) { return(0,530,"5.7.0 Authentication Required"); } return(0,550,"Invalid Address.") if (!extract_addresses($input)); return 1; } sub start_mail { my($session,$data) = @@_; my @@recipients = $session->get_recipients(); my $sender = $session->get_sender(); return(0,554,'No recipients given.') if (!@@recipients); return(0,554,'No sender given.') if (!$sender); my $qid=join("",Time::HiRes::gettimeofday); my $fn=$config{queuedir}."/".$qid; if (!open(F,">$fn")) { alert("can't open new queuefile $fn: $!"); return(0,450,"can't create queuefile. please try again later."); } if (!flock(F,LOCK_NB|LOCK_EX)) { alert("can't lock queuefile $qid: $!"); return(0,450,"can't lock queuefile. please try again later."); } print F "X-Kuvert-From: $sender\nX-Kuvert-To: " .join(", ",@@recipients)."\n"; logit("queueing email from $sender to ".join(", ",@@recipients)); $session->{DATA}->{qfh}=\*F; $session->{DATA}->{qid}=$qid; return 1; } sub cont_mail { my ($session,$dr)=@@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; return 1; } sub finish_mail { my ($session,$dr)=@@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; my $qid=$session->{DATA}->{qid}; if (!close($session->{DATA}->{qfh})) { alert("could not close queuefile $qid: $!"); return(0,450,"could not close queuefile"); } logit("finished enqueueing mail $qid"); return(1,250,"Mail enqueued as $qid"); } __END__ =pod =head1 NAME kuvert - Automatically sign and/or encrypt emails based on the recipients =head1 SYNOPSIS kuvert [-d] [-o] [-r|-k] =head1 DESCRIPTION Kuvert is a tool to protect the integrity and secrecy of your outgoing email independent of your mail client and with minimal user interaction. It reads mails from its queue (or accepts SMTP submissions), analyzes the recipients and decides to whom it should encrypt and/or sign the mail. The resulting mail is coerced into the PGP-MIME framework defined in RFC3156 and finally sent to your outbound mail server. Kuvert uses GnuPG for all cryptographic tasks and is designed to interface cleanly with external secret caching tools. =head1 OPTIONS After startup kuvert periodically scans its queue directory and processes mails from there; depending on your GnuPG passphrase setup kuvert may daemonize itself. In either case, kuvert runs forever until actively terminated. Kuvert's behaviour is configured primarily using a configuration file, with exception of the following commandline options: =over =item -d Enables debugging mode: extra debugging information is written to STDERR. (This is independent of normal logging.) =item -o Enables one-shot mode: kuvert does not loop forever but processes only the current queue contents and then exits. Kuvert does also not start an SMTP listener in this mode. =item -r Tells a running kuvert daemon to reload the configuration file and the gpg keyring. This is equivalent to sending a SIGUSR1 to the respective process. =item -k Tells a running kuvert daemon to terminate cleanly. This is equivalent to sending a SIGTERM to the respective process. =back =head1 OPERATION At startup kuvert reads its configuration file and your gnugp keyring and remembers the association of email addresses to keys. Kuvert then works as a wrapper around your mail transfer agent (MTA): you author your emails like always but instead of sending them out directly you submit them to kuvert. Periodically kuvert scans its queue and processes any email therein. If your keyring contains a key for a recipient, kuvert will encrypt and sign the email to that recipient. If no key is available, kuvert will only (clear/detached-)sign the email. Subsequently, the email is sent onwards using your MTA program or SMTP. Emails to be processed can have any valid MIME structure; kuvert unpacks the MIME structure losslessly and repacks the (encrypted/signed) mail into a PGP/MIME object as described in RFC3156. The mail's structure is preserved. Signature and encryption cover all of the mail content with the exception of the top-level headers: for example the "Subject" header will be passed in clear, whereas any body or attached MIME object will be signed/encrypted. The encrypt-or-sign decision can be overridden on a per-address basis using the configuration file or, even more fine-grainedly, by using directives in the actual email. Kuvert can also be told not to modify an email at all. =head2 Submitting Emails to Kuvert Kuvert primarily relies on mails being dumped into its queue directory. Kuvert operates on files with numeric file names only. Anything that you store in its queue directory with such a filename will be treated as containing a single RFC2822-formatted email. However, no mainstream MUA supports such a drop-your-files-somewhere scheme, and therefore kuvert comes with a helper program called kuvert_submit (see L) which mimics sendmail's mail submission behaviour but feeds to the kuvert queue. If your MUA can be instructed to run a program for mail submission, kuvert_submit can be used. Alternatively, you can send your email to kuvert via SMTP. Kuvert comes with a built-in receive-only mail server, which feeds to the queue directory. As allowing others to submit emails for your signature would be silly and dangerous, kuvert's mail server only listens on the localhost IP address and requires that your MUA uses SMTP Authentication to ensure that only your submissions are accepted. If your MUA supports SMTP AUTH PLAIN or LOGIN and can be told to use localhost and a specific port for outbound email, then you can use this mechanism. =head2 Transporting Emails Onwards Kuvert can send outbound emails either by running a local MTA program or by speaking SMTP to some (fixed) outbound mail server of your choice. =head2 Recipients, Identities and the SMTP Envelope In general kuvert identifies recipients using the To, Cc, Bcc and Resent-To headers of the queued email. If the mechanism you used to submit the mail to kuvert did explicitely set recipients, then these B the headers within the email. This is the case if kuvert_submit is called with a list of recipients and no -t option and for SMTP submission. If kuvert enqueues email via inbound SMTP, the SMTP envelope B the email headers: recipients that are present in the envelope but not the headers are treated as Bcc'd, and recipients listed in the headers but not the envelope are B. Any Resent-To header is ignored for SMTP-submitted email. Only if no overriding recipients are given, kuvert checks the mail for a Resent-To header. If present, the email is sent out immediately to the Resent-To addresses I. (This is the standard "bounce" behaviour for MUAs that don't pass recipients on to an MSP/MTA directly.) When sending outbound email, kuvert usually uses the From header from the queued email as identity. If the email was queued via SMTP, the envelope again B the mail headers. Note that kuvert sets the envelope sender using "-f" if sending email via a local MTA program; if you are not sufficiently trusted by your MTA to do such, your mail may get an X-Authentication-Warning header tacked on that indicates your username and the fact that the envelope was set explicitely. =head2 Passphrase Handling Kuvert does not handle your precious keys' passphrases. You can either elect to use gpg-agent as an (on-demand or caching) passphrase store, or you can tell kuvert what program it should run to query for a passphrase when required. Such a query program will be run in a pipeline to GnuPG, and kuvert will not access, store or cache the passphrases themselves: there are better programs available for secret caching, eg. quintuple-agent or the Linux in-kernel keystorage (L). Kuvert interfaces cleanly with these. =head2 How Kuvert Decides What (Not) To Do For each recipient, kuvert can be told to apply one of four different actions: =over =item none The email is sent as-is (except for configuration directive removal). =item signonly The email is (clear/detached-) signed. =item fallback The email is encrypted and signed if there is a key available for this recipient or only signed. =item fallback-all The email is encrypted and signed if keys are available for B recipients, or only signed otherwise. Recipients whose action is set to "none" and Bcc'd recipients are not affected by this action. The fallback-all action is an "all-or-nothing" action as far as encryption is concerned and ensures that no mix of encrypted or unencrypted versions of this email are sent out: if we can we use encryption for everybody, or otherwise everybody gets it signed (or even unsigned). (Bcc'd recipients are the exception.) =back =head2 Specifying Actions Kuvert uses four sources for action specifications: directives in the individual email addresses, action directives in the configuration file, an X-Kuvert header in your email, and finally the default action given in the configuration file. =over =item 1. First kuvert looks for action directives in your configuration file. Such directives are given as action plus regular expression to be matched against an address, and the first matching directive is used. =item 2. If no matching directive is found, the default action given in the configuration file is applied. =item 3. Kuvert now checks for the presence of an X-Kuvert header: its content must be an action keyword, which is applied to all recipients of this email except the ones whose action at this stage is "none". (In other words: if you specify "no encryption/signing" for some addresses, then this cannot be overridden in a blanket fashion.) =item 4. Kuvert then analyzes each recipient email address. If an address has the format Some Text "action=someaction" ", kuvert strips the quoted part and overrides the addressee's action with someaction. =item 5. Finally kuvert checks if any recipient has action "fallback-all". If so, kuvert =over =item a) checks if any recipients (except Bcc'd) have action "signonly" or "none". If this is the case, all "fallback" and "fallback-all" actions are downgraded to "signonly". =item b) checks if keys for all recipients (except Bcc'd) are available. If not, all "fallback" and "fallback-all" actions are downgraded to "signonly". =back =item 6. Recipients which are given in a Bcc: header are always treated independently and separately from all others: any "fallback-all" action is downgraded to "fallback" for Bcc'd addresses, and if encryption is used, the email is encrypted separately so that no record of the Bcc'd recipient is visible in the email as sent out to the "normal" recipients. Also, any Bcc: header is removed before sending an email onwards. =back =head2 Key Selection Kuvert depends on the order of keys in your keyring to determine which key (of potentially many) with a given address should be used for encryption. By default kuvert uses the B key that it encounters for a given address. For people who have multiple keys for a single address this can cause problems, and therefore kuvert has override mechanisms for encryption key selection: You can specify a key to encrypt to for an address in the configuration file (see below), or you can override the key selection for and within a single mail: If the recipient address is given in the format Some Name "key=keyid" Kuvert will strip the double-quoted part and use this particular key for this recipient and for this single email. The keyid must be given as the hex key identifier. This mechanism overrides whatever associations your keyring contains and should be used with caution. Note that both key and action overrides can be given concurrently as a single comma-separated entry like this: Some Name "action=fallback,key=0x12345" The signing key can be overridden in a similar fashion: if the From address contains a "key=B" stanza, kuvert will use this key for signing this single email. =head1 CONFIGURATION The kuvert configuration file is plain text, blank lines and lines that start with "#" are ignored. The configuration has of two categories: options and address/action specifications. =head2 Address and Action Address+action specifications are given one per line. Such lines must start with some whitespace, followed by an address regexp, followed by some whitespace and the action keyword. For actions "fallback" and "fallback-all" kuvert also allows you to specify a single key identifier like this: "fallback,0x42BD645D". The remainder of the line is ignored. The address regexp is a full Perl regular expression and will be applied to the raw SMTP address (i.e. not to the comment or name in the email address), case-insensitively. The regular expression may need to be anchored with ^ and $; kuvert does not do that for you. You must give just the core of the regexp (no m// or //), like in this example: # don't confuse mailing list robots ^.*-request@@.*$ none The action keyword must be one of "none", "signonly", "fallback" or "fallback-all"; see section L for semantics. Order of action specifications in the config file is significant: the search terminates on first match. =head2 Options Options are given one per line, and option lines must start with the option name followed by some whitespace. All options are case-sensitive. Depending on the option content, some or all of the remainder of the option line will be assigned as option value. Inline comments are not supported. In the following list of options angle brackets denote required arguments like this: defaultkey Options that have boolean arguments recognize "1", "on" and "t" as true and "0", "off", "f" as false (plus their upper-case versions). Other options have more restricted argument types; kuvert generally sanity-checks options at startup. =head2 Known Options =over =item syslog Whether kuvert should use syslog for logging, and if so, what facility to use. Default: nothing. This is independent of the logfile option below. =item logfile Whether kuvert should write log messages to a file, appending to it. Default: not set. This is independent of the syslog option above. =item mail-on-error If kuvert encounters serious or fatal errors, an email is sent back to this address if set. Default: undef. This email is sent in addition to the normal logging via syslog or logfile. =item queuedir Where kuvert and its helper programs store mails to be processed. Default: ~/.kuvert_queue. The directory is created if necessary. The directory must be owned by the user running kuvert and have mode 0700. =item tempdir Where kuvert stores temporary files. Default: a directory called kuvert.. in $TMPDIR or /tmp. The directory is created if necessary, and must be owned by the user running kuvert and have mode 0700. This directory is completely emptied after processing an email. =item identify Whether kuvert should add an X-Mailer header to outbound emails. Default: false. The X-Mailer header consists of the program name and version. =item preamble Whether kuvert should include an explanatory preamble in the generated MIME mail. Default: true =item interval This sets the queue checking interval in seconds. Default: 60 seconds. =item msserver Mail Submission Server for outbound email. Default: unset. If this is set, kuvert will use SMTP to send outbound emails. If not set, kuvert uses the mail submission program on the local machine. See msp below. =item msport The TCP port on which the Mail Submission Server listens. Default: 587. Ignored if msserver is not set. =item ssl Whether SSL or STARTTLS are to be used for outbound SMTP submission. The value must be either "starttls" to use STARTTLS or "ssl" for raw SSL. SSL encryption is not used if this option is unset. =item ssl-cert =item ssl-key =item ssl-ca If an SSL client certificate is to be presented to the SMTP server, set both ssl-cert and ssl-key. If your system-wide CA certificate setup doesn't include the certificate your SMTP server uses, set ssl-ca to point to a PEM file containing all the relevant CA certificates. All these are ignored if the ssl option isn't set. =item msuser The username to use for SMTP authentication at the Mail Submission Server. SMTP Auth is not attempted if msuser isn't set. Ignored if msserver is not set. =item mspass The password for SMTP authentication. Ignored if msserver or msuser are not set. =item mspass-from-query-secret Whether the mspass should be retrieved using the query-secret program instead of giving the mspass in the config file. Ignored if msserver or msuser are not set. If this option is set, the query-secret program will be used to ask for the "smtp-password" when the first mail is processed. The password will be cached if authentication succeeds or you will be asked again, until authentication succeeds. =item msp Defines the program kuvert should use to deliver email. Default: "/usr/sbin/sendmail -om -oi -oem". This is ignored if msserver is set. The argument must include the full path to the program, and the program must accept the common mail transfer agent arguments as defined in the Linux Standards Base (see L). =item can-detach Indicates to kuvert that it can background itself on startup, detaching from the terminal. Default: false. This is possible only if you either delegate passphrase handling to gpg-agent, or if your secret-query program does not require interaction via the original terminal (e.g. if it is an X11 program with its own window). =item maport Kuvert can accept email for processing via SMTP. This option sets the TCP port kuvert listens on (localhost only). Default: 2587. Ignored if ma-user and ma-pass are not both set. If you want to use this mechanism, tell your mail program to use localhost or 127.0.0.1 as outgoing mail server and enable SMTP Authentication (see below). =item ma-user This option sets the required SMTP authentication username for accepting mails via SMTP. Default: undef. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. Kuvert does not accept emails for processing via SMTP unless you prove your identity with SMTP Authentication (or anybody on your local machine could use kuvert to send emails signed by you!). Kuvert currently supports only AUTH PLAIN and LOGIN (which is not a major problem as we listen on the loopback interface only). This option sets the username kuvert recognizes as yours. This can be anything and doesn't have to be a real account name. =item ma-pass This option sets the password your mail user agent must use for SMTP Authentication if submitting mails via SMTP. Default: unset. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. This password does not have to be (actually shouldn't be) your real account's password. Note that using SMTP submission requires that you protect your kuvert configuration file with strict permissions (0600 is suggested). =item defaultkey Specifies a default key to use as signing key. Default: unset, which means GnuPG gets to choose (usually the first available secret key). Can be overridden in the From: address, see section L. =item defaultaction Which action is to be taken if no overrides are found for a recipient. Default: none. See section L for recognized actions. =item alwaystrust Whether gpg should be told to trust all keys for encryption or not. Default: false. =item use-agent Whether kuvert should delegate all passphrase handling to the gpg-agent and call gpg with appropriate options. Default: false. If not set, kuvert will ask the user (or some nominated passphrase store) for passphrases on demand. =item query-secret Tells kuvert which program to use for passphrase retrieval. Default: "/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; \ stty echo; echo $X'" Ignored if use-agent is set. Kuvert does not store passphrases internally but rather runs the indicated program in a pipeline with gpg when signing. If you use a passphrase store (like the Linux-kernel keyutils or secret-agent or the like), enter your retrieval program here. The program is run with kuvert's environment, the first %s in the argument spec is replaced with the hex keyid and the passphrase is expected on stdout. The exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. Note that the default query program prohibits kuvert from backgrounding itself. =item flush-secret This program is called to invalidate an external passphrase cache if kuvert is notified by gpg of the passphrase being invalid. Default: undef. Ignored if use-agent is set. The program is run with kuvert's environment and with the first %s of its argument spec being replaced by the hex keyid in question. Its exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. =back =head1 DIAGNOSTICS Kuvert usually logs informational messages to syslog and/or its own logfile, both of which can be disabled and adjusted. If kuvert detects a fault that makes successful processing of a particular email impossible, kuvert will report that on STDERR (if not detached) and also email an error report if the option mail-on-error is enabled. Such partially or completely unprocessed mails are left in the queue but are renamed (the name is prefixed with "failed."); it is up to you to either remove such leftovers or rename them to something all-numeric once the problem has been resolved. The behaviour is similar if fatal problems are encountered; after alerting kuvert will terminate with exit code 1. =head1 ENVIRONMENT AND SIGNALS Kuvert itself uses only on environment variable: $TMPDIR provides the fallback location for kuvert's temporary directory. Kuvert passes its complete environment to child processes, namely gpg and any passphrase-query programs. On reception of SIGUSR1, kuvert reloads its configuration file and keyring. Any one of SIGHUP, SIGINT, SIGQUIT and SIGTERM causes kuvert to terminate cleanly, invalidating the passphrases if a query program is used. All other signals are ignored. =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. =item ~/.kuvert_queue The default queue directory. =item /tmp/kuvert.pid.EuidE holds the pid of a running kuvert daemon. =back =head1 SEE ALSO L, L, RFC3156, RFC2440, RFC2015 =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2008 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut @ 2.29 log @updated copyright timestamp @ text @d22 1 a22 1 # $Id: kuvert,v 2.28 2013/11/25 11:48:37 az Exp az $ d914 1 a914 1 close($config{logfh}); d1326 2 @ 2.28 log @added support for starttls @ text @d7 1 a7 1 # copyright (c) 1999-2008 Alexander Zangerl d22 1 a22 1 # $Id: kuvert,v 2.27 2012/09/04 10:27:32 az Exp az $ @ 2.27 log @added 600s timeout for gpg invocations @ text @d22 1 a22 1 # $Id: kuvert,v 2.26 2012/02/21 02:19:28 az Exp az $ d31 1 a31 1 use Net::SMTP; # for sending via smtp d774 4 d862 1 a862 1 elsif ($key =~ /^(ma-pass|ma-user|mail-on-error|msserver|msuser|mspass)$/) d1094 6 a1099 2 my $s=Net::SMTP->new($config{msserver},Port=>$config{msport}, Hello=>$dom); d1908 18 @ 2.26 log @added option to set or suppress the mime preamble @ text @d22 1 a22 1 # $Id: kuvert,v 2.25 2010/09/16 05:17:22 az Exp az $ d54 2 d1280 16 a1295 4 # outermost parent: read gpg status info my @@output=; close(F); if ($?) @ 2.25 log @added support for optional outbound smtp authentication @ text @d22 1 a22 1 # $Id: kuvert,v 2.24 2009/10/20 06:43:04 az Exp az $ d269 3 a271 1 $newent->preamble(["This is a multi-part message in MIME format.\n", d273 2 a274 2 "You need GPG to check the signature.\n"]); d327 3 a329 2 # set the new preamble $newent->preamble(["This is a multi-part message in MIME format.\n", d331 2 a332 2 "You need GPG to view the content.\n"]); d789 1 d842 1 a842 1 if ($key =~ /^(identify|use-agent|alwaystrust|can-detach|mspass-from-query-secret)$/) d1865 5 @ 2.24 log @fixed stupid case-sensitivity bug: keys are downcased but addresses were not... @ text @d22 1 a22 1 # $Id: kuvert,v 2.23 2008/08/31 06:39:26 az Exp az $ d767 3 d838 1 a838 1 if ($key =~ /^(identify|use-agent|alwaystrust|can-detach)$/) d852 1 a852 1 elsif ($key =~ /^(ma-pass|ma-user|mail-on-error)$/) a889 7 elsif ($key eq "msserver") { # crude check for ip or host name bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^([0-9:.]+|[a-z0-9.]+)$/); $options{$key}=$value; } d914 7 d1088 37 d1126 1 a1126 1 or return("mailserver rejected our from address \"$from\""); d1136 2 a1137 1 "rejected: ".join(", ",@@missed)); d1139 1 a1139 1 $s->data($ent->as_string) or return("mailserver rejected our data"); d1878 19 d1900 2 a1901 2 Default: "/usr/sbin/sendmail -om -oi -oem" Ths is ignored if msserver is set. The argument must include the @ 2.23 log @fixed queuedir-creation @ text @d22 1 a22 1 # $Id: kuvert,v 2.22 2008/06/29 12:31:01 az Exp az $ d653 1 a653 1 my ($addr,$comment,$phrase)=($a->address,$a->comment,$a->phrase); @ 2.22 log @fixed silly manpage typo @ text @d22 1 a22 1 # $Id: kuvert,v 2.21 2008/06/29 11:57:31 az Exp az $ a870 10 if (!-d $value) { mkdir($value,0700) or bailout("cannot create $key $value: $!\n"); } my @@stat=stat($value); if ($stat[4] != $< or ($stat[2]&0777) != 0700) { bailout("$key $value does not belong to you or has bad mode."); } d917 15 @ 2.21 log @syslog is now off by default @ text @d22 1 a22 1 # $Id: kuvert,v 2.20 2008/06/29 11:39:34 az Exp az $ d1725 1 a1725 1 address contains a ((key=B)) stanza, kuvert will use this key for @ 2.20 log @added logging for end of keyring read @ text @d22 1 a22 1 # $Id: kuvert,v 2.19 2008/06/29 11:01:55 az Exp az $ d770 1 a770 1 syslog=>"mail", d1785 1 a1785 1 use. Default: mail. This is independent of the logfile option below. @ 2.19 log @added auto-conversion @ text @d22 1 a22 1 # $Id: kuvert,v 2.18 2008/06/29 10:26:41 az Exp az $ d990 1 @ 2.18 log @kuvert v2 looks ready :-) @ text @d22 1 a22 1 # $Id: kuvert,v 2.17 2008/06/29 07:24:53 az Exp az $ a168 2 print PIDF "$$\n"; close PIDF; # clears the lock d170 2 d789 3 a791 1 while () d796 12 d931 44 d1762 1 a1762 1 the option name followed by some whitespace. @ 2.17 log @semi-tested (missing: test of smtp server, some options) on the way to kuvert v2. @ text @d22 1 a22 1 # $Id: kuvert,v 2.16 2007/06/23 02:37:57 az Exp az $ a74 1 # fixme: pidstuff for the smtp daemon d94 4 a97 2 die "can't send signal to process $p: $!\n" if (!kill(($n=~/^$listenername/?$ssig:$sig),$p)); d103 1 a135 1 $config{syslog}="mail"; d137 1 d140 1 a142 9 # 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; a143 1 # fixme: pidfile stuff! d148 3 a150 19 my $pid=fork; if (!defined($pid)) { die "cannot fork: $!\n"; } elsif (!$pid) { # run mailserver, which does never reload the config $0=$listenername; close STDIN; close PIDF; # clears the inherited lock $SIG{'USR1'}=\&handle_term; &accept_mail; } else { # parent updates pidfile with mailserver pid print PIDF "$pid\n"; } d153 5 d219 1 a219 1 "Your mail \"$config{queuedir}/$file\" could not be processed successfully, d224 2 a225 1 if you wish to retry rename it to an all-numeric filename. Otherwise you should delete the file.\n"); d283 1 a283 1 $debug && logit("gpg reported bad passphrase, retrying."); d286 1 a286 1 $debug && logit("invalidating passphrase for $signkey"); d341 1 a341 1 $debug && logit("gpg reported bad passphrase, retrying."); d344 1 a344 1 $debug && logit("invalidating passphrase for $signkey"); d392 1 a392 1 $debug && logit("local signkey override: $signkey"); d436 1 a436 1 $in_ent->head->replace("cc",$newcc); d556 1 a556 1 my @@res=&crypt_send($in_ent,$cryptoin,$orig_header,$signkey, d589 1 a589 1 $debug && logit("action $actions{$addr} for $addr"); d595 1 a595 1 $debug && logit("override header: $override for $addr"); d604 1 a604 2 $debug && logit("local override: action $thisaction for $addr"); d610 1 a610 2 $debug && logit("local key override: $specialkeys{$addr} for $addr"); d636 1 a636 1 $debug && logit("downgrading to signonly for $addr"); d708 1 a708 1 logit("$sig triggered termination, cleaning up"); d718 2 a719 1 logit("rereading configuration"); a720 1 logit("rereading keyring"); d722 10 d806 2 a807 2 $debug && logit("got override $action " .($key?"key $key ":"")."for $who"); d815 1 a901 1 $value=undef if (!$value || $value=~/^(\'|\"){2}$/); d909 1 a909 1 $debug && logit("got config $key=$value"); d970 1 a970 2 &logit("got key 0x$lastkey type $info[3] for $name") if ($debug); d974 1 a974 2 &logit("saved key 0x$lastkey, no address known yet") if ($debug); d993 1 a993 2 &logit("got key (uid) 0x$lastkey for $name") if ($debug); d997 1 a997 2 &logit("ignoring uid without valid address") if ($debug); d1106 1 a1106 1 or die "can't dup2 stderr onto stdout: $!\n"; d1108 1 a1108 1 die "exec gpg failed: $!\n"; d1115 1 a1115 1 $precmd=sprintf($config{"query-secret"},substr($2,8)); d1137 1 a1137 1 or die "can't dup2 stderr onto stdout: $!\n"; d1139 1 a1139 1 die "exec gpg failed: $!\n"; d1151 1 a1151 1 die "Error: couldn't fork: $!\n"; d1162 1 a1162 1 or die "can't dup2 stderr onto stdout: $!\n"; d1164 1 a1164 1 or die "can't dup stdin onto child-pipe: $!\n"; d1166 1 a1166 1 die "exec gpg failed: $!\n"; d1178 1 a1178 1 "Detailed error message:",join("\n",@@output)); d1198 1 a1198 1 print { $config{logfh} } scalar(localtime).join("\n\t",@@msgs)."\n"; d1210 5 d1234 1 a1235 2 my $subject=$progname.": ".shift @@msgs; d1238 1 a1238 1 Subject=>$subject, d1257 23 d1286 1 d1293 1 a1293 1 $esmtp->register('Net::Server::Mail::ESMTP::Extension::plainAUTH'); d1309 1 a1309 1 return ($user eq $config{'ma-user'} && $pwd eq $config{'ma-pass'}); d1333 1 a1333 1 my $qid=(Time::HiRes::gettimeofday)[1]; d1369 1 a1369 1 if (!close {$session->{DATA}->{qfh}}) d1418 2 a1419 2 Enables debugging mode: extra information is written to kuvert's logfile and/or syslog. d1424 2 a1425 1 only the current queue contents and then exits. @ 2.16 log @signatures are now tagged more extensively (as per hint by Andreas Labres/Andreas Kreuzinger) @ text @d3 2 a4 2 # this file is part of kuvert, a wrapper around sendmail that # does pgp/gpg signing/signing+encrypting transparently, based d7 1 a7 1 # copyright (c) 1999-2005 Alexander Zangerl d10 2 a11 3 # 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. d22 1 a22 1 # $Id: kuvert,v 2.15 2005/11/04 06:21:20 az Exp az $ d31 4 d36 4 a39 2 use Term::ReadKey; use Proc::PID::File; d45 3 d50 1 a52 6 # configuration directives, keyring my (%config,@@overrides,%keys); # the passphrases are stored here if passphrase store is not a/v my %secrets=(); my ($debug,$barfmail); my @@detailederror=(); d54 2 a55 2 my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp"); my $pidname="$progname.$<"; d57 2 a58 1 sub main d60 2 a61 6 my %options; if (!getopts("dkrnvb",\%options) || @@ARGV) { die "usage: $progname [-n] [-d] [-v] [-b]| [-k] | [-r] -k: kill running $progname d64 4 a67 13 -n don't fork -v: output version and exit -b: complain via mail when dying\n"; } if ($options{'v'}) { print STDERR "$progname $version\n"; exit 0; } $debug=1 if ($options{"d"}); $barfmail=1 if ($options{"b"}); d69 3 a71 6 # 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'); d73 25 a97 10 open(PIDF,"$piddir/$pidname.pid") || &bailout("cant open pidfile: $! -- exiting"); $pid=; close(PIDF); chomp $pid; &bailout("no valid pid found, cant kill any process -- exiting") if (!$pid); &bailout("cant kill -$sig $pid: $! -- exiting") if (!kill $sig, $pid); exit 0; d99 3 d103 20 a122 1 if (! -e $rcfile) d124 44 a167 7 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" d169 1 a169 9 logit("$progname version $version starting"); # read the config, setup dirs, logging, defaultkeys etc. &read_config; # get the passphrase(s) if no external passphrase store is used # this has to be done before a fork... if (!$config{secretondemand}) d171 2 a172 4 # 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}); d174 1 d176 4 a179 1 if (!$debug && !$options{"n"}) d181 1 a181 5 my $res=fork; &bailout("fork failed: $! -- exiting") if ($res == -1); exit 0 if ($res); d183 1 a183 20 # check that we're the only instance running bailout("$progname: some other instance is running!") if (Proc::PID::File->running(dir=>$piddir, name=>$pidname)); # make things clean and ready. we're in sole command now. cleanup($config{tempdir},0); &read_keyrings; # 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; # the main loop, left only via signal handler handle_term while (1) d185 1 a185 50 &bailout("cant open $config{queuedir}: $! -- exiting") if (!opendir(D,"$config{queuedir}")); my $file; foreach $file (grep(!/^\./,readdir(D))) { 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; } #ok, open & locked, let's proceed logit("processing $file for $username"); $barfmail=0; # avoid duplicate mails, we're eval()ing! eval { process_file(*FH,"$config{queuedir}/$file"); }; $barfmail=1 if ($options{"b"}); if ($@@) { chomp(my $error=$@@); rename("$config{queuedir}/$file","$config{queuedir}/.$file") || &bailout("cant rename $config{queuedir}/$file: $! -- exiting"); logit("failed to process $file, left as \".$file\".\n"); send_bounce($error,$file); } else { logit("done with file $file"); unlink("$config{queuedir}/$file") || &bailout("cant unlink $config{queuedir}/$file: $! -- exiting"); } # and clean up the cruft left behind, please! cleanup("$config{tempdir}",0); # unlock the file bailout("problem unlocking $config{queuedir}/$file: $! -- exiting") if (!flock(FH,LOCK_UN)); close(FH); } closedir(D); &handle_term("debug mode") if ($debug); sleep($config{interval}); d187 2 d191 17 a207 3 # processes a file in the queue, does not remove stuff from the tempdir or the queue # exception on errors sub process_file d209 5 a213 78 my ($fh,$file)=@@_; my $parser = new MIME::Parser; # dump mime object to tempdir $parser->output_dir($config{tempdir}); # retain rfc1522-encoded headers, please $parser->decode_headers(0); # make the parser ignore all filename info and just invent filenames. $parser->filer->ignore_filename(1); my $in_ent; eval { $in_ent=$parser->read(\$fh); }; bailout("could not parse MIME stream, last header was ".$parser->last_head) if ($@@); # add version header $in_ent->head->add('X-Mailer',"$progname $version") if ($config{identify}); # extract and delete instruction header my $custom_conf=lc($in_ent->head->get("x-kuvert")); $in_ent->head->delete("x-kuvert"); # strip trailing and leading whitespace from the custom header $custom_conf =~ s/^\s*(\S*)\s*$/$1/; # check the custom header for validity undef $custom_conf unless ($custom_conf=~/^(none|std(sign)?|ng(sign)?|fallback)(-force)?$/); # extract a possible resend-request-header, if set call mta immediately if ($custom_conf eq "none" || $in_ent->head->get("resent-to")) { logit(($custom_conf eq "none"?"resending ":"") ."sign/encrypt disabled, calling $config{mta} -t"); # we do not send the original file here because this file possibly # holds the instruction header... &send_entity($in_ent,"-t"); $in_ent->purge; return; } my (@@recip_all,@@recip_bcc); # get the recipients map { push @@recip_all, lc($_->address); } Mail::Address->parse($in_ent->head->get("To"), $in_ent->head->get("Cc")); map { push @@recip_bcc, lc($_->address); } Mail::Address->parse($in_ent->head->get("Bcc")); # but don't leak Bcc... $in_ent->head->delete("Bcc"); # cry out loud if there is a problem with the submitted mail # and no recipients were distinguishable... # happens sometimes, with mbox-style 'From bla' lines in the headers... bailout("no recipients found! the mail headers seem to be garbled.") if (!@@recip_all && !@@recip_bcc); # figure out what to do for specific recipients my %actions=findaction($custom_conf,\@@recip_all,\@@recip_bcc); my $orig_header; my $input="$config{tempdir}/.input"; # take care of raw mails, before mangling the headers my @@recips=grep($actions{$_} eq "none",keys %actions); if (@@recips) { logit("sending mail (raw) to ".join(",",@@recips)); &send_entity($in_ent,@@recips); } # prepare various stuff we need only when encrypting or signing if(grep(/(ng|std)/,values(%actions))) d215 1 a215 7 # 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; # content-* stays with the entity and the rest moves to orig_header foreach my $headername ($in_ent->head->tags) d217 2 a218 10 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); } d220 2 a221 31 # 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 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. bailout("cant open >$input: $!") if (!open(F,">$input")); $in_ent->print(\*F); close(F); } foreach my $action qw(ng ngsign std stdsign bcc-ng bcc-std) { my @@recips=grep($actions{$_} eq $action,keys %actions); next if (!@@recips); my $type=($action=~/ng/?"ng":"std"); if ($action=~/bcc/) d223 2 a224 8 # send stuff single file, one completely separate mail per bcc recipient...ugly and slow # but the Right Thing, otherwise we leak encryption key information # (only necessary for encryption) foreach (@@recips) { logit("sending mail (bcc,crypt,$type) to $_"); &crypt_send($in_ent,$input,$type,$orig_header,[$keys{$type}->{$_}],$_); } d227 6 a232 2 if ($action=~/sign/) d234 9 a242 3 logit("sending mail (sign,$type) to ".join(",",@@recips)); &sign_send($in_ent,$input,$type,$orig_header,@@recips); next; d246 14 a259 6 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); } } d262 1 d264 3 a266 2 # args: entity, location of dump of entity, type, outermost headers, recipients # exception on errors d269 2 a270 2 my ($ent,$dumpfile,$type,$header,@@recips)=@@_; my $output="$config{tempdir}/.signout"; d284 1 a284 1 $newent->head->mime_attr("content-Type.Micalg" => ($type eq "ng"?"pgp-sha1":"pgp-md5")); d288 1 a288 1 "You need GPG or PGP to check the signature.\n"]); d294 3 a296 1 while (&sign_encrypt(0,$type,$dumpfile,$output,undef)) d298 4 a301 11 # get rid of broken passphrase and lets try again if ($config{secretondemand}) { $debug && logit("bad passphrase, retry"); my $cmd=sprintf($config{delsecret},$config{$type."key"}); my $res=0xffff & system("$cmd >$config{tempdir}/subproc 2>&1"); bailout("error deleting broken passphrase from store: $res", "$config{tempdir}/subproc") if ($res); } else d303 3 a305 2 # bad passphrase but we're on our own -> cant recover bailout("bad passphrase, but no passphrase store to query!"); d308 2 d312 1 a312 1 Path => "$output", d318 1 a318 1 &send_entity($newent,@@recips); d322 3 a324 1 # args: entity, location of dump of entity, type, outermost headers, recipient keys, recipient addresses d327 2 a328 2 my ($ent,$dumpfile,$type,$header,$rec_keys,@@recips)=@@_; my $output="$config{tempdir}/.encout"; d345 1 a345 1 "You need PGP or GPG to view the content.\n"]); d353 2 a354 1 while (&sign_encrypt(1,$type,$dumpfile,$output,@@{$rec_keys})) d356 4 a359 11 # get rid of broken passphrase and lets try again if ($config{secretondemand}) { $debug && logit("bad passphrase, retry"); my $cmd=sprintf($config{delsecret},$config{$type."key"}); my $res=0xffff & system("$cmd >$config{tempdir}/subproc 2>&1"); bailout("error deleting broken passphrase from store: $res", "$config{tempdir}/subproc") if ($res); } else d361 3 a363 2 # bad passphrase but we're on our own -> cant recover bailout("bad passphrase, but no passphrase store to query!"); d366 1 d370 1 a370 1 Path => "$output", d376 1 a376 1 &send_entity($newent,@@recips); d379 4 a382 5 # send entity to $mta, passing $args to $mta # ent is a MIME::Entity and args is either "-t" or a list of recipients # exception on errors sub send_entity d384 1 a384 1 my ($ent,@@args)=@@_; d386 4 a389 13 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]: $!"); } } d391 9 a399 7 # remove temporary stuff left behind in directory $what # remove_what set: remove the dir, too. # exception on error, no retval sub cleanup { my ($what,$remove_what)=@@_; my ($name,$res); d401 10 a410 6 opendir(F,$what) || bailout("cant opendir $what: $!"); foreach $name (readdir(F)) { next if ($name =~ /^\.{1,2}$/o); (-d "$what/$name")?&cleanup("$what/$name",1): (unlink("$what/$name") || bailout("cant unlink $what/$name: $!")); a411 4 closedir(F); $remove_what && (rmdir("$what") || bailout("cant rmdir $what: $!")); return 0; } d413 3 a415 4 # log termination, cleanup, exit sub handle_term { my ($sig)=@@_; d417 6 a422 16 logit("got termination signal SIG$sig, cleaning up"); my $res=&cleanup($config{tempdir},1); logit("problem cleaning up $config{tempdir}: $res") if ($res); # wipe keys if ($config{secretondemand}) { foreach ($config{ngkey},$config{stdkey}) { next if (!$_); my $cmd=sprintf($config{delsecret},$_); my $res=0xffff & system $cmd; &logit("problem deleting secret for $_: $res") if ($res); } d424 1 a424 3 close $config{logfh} if ($config{logfh}); exit 0; } d426 28 a453 8 # reread configuration file and keyrings # no args or return value; intended as a sighandler. sub handle_reload { logit("rereading config file"); &read_config; &read_keyrings; } d455 5 a459 9 # read keyrings into global hashes # note: this must happen after the config is read, so that # the right tools are used (gpg vs. pgp) sub read_keyrings { my ($lastkey,$lasttype,@@tmp,$name,$now,@@info); my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); %{$keys{std}}=(); d461 7 a467 1 if ($config{usepgp}) d469 6 a474 1 if (!$config{stdkey}) d476 6 a481 1 logit("ignoring std keyring, no key a/v."); d483 3 a485 1 else d487 1 a487 45 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) { if (/^pub\s+\d+\/(\S+)\s+(.+)$/) { 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; } if (/^\s+.*(\s|<)([^\s<]+\@@[^\s>]+)>?\s*$/) { my $name=lc($2); $keys{std}->{$name}="0x$lastkey"; &logit("got stdkey (uid) 0x$lastkey for $name") if ($debug); } } d489 1 d492 2 a493 1 %{$keys{ng}}=(); d495 12 a506 1 if ($config{ngkey} || !$config{usepgp} && $config{stdkey}) d508 4 a511 1 logit("reading ".(!$config{usepgp} && $config{stdkey}?"combined":"ng")." keyring."); d513 4 a516 35 # 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) { @@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") { &logit("ignoring stdkey 0x$info[4]") if ($debug && $info[4]); undef $lastkey; next; } elsif (!$config{ngkey} && $info[3] ne "1") { &logit("ignoring ngkey 0x$info[4]") if ($debug && $info[4]); undef $lastkey; next; } $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>]+)>?/) d518 2 a519 1 $name=lc($2); d523 2 a524 69 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]; $lasttype=$info[3]==1?"std":"ng"; if ($name) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring ".($info[3]==1?"std":"ng") ." key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } $keys{$lasttype}->{$name}="0x$lastkey"; &logit("got $lasttype key 0x$lastkey for $name") if ($debug); } else { &logit("saved $lasttype key 0x$lastkey, no address known yet") if ($debug); } next; } else { # uid: associate the current address with the key # given in the most recent public key line # if no such key saved: the pub key was an rsa key & # we're set to ignore those if (!$lastkey) { $name="" if (!$name); &logit("ignoring uid $name, belongs to std key?") if ($debug); } else { if ($name) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring ".($info[3]==1?"std":"ng") ." uid $name for 0x$lastkey, " ."reason: ".$badcauses{$info[1]}); next; } $keys{$lasttype}->{$name}="0x$lastkey"; &logit("got $lasttype key (uid) 0x$lastkey for $name") if ($debug); } else { &logit("ignoring uid without valid address") if ($debug); } } a526 6 } else { logit("ignoring ng keyring, no key a/v."); } } d528 57 a584 4 # reads the configuration file, sets config variables # exception on major problems # no retval. changes %config and @@overrides on success. sub read_config d586 2 a587 1 my @@over; d589 2 a590 23 # default settings my $defaction="none"; my %newconf=(ngkey=>undef, stdkey=>undef, pgppath=>"/usr/bin/pgp", gpgpath=>"/usr/bin/gpg", usepgp=>0, getsecret=>undef, delsecret=>undef, 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, logfh=>undef, identify=>0); &bailout("cant open $rcfile: $! -- exiting") if (!open (F,$rcfile)); logit("reading config file"); while () d592 2 a593 4 chomp; next if (/^\#/ || /^\s*$/); # strip comments and empty lines if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/) d595 1 a595 2 my ($key,$action)=(lc($1),lc($2)); if ($key eq "default") d597 5 a601 9 $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"); d604 7 a610 1 elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/) d612 3 a614 1 my ($key,$value)=(lc($1),$2); d616 7 a622 9 if (grep($_ eq $key, keys %newconf)) { $newconf{$key}=$value; $debug && logit("set config $key to $value"); } else { &bailout("bad config entry \"$_\" -- exiting"); } d624 2 a625 1 else d627 3 a629 1 &bailout("bad config entry \"$_\" -- exiting"); a630 7 } close F; # last per-address override is the catch-all default push @@over,{"key"=>"default", "re"=>qr/.*/, "action"=>"$defaction"}; d632 3 a634 22 # generate queuedir if not existing if (!-d $newconf{queuedir}) { unlink "$newconf{queuedir}"; &bailout("cant mkdir $newconf{queuedir}: $! -- exiting") if (!mkdir($newconf{queuedir},0700)); } # check queuedir owner & perm elsif ((stat($newconf{queuedir}))[4] != $<) { &bailout("$newconf{queuedir} is not owned by you -- exiting"); } elsif ((stat($newconf{queuedir}))[2] & 0777 != 0700) { &bailout("$newconf{queuedir} does not have mode 0700 -- exiting"); } # make tempdir if (!-d $newconf{tempdir}) { unlink "$newconf{tempdir}"; if (!mkdir($newconf{tempdir},0700)) d636 4 a639 1 &bailout("cant mkdir $newconf{tempdir}: $! -- exiting"); a641 12 elsif ((stat($newconf{tempdir}))[4] != $<) { &bailout("$newconf{tempdir} is not owned by you -- exiting"); } elsif ((stat($newconf{tempdir}))[2]&0777 != 0700) { &bailout("$newconf{tempdir} does not have mode 0700 -- exiting"); } # close old logfile if there is one close($config{logfile}) if ($config{logfile} && $config{logfile} ne $newconf{logfile}); d643 4 a646 1 if ($newconf{logfile}) d648 2 a649 37 &bailout("cant open logfile $newconf{logfile}: $! -- exiting") if (!open($newconf{logfh},">>$newconf{logfile}")); $newconf{logfh}->autoflush(1); } # secret on demand is only possible with both a get and a del command $newconf{secretondemand}=0 if (!$newconf{getsecret} || !$newconf{delsecret}); # sanity checks: external executables &bailout("bad executable '$newconf{mta}' -- exiting") if ($newconf{mta}=~/^(\S+)/ && ! -x $1); if ($newconf{secretondemand}) { &bailout("bad executable '$newconf{getsecret}' -- exiting") if ($newconf{getsecret} && $newconf{getsecret}=~/^(\S+)/ && ! -x $1); &bailout("bad executable '$newconf{delsecret}' -- exiting") if ($newconf{delsecret} && $newconf{delsecret}=~/^(\S+)/ && ! -x $1); } &bailout("bad executable '$newconf{pgppath}' -- exiting") if ($newconf{usepgp} && $newconf{stdkey} ne "0" && (!$newconf{pgppath} || $newconf{pgppath}=~/^(\S+)/ && ! -x $1)); &bailout("bad executable '$newconf{gpgpath}' -- exiting") if ($newconf{ngkey} ne "0" && ( !$newconf{gpgpath} || $newconf{gpgpath}=~/^(\S+)/ && ! -x $1)); # figure out the default keys if none were supplied, check them if ($newconf{ngkey}) { my $res=0xffff & system("$newconf{gpgpath} -q --batch --list-secret-keys --with-colons $newconf{ngkey} >$newconf{tempdir}/subproc 2>&1"); bailout("bad ngkey spec '$newconf{ngkey}' -- exiting","$newconf{tempdir}/subproc") if ($res); } elsif (!defined $newconf{ngkey}) { open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc |") || bailout("cant fork $newconf{gpgpath} to list sec keys: $! -- exiting"); while () d651 6 a656 6 my @@list=split(/:/); next if ($list[0] ne "sec" || $list[3] ne "17"); $list[4] =~ s/^.{8}//; # truncate key-id $newconf{ngkey}="0x$list[4]"; $debug && logit("set ngkey to $newconf{ngkey}"); last; a657 3 close F; bailout("error running $newconf{gpgpath}: $? -- exiting","$newconf{tempdir}/subproc") if ($?); bailout("could not find ngkey -- exiting") if (!$newconf{ngkey}); d659 10 d670 1 a670 1 if ($newconf{stdkey}) d672 8 a679 4 if ($newconf{usepgp}) { my $res=0xffff & system("$newconf{pgppath} -kv $newconf{stdkey} $home/.pgp/secring.pgp >$newconf{tempdir}/subproc 2>&1"); bailout("bad stdkey spec \"$newconf{stdkey}\" -- exiting","$newconf{tempdir}/subproc") if ($res); d683 1 a683 3 my $res=0xffff & system("$newconf{gpgpath} -q --batch --list-secret-keys --with-colons $newconf{stdkey} >$newconf{tempdir}/subproc 2>&1"); bailout("bad stdkey spec \"$newconf{stdkey}\" -- exiting","$newconf{tempdir}/subproc") if ($res); d686 1 a686 52 elsif (!defined $newconf{stdkey}) { if ($newconf{usepgp}) { open(F,"$newconf{pgppath} -kv $home/.pgp/secring.pgp 2>$newconf{tempdir}/subproc |") || bailout("cant fork $newconf{pgppath} to list sec keys: $! -- exiting"); while () { if (/^sec\s+\d+\/(\S+)\s+/) { $newconf{stdkey}="0x$1"; $debug && logit("set stdkey to $newconf{stdkey}"); last; } } close F; bailout("error running $newconf{pgppath}: $? -- exiting","$newconf{tempdir}/subproc") if ($?); } else { open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc|") || bailout("cant run $newconf{gpgpath} to list sec keys: $! -- exiting","$newconf{tempdir}/subproc"); while () { my @@list=split(/:/); next if ($list[0] ne "sec" || $list[3] ne "1"); $list[4] =~ s/^.{8}//; # truncate key-id $newconf{stdkey}="0x$list[4]"; $debug && logit("set stdkey to $newconf{stdkey}"); last; } close F; bailout("error running $newconf{gpgpath}: $? -- exiting","$newconf{tempdir}/subproc") if ($?); } bailout("could not find stdkey -- exiting") if (!$newconf{stdkey}); } # finally make sure that no action conflicts with the keys we may lack bailout("no keys whatsoever a/v! -- exiting") if (!$newconf{stdkey} && !$newconf{ngkey}); bailout("config specifies ng but no ng key a/v -- exiting") if (!$newconf{ngkey} && grep($_->{action} =~ /^ng/, @@over)); bailout("config specifies std but no std key a/v -- exiting") if (!$newconf{stdkey} && grep($_->{action} =~ /^std/, @@over)); # everything seems ok, overwrite global vars config and override %config=%newconf; @@overrides=@@over; return; d689 1 a689 1 # traverses the entity and sets all parts with d692 1 a692 1 # input: entity, retval: none a695 1 d700 2 a701 1 &qp_fix_parts($_); d711 1 a711 1 bailout("changing Content-Transfer-Encoding failed") d713 2 a714 1 => "quoted-printable")!="quoted-printable"); d717 14 d733 8 a740 21 # notifies the sender of a problem, via email # retrieves the detailed error message from @@detailederror # no return value, exception on problems sub send_bounce { my ($res,$file)=@@_; open(F,"|$config{mta} $username") || bailout("cant fork $config{mta}: $! -- exiting"); print F "From: $username\nTo: $username\nSubject: $progname Mail Sending Failure\n\n" ."Your mail $config{queuedir}/$file could not be sent to some or all recipients.\n\n" ."The error message was:\n\n$res\n\n\n"; print F "Detailed error message:\n\n" .join("",@@detailederror)."\n\n\n" if (@@detailederror); print F "$progname has no reliable way of figuring out whether this failure did affect\n" ."all recipients of your mail, so please look into the log for further error indications.\n\n" ."$progname has backed the failed mail up as $config{queuedir}/.$file;\n" ."If you wish to retry again for all original recipients, just rename the file back to\n" ."$config{queuedir}/$file or otherwise remove the backup file.\n"; close F; bailout("error running $config{mta}: $? -- exiting") if ($?); d743 4 a746 37 # get, verify and store a secret # input: what kind of secret # retval: none, changes %secrets, exception on major errors # note: only used when secretondemand is unset. sub get_secret { my ($type)=@@_; my $id=$config{($type eq "std"?"stdkey":"ngkey")}; my $res; do { # 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=); ReadMode("restore"); bailout("error reading $type passphrase: $!") if (!defined($phrase)); print "\n"; $secrets{$id}=$phrase; $phrase="x" x 64; $res=sign_encrypt(0,$type,undef,undef); } while ($res); } # sign/encrypt a file, or test the passphrase if infile and outfile are undef. # input: encrypt, type std/ng, infile and outfile path, recipient keys if encrypt. # returns: 0 if ok, 1 if bad passphrase, exception on other errors sub sign_encrypt d748 5 a752 5 my ($enc,$type,$infile,$outfile,@@recips)=@@_; my ($passphrase,$passphrase_cmd,$cmd); # passphrase issues if ($config{secretondemand}) d754 3 a756 2 $cmd="|".sprintf($config{getsecret}, ($type eq "std"?$config{stdkey}:$config{ngkey})); d758 41 a798 1 else d800 2 a801 2 $passphrase=$secrets{$config{($type eq "std"?"stdkey":"ngkey")}}; } d803 1 a803 4 # how to arrange the command if (!$enc) { if ($type eq "std" && $config{usepgp}) d805 13 a817 1 $cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode -u $config{stdkey} -sbat"; d819 2 a820 1 else d822 34 a855 2 $cmd.="|$config{gpgpath} -q -t --batch --armor --detach-sign --passphrase-fd 0 --status-fd 1 --default-key"; if ($type eq "std") d857 4 a860 1 $cmd.=" $config{stdkey} --rfc1991 --cipher-algo idea --digest-algo md5 --compress-algo 1"; d862 55 a916 3 else { $cmd.=" $config{ngkey}"; d918 1 a918 10 } # only check the passphrase: pgp needs -f(ilter) flag then if (!$infile && !$outfile) { $cmd.=" -f" if ($type eq "std" && $config{usepgp}); } else { $cmd.=" -o $outfile $infile"; d921 23 a943 1 else # encrypt and sign d945 43 a987 5 if ($type eq "std" && $config{usepgp}) { $cmd.="|PGPPASSFD=0 $config{pgppath} +batchmode " ."-u $config{stdkey} -seat -o $outfile $infile " .join(" ",@@recips); d991 3 a993 2 # gpg: normal mode... if ($type ne "std") d995 11 a1005 5 $cmd.="|$config{gpgpath} -q -t --batch --armor --passphrase-fd 0 " ."--status-fd 1 --default-key $config{ngkey} -r " .join(" -r ",@@recips) .($config{alwaystrust}?" --always-trust":"") ." --encrypt --sign -o $outfile $infile"; d1009 2 a1010 11 # or compatibility-mode, bah # very elaborate but working procedure, found by # Gero Treuner # http://muppet.faveve.uni-stuttgart.de/~gero/gpg-2comp # first, generate the signature and store it $cmd.="|$config{gpgpath} --batch -q --detach-sign " ."--default-key $config{stdkey} " ."--passphrase-fd 0 --status-fd 1 -o $outfile $infile"; # the rest is done later on d1014 2 d1017 8 a1024 1 $cmd.=" >$config{tempdir}/subproc 2>&1"; d1026 3 a1028 1 unlink($outfile) if (-e $outfile); d1030 21 a1050 10 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 ($?) d1052 9 a1060 9 # 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}) d1062 4 a1065 5 open F,"$config{tempdir}/subproc"; my @@result=; close F; return 1 if (grep(/^\[GNUPG:\] BAD_PASSPHRASE/,@@result)); a1066 4 bailout("error running sign prog: $?","$config{tempdir}/subproc") if ($? == 0xff00); bailout("sign prog died from signal " . ($? & 0x7f),"$config{tempdir}/subproc") if ($? <= 0x80); bailout(("sign prog returned error ".($?>>8)),"$config{tempdir}/subproc") if ($?>>8); d1068 15 d1084 12 a1095 1 # ok, must be in compat mode...let's complete the nasty construction d1097 8 a1104 46 # next, convert the cleartext to the internal literal structure unlink("$outfile.inter1") if (-e "$outfile.inter1"); my $res=0xffff & system("$config{gpgpath} --batch -q --store --batch -z 0 -o $outfile.inter1 " ."$infile >$config{tempdir}/subproc 2>&1"); bailout("error running gpg","$config{tempdir}/subproc") if ($res); # now compress signature and literal in the required order open(F,"$outfile") || bailout("cant open $outfile: $!"); open(G,"$outfile.inter1") || bailout("cant open $outfile.inter1: $!"); unlink("$outfile.inter2") if (-e "$outfile.inter2");; open(H,"|$config{gpgpath} --no-literal --store --batch --compress-algo 1 " ."-o $outfile.inter2 >$config{tempdir}/subproc 2>&1") || bailout("cant open pipe to $config{gpgpath}: $!"); print H ; print H ; close F; close G; close H; bailout("error running $config{gpgpath}: $?","$config{tempdir}/subproc") if ($?); # and finally encrypt all this for the wanted recipients. unlink($outfile); $cmd="$config{gpgpath} --no-literal --batch --encrypt --rfc1991 --cipher-algo idea " .($config{alwaystrust}?"--always-trust ":"") ."--armor -o $outfile -r " .join(" -r ",@@recips) ." $outfile.inter2 >$config{tempdir}/subproc 2>&1"; $res=0xffff & system($cmd); bailout("error running $config{gpgpath}: $res","$config{tempdir}/subproc") if ($res); return 0; } # find the correct action for a given email address # input: addresses and custom-header, bcc-addresses # result: hash with address as key and action as value # the fallback and -force options are expanded into atoms, ie. # resulting actions are: ng, ngsign, std, stdsign, none, # or bcc-{ng,std}. # note: ng and std means encryption here, no check for keys necessary anymore sub findaction { my ($custom,$allref,$bccref)=@@_; my(@@affected,%actions,$addr); d1106 1 a1106 2 # lookup addresses in configured overrides foreach $addr (@@{$allref},@@{$bccref}) d1108 8 a1115 1 foreach (@@overrides) d1117 10 a1126 1 if ($addr =~ $_->{re}) d1128 2 a1129 2 $actions{$addr}=$_->{action}; $debug && logit("found directive: $addr -> $actions{$addr}"); d1133 1 a1133 8 # custom set? then override the config except where action=none if ($custom && $actions{$addr} ne "none") { $debug && logit("custom conf header: overrides $addr -> $custom"); $actions{$addr}=$custom; next; } &bailout("internal error, no action found for $addr") if (!exists $actions{$addr}); d1135 1 d1137 3 a1139 2 # no -force options for bcc foreach $addr (@@{$bccref}) d1141 1 a1141 1 $actions{$addr}=~s/^(\S+)-force$/$1/; d1143 1 a1143 4 # check the found actions: anyone with -force options? # note: normal addresses only, bcc don't count here foreach $addr (@@{$allref}) d1145 2 a1146 12 next if ($actions{$addr} !~ /^(\S+)-force$/); my $force=$1; $debug && logit("found force directive: $addr -> $actions{$addr}"); # yuck, must find affected addresses: those with action=none # have to be disregarded and unchanged. @@affected = grep($actions{$_} ne "none",@@{$allref}); # (almost) unconditionally apply the simple force options: # none,ngsign,stdsign; others need more logic if ($force eq "std") d1148 5 a1152 2 # downgrade to sign if not all keys a/v $force="stdsign" if (grep(!exists $keys{std}->{$_}, @@affected)); d1154 1 a1154 5 elsif ($force eq "ng") { $force="ngsign" if (grep(!exists $keys{ng}->{$_}, @@affected)); } elsif ($force eq "fallback") d1156 2 a1157 6 # fallback-logic: ng-crypt or std-crypt, otherwise ngsign or stdsign # -force: ng- or std-crypt for all, otherwise ngsign or stdsign $force="ngsign" if (grep(!exists $keys{ng}->{$_} && !exists $keys{std}->{$_}, @@affected)); } d1159 33 a1191 5 # apply forced action to the affected addresses map { $actions{$_}=$force; } (@@affected); $debug && logit("final force directive: $force"); # the first force-option wins, naturally. last; d1193 9 d1203 1 a1203 3 # check the actions for fallback, ng or std and expand that # also bail out if no suitable keys available! foreach $addr (@@{$allref},@@{$bccref}) d1205 1 a1205 1 if ($actions{$addr} eq "fallback") d1207 2 a1208 5 ($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"); a1209 11 elsif ($actions{$addr} =~ /^ng(sign)?$/) { bailout("no ng key available but ng action required for $addr") if (!$config{ngkey}); $actions{$addr}="ngsign" if ($actions{$addr} eq "ng" && !$keys{ng}->{$addr}); } elsif ($actions{$addr} =~ /^std(sign)?$/) { bailout("no std key available but std action required for $addr") if (!$config{stdkey}); $actions{$addr}="stdsign" if ($actions{$addr} eq "std" && !$keys{std}->{$addr}); d1211 1 a1211 2 } $debug && logit("final action: $addr -> $actions{$addr}"); d1214 1 a1214 3 # tag ng and std actions for bcc recipients: # those must be handled separately (separate encryption step...) foreach $addr (@@{$bccref}) d1216 4 a1219 1 $actions{$addr}=~s/^(ng|std)$/bcc-$1/; a1220 1 return %actions; d1223 9 a1231 6 # logging and dying with a message # does not return # if barfmail is set, then a mail with the log information is sent (message and detailfn-content) # args: the message to spit out, path to a file with details. # the details from the file are logged only, not printed in the die-message sub bailout d1233 4 a1236 3 my ($msg,$detailfn)=@@_; if ($detailfn && open(DF,$detailfn)) d1238 1 a1238 2 push @@detailederror,; close DF; d1240 3 d1244 6 a1249 16 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" ."The error message was:\n\n$msg\n\n\n"; print F "Detailed error message:\n\n" .join("",@@detailederror)."\n\n\n" if (@@detailederror); close F; bailout("error running $mta: $?") if ($?); $barfmail=$oldbarfmail; d1251 8 d1260 4 a1263 2 logit($msg,$detailfn); die($msg."\n"); d1266 24 d1291 1 a1291 5 # log the msg(s) to syslog or the logfile # the detailed info is put into @@detailederror # args: message, path to file with details # no retval. sub logit d1293 3 a1295 1 my ($msg,$detailfn)=@@_; d1297 4 a1300 1 if ($detailfn) d1302 1 a1302 6 @@detailederror=(); if (open(DF,$detailfn)) { push @@detailederror,; close DF; } d1304 13 d1318 3 a1320 1 if ($config{logfh}) d1322 2 a1323 4 # logfile is opened with autoflush set to 1, # so no extra flushing needed # we're more or less emulating the syslog format here... print { $config{logfh} } scalar(localtime)." $progname\[$$\] $msg\n"; d1325 30 a1354 1 else d1356 2 a1357 4 setlogsock('unix'); openlog($progname,"pid,cons","mail"); syslog("notice",$msg); closelog; d1359 2 d1363 3 d1367 552 a1918 1 &main; @ 2.15 log @no more use_agent, client_path config added getsecret and delsecret config options @ text @d23 1 a23 1 # $Id: kuvert,v 2.14 2005/02/25 22:09:21 az Exp az $ d417 1 a417 1 Filename => undef, d419 1 @ 2.14 log @fixed calling setup of mta: no more shell intervention @ text @d7 1 a7 1 # copyright (c) 1999-2003 Alexander Zangerl d23 1 a23 1 # $Id: kuvert,v 2.13 2003/08/03 02:06:53 az Exp az $ d34 1 d46 1 a46 1 # the passphrases are stored here if agent is not a/v d51 3 a56 1 my $pidf=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.pid.$<"; d85 1 a85 1 open(PIDF,"$pidf") || &bailout("cant open $pidf: $! -- exiting"); a93 1 unlink $pidf if ($options{"k"}); a107 16 # retain content of pidf, in case we cant lock it if (-f "$pidf") { open(PIDF,"+<$pidf") || &bailout("cant open <+$pidf: $! -- exiting"); } else { open(PIDF,">$pidf") || &bailout("cant open >$pidf: $! -- exiting"); } my $other=; chomp $other; logit("there seems to be another instance with PID $other") if ($other); &bailout("cant lock $pidf ($!) -- exiting.") if (!flock(PIDF,LOCK_NB|LOCK_EX)); seek(PIDF,0,'SEEK_SET'); a111 2 # make things clean and ready cleanup($config{tempdir},0); d113 2 a114 40 # 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 # if use_agent is set, check if the agent is running and start one # if needed. if ($config{use_agent}) { # not running? start a personal instance # and remember its pid if (!$ENV{"AGENT_SOCKET"}) { # start your own agent process # and remember its pid $config{private_agent}=open(SOCKETNAME,"-|"); bailout("cant fork agent: $! -- exiting") if (!defined $config{private_agent}); if ($config{private_agent}) # original process { # get the socketname my $res=; # 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 $debug && &logit("forked secret-agent pid $config{private_agent}," ."socket is $1"); } else { # the child that should exec the quintuple-agent exec "$config{agentpath}" || &bailout("cant exec $config{agentpath}: $! -- exiting"); } } } a122 1 d132 7 a138 5 # 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... # now read the keyrings d399 1 a399 1 if ($config{use_agent}) d402 3 a404 3 my $res=0xffff & system("$config{clientpath} delete " .$config{$type."key"}." >$config{tempdir}/subproc 2>&1"); bailout("error deleting broken passphrase from agent: $res", d411 1 a411 1 bailout("bad passphrase, but no passphrase agent to query!"); d457 1 a457 1 if ($config{use_agent}) d460 3 a462 3 my $res=0xffff & system("$config{clientpath} delete " .$config{$type."key"}." >$config{tempdir}/subproc 2>&1"); bailout("error deleting broken passphrasee from agent: $res", d469 1 a469 1 bailout("bad passphrase, but no passphrase agent to query!"); d537 1 a537 1 if ($config{use_agent}) d539 1 a539 1 if ($config{private_agent}) d541 5 a545 15 # kill the private agent process $res = kill('TERM',$config{private_agent}); &logit("problem killing $config{private_agent}: $!") if (!$res); wait; } else { foreach ($config{ngkey},$config{stdkey}) { next if (!$_); my $res=0xffff & system "$config{clientpath} delete $_"; &logit("problem deleting secret for $_: $res") if ($res); } d765 2 a766 4 use_agent=>0, private_agent=>0, clientpath=>undef, agentpath=>undef, d873 3 d880 9 a888 5 &bailout("bad agent-executable '$newconf{agentpath}' -- exiting") if ($newconf{agentpath} && $newconf{agentpath}=~/^(\S+)/ && ! -x $1); &bailout("bad client-executable '$newconf{clientpath}' -- exiting") if ($newconf{clientpath} && $newconf{clientpath}=~/^(\S+)/ && ! -x $1); a894 5 $newconf{use_agent}=$newconf{clientpath} && $newconf{agentpath}; # secret on demand is only possible with agent *and* X11 $newconf{secretondemand}=0 if (!$newconf{use_agent} || !$ENV{DISPLAY}); d1051 15 a1065 39 if ($config{use_agent}) { # cleanup possible previous blunder if ($res) { $res=0xffff & system("$config{clientpath} delete $id >$config{tempdir}/subproc 2>&1"); bailout("error deleting $id from agent: $res/$!","$config{tempdir}/subproc") if ($res); } # if we have a display, we can use the demand-query option of # client get, otherwise we use client put. # the display-situation is covered by sign() itself. if (!$ENV{DISPLAY}) { $res = 0xffff & system("$config{clientpath} put $id >$config{tempdir}/subproc 2>&1"); bailout("error running client storing $type passphrase: $res/$!", "$config{tempdir}/subproc") if ($res); } } else { # 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=); ReadMode("restore"); bailout("error reading $type passphrase: $!") if (!defined($phrase)); print "\n"; $secrets{$id}=$phrase; $phrase="x" x 64; } d1080 1 a1080 1 if ($config{use_agent}) d1082 2 a1083 2 $cmd="|$config{clientpath} get ". ($type eq "std"?$config{stdkey}:$config{ngkey}); @ 2.13 log @added auto-generation of blank .kuvert file @ text @d23 1 a23 1 # $Id: kuvert,v 2.12 2003/08/03 01:45:37 az Exp az $ d545 12 a556 5 open(TOMTA,("|$config{mta} ".join(" ",@@args))) || bailout("cant open pipe to $config{mta}: $!"); $ent->print(\*TOMTA); close(TOMTA); bailout("error running $config{mta}: $?") if ($?); @ 2.12 log @fixed bad bug with mixture of raw and encrypted mails (headers were lost) @ text @d23 1 a23 1 # $Id: kuvert,v 2.11 2003/04/25 07:52:15 az Exp az $ d95 10 a104 2 &bailout("no configuration file \"$rcfile\" -- exiting") if (! -r $rcfile); @ 2.11 log @fixed duplicate mail issues @ text @d23 1 a23 1 # $Id: kuvert,v 2.10 2003/02/22 04:57:58 az Exp az $ d321 8 d375 1 a375 1 foreach my $action qw(none ng ngsign std stdsign bcc-ng bcc-std) a379 6 if ($action eq "none") { logit("sending mail (raw) to ".join(",",@@recips)); &send_entity($in_ent,@@recips); next; } @ 2.10 log @fixed typo @ text @d7 1 a7 1 # copyright (c) 1999-2001 Alexander Zangerl d23 1 a23 1 # $Id: kuvert,v 2.9 2003/02/21 11:41:06 az Exp az $ d218 1 d220 1 d223 1 a223 1 chomp $@@; d227 2 a228 3 logit("problem \"$@@\" while processing $file," ." left as \".$file\".\n"); send_bounce($@@,$file); d1071 6 a1076 6 ."Your mail $config{queuedir}/$file could not be sent to some or all recipients.\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 was partial\n" ."or total, so please look into the log for further error indications.\n\n" d1426 6 a1436 7 my @@detailederror=(); if (open(DF,$detailfn)) { push @@detailederror,; close DF; } d1442 3 a1444 3 ."The error message was:\n-----\n$msg\n-----\n\n"; print F "Detailed error message:\n-----\n" .join("",@@detailederror)."\n-----\n\n" if (@@detailederror); @ 2.9 log @added x-mailer option @ text @d23 1 a23 1 # $Id: kuvert,v 2.8 2003/02/16 13:42:10 az Exp az $ d271 1 a271 1 $in_ent->head->add('X-mailer',"$progname $version") d822 1 a822 1 identify=0); @ 2.8 log @added -b option @ text @d23 1 a23 1 # $Id: kuvert,v 2.7 2003/02/08 13:09:39 az Exp az $ d270 4 d821 2 a822 1 logfh=>undef); @ 2.7 log @more pidfile fixing @ text @d23 1 a23 1 # $Id: kuvert,v 2.6 2003/02/08 13:08:06 az Exp az $ d47 1 a47 1 my $debug=0; d55 1 a55 1 if (!getopts("dkrnv",\%options) || @@ARGV) d57 1 a57 1 print "usage: $progname [-n] [-d] [-v] | [-k] | [-r] d62 2 a63 2 -v: output version and exit"; exit 1; d73 1 d82 1 a82 1 open(PIDF,"$pidf") || &bailout("cant open $pidf: $!"); d87 1 a87 1 &bailout("no valid pid found, cant kill any process.") d89 1 a89 1 &bailout("cant kill -$sig $pid: $!") d95 1 a95 1 &bailout("no configuration file \"$rcfile\", can't start!") d101 1 a101 1 open(PIDF,"+<$pidf") || &bailout("cant open <+$pidf: $!"); d105 1 a105 1 open(PIDF,">$pidf") || &bailout("cant open >$pidf: $!"); a108 1 seek(PIDF,0,'SEEK_SET'); d110 1 a110 1 &bailout("cant lock $pidf ($!), exiting.") d112 1 d136 1 a136 1 bailout("cant fork agent: $!") d156 1 a156 1 || &bailout("cant exec $config{agentpath}: $!"); d174 1 a174 1 &bailout("fork failed: $!") d197 1 a197 1 &bailout("cant open $config{queuedir}: $!") d224 1 a224 1 || &bailout("cant rename $config{queuedir}/$file: $!"); d233 1 a233 1 || &bailout("cant unlink $config{queuedir}/$file: $!"); d239 1 a239 1 bailout("problem unlocking $config{queuedir}/$file: $!") d802 16 a817 16 stdkey=>undef, pgppath=>"/usr/bin/pgp", gpgpath=>"/usr/bin/gpg", usepgp=>0, use_agent=>0, private_agent=>0, clientpath=>undef, agentpath=>undef, 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, logfh=>undef); d819 1 a819 1 &bailout("cant open $rcfile: $!") d854 1 a854 1 &bailout("bad config entry \"$_\""); d859 1 a859 1 &bailout("bad config entry \"$_\""); d873 1 a873 1 &bailout("cant mkdir $newconf{queuedir}: $!") d879 1 a879 1 &bailout("$newconf{queuedir} is not owned by you - refusing to run"); d883 1 a883 1 &bailout("$newconf{queuedir} does not have mode 0700 - refusing to run"); d892 1 a892 1 &bailout("cant mkdir $newconf{tempdir}: $!"); d897 1 a897 1 &bailout("$newconf{tempdir} is not owned by you - refusing to run"); d901 1 a901 1 &bailout("$newconf{tempdir} does not have mode 0700 - refusing to run"); d910 1 a910 1 &bailout("cant open logfile $newconf{logfile}: $!") d939 1 a939 1 bailout("bad ngkey spec '$newconf{ngkey}'","$newconf{tempdir}/subproc") if ($res); d943 1 a943 1 open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc |") || bailout("cant fork $newconf{gpgpath} to list sec keys: $!"); d954 2 a955 2 bailout("error running $newconf{gpgpath}: $?","$newconf{tempdir}/subproc") if ($?); bailout("could not find ngkey") if (!$newconf{ngkey}); d963 1 a963 1 bailout("bad stdkey spec \"$newconf{stdkey}\"","$newconf{tempdir}/subproc") if ($res); d968 1 a968 1 bailout("bad stdkey spec \"$newconf{stdkey}\"","$newconf{tempdir}/subproc") d977 1 a977 1 || bailout("cant fork $newconf{pgppath} to list sec keys: $!"); d988 1 a988 1 bailout("error running $newconf{pgppath}: $?","$newconf{tempdir}/subproc") d994 1 a994 1 || bailout("cant run $newconf{gpgpath} to list sec keys: $!\n","$newconf{tempdir}/subproc"); d1005 1 a1005 1 bailout("error running $newconf{gpgpath}: $?","$newconf{tempdir}/subproc") d1008 1 a1008 1 bailout("could not find stdkey") if (!$newconf{stdkey}); d1012 1 a1012 1 bailout("no keys whatsoever a/v!") if (!$newconf{stdkey} && !$newconf{ngkey}); d1014 1 a1014 1 bailout("config specifies ng but no ng key a/v") d1016 1 a1016 1 bailout("config specifies std but no std key a/v") d1063 1 a1063 1 bailout("cant fork $config{mta}: $!"); d1075 1 a1075 1 bailout("error running $config{mta}: $?") if ($?); d1413 1 d1420 25 d1448 1 @ 2.6 log @pidfile in $TMPDIR if available @ text @d23 1 a23 1 # $Id: kuvert,v 2.5 2003/02/05 22:45:39 az Exp az $ d107 1 @ 2.5 log @fixed duplicate entries in pidfile @ text @d23 1 a23 1 # $Id: kuvert,v 2.4 2003/01/21 12:27:01 az Exp az $ d53 1 a53 1 my $pidf="/tmp/kuvert.pid.$<"; @ 2.4 log @testing done. some minor fixes were necessary, but things got more reliable as well. more side-effect testing was done. @ text @d23 1 a23 1 # $Id: kuvert,v 2.3 2003/01/15 22:57:54 az Exp az $ d107 1 @ 2.3 log @fixed signing hoppala @ text @d23 1 a23 1 # $Id: kuvert,v 2.2 2003/01/15 15:03:03 az Exp az $ d48 1 d218 2 d222 1 a222 1 logit("problem \"$@@\" processing $file," d380 1 a380 1 &crypt_send($in_ent,$input,$type,$orig_header,$keys{$type}->{$_},$_); d439 1 a439 1 bailout("error deleting broken passphrasee from agent: $res", d688 7 a694 1 &logit("ignoring stdkey 0x$info[4]") if ($debug); d1053 1 d1063 4 a1066 2 ."The error message was:\n\n$res\n\n" ."$progname has no reliable way of figuring out whether this failure was partial\n" d1122 1 a1122 1 if (eof(STDIN)); d1148 1 a1148 1 $passphrase=$secrets{($type eq "std"?"stdkey":"ngkey")}; d1251 1 a1251 1 bailout("sign prog returned error ".$?>>8,"$config{tempdir}/subproc") if ($?>>8); d1357 2 a1358 2 # fallback-logic: ng-crypt or std-crypt, otherwise ngsign # -force: ng- or std-crypt for all, otherwise ngsign d1372 1 d1377 11 a1387 7 ($keys{ng}->{$addr} && ($actions{$addr}="ng")) || ($keys{std}->{$addr} && ($actions{$addr}="std")) || ($actions{$addr}="ngsign"); } elsif ($actions{$addr} eq "ng") { $actions{$addr}="ngsign" if (!$keys{ng}->{$addr}); d1389 1 a1389 1 elsif ($actions{$addr} eq "std") d1391 4 a1394 1 $actions{$addr}="stdsign" if (!$keys{std}->{$addr}); d1421 1 a1426 1 my @@lotsaoutput=($msg); d1430 1 a1430 1 push @@lotsaoutput,"Details:"; d1433 1 a1433 1 push @@lotsaoutput,; d1443 1 a1443 4 foreach (@@lotsaoutput) { print { $config{logfh} } scalar(localtime)." $progname\[$$\] $_\n"; } d1449 1 a1449 4 foreach (@@lotsaoutput) { syslog("notice",$_); } @ 2.2 log @lotsa small and medium bugs removed pgp/gpg interaction tested, works everywhere. agent-related passphrase handling works perfectly todo: test findaction, test signonly, !secretondemand, !agent check errormsg in send bounce (incomplete) @ text @d23 1 a23 1 # $Id: kuvert,v 2.1 2003/01/12 15:21:03 az Exp az $ d448 1 a448 1 Path => "output", d1148 1 a1148 1 $cmd.="|$config{gpgpath} -q -t --batch --armor --passphrase-fd 0 --status-fd 1 --default-key"; d1423 4 a1426 1 map { print $config{logfh} (scalar(localtime)." $progname\[$$\] $_\n"); } (@@lotsaoutput); d1432 4 a1435 1 map { syslog("notice",$_); } (@@lotsaoutput); @ 2.1 log @fixed bugs in config and keyring reading @ text @d23 1 a23 1 # $Id: kuvert,v 2.0 2003/01/12 14:05:48 az Exp az $ d218 1 a218 1 && &bailout("cant rename $config{queuedir}/$file: $!"); d227 1 a227 1 && &bailout("cant unlink $config{queuedir}/$file: $!"); d359 1 d430 15 a444 2 # unless we can't query for the passphrase... bailout("bad passphrase, but no passphrase agent to query!") if (!$config{secretondemand}); d488 15 a502 2 # unless we can't query for the passphrase... bailout("bad passphrase, but no passphrase agent to query!") if (!$config{secretondemand}); d601 1 a601 1 my ($lastkey,@@tmp,$name,$now,@@info); d606 1 a606 1 if ($config{use_pgp}) d608 5 a612 10 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) d614 10 a623 1 if (/^pub\s+\d+\/(\S+)\s+(.+)$/) d625 1 a625 3 my ($key,$userspec)=($1,$2); if ($userspec =~ /]+)>?/) d627 24 a650 12 $name=lc($1); } else { undef $name; } if ($name) { $keys{std}->{$name}="0x$key"; $lastkey=$key; &logit("got stdkey 0x$key for $name") if ($debug); d652 1 a652 1 else d654 3 a656 3 $lastkey=$key; &logit("saved stdkey 0x$key, no address known yet") if ($debug); a657 7 next; } if (/^\s+.*]+)>?\s*$/) { my $name=lc($1); $keys{std}->{$name}="0x$lastkey"; &logit("got stdkey (uid) 0x$lastkey for $name") if ($debug); a659 1 logit("reading ng keyring."); a660 1 logit("reading combined keyring.") if (!$config{use_pgp}); a662 24 # 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) { @@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 !$use_pgp # and be sure to skip these uid's, too if ($config{use_pgp} && $info[3] eq "1") { &logit("ignoring stdkey 0x$info[4]") if ($debug); undef $lastkey; next; } $info[9] =~ s/\\x3a/:/g; # re-insert colons, please d664 10 a673 5 # 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] =~ /]+)>?/) d675 22 a696 15 $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) d698 1 a698 14 # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring ".($info[3]==1?"std":"ng") ." key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } $keys{ng}->{$name}="0x$lastkey"; &logit("got ".($info[3]==1?"std":"ng") ." key 0x$lastkey for $name") if ($debug); d702 1 a702 3 &logit("saved ".($info[3]==1?"std":"ng") ." key 0x$lastkey, no address known yet") if ($debug); d704 3 a706 15 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="" if (!$name); &logit("ignoring uid $name, belongs to std key?") if ($debug); } else d708 5 d719 2 a720 2 ." uid $name for 0x$lastkey, " ."reason: ".$badcauses{$info[1]}); d723 4 a726 4 $keys{ng}->{$name}="0x$lastkey"; &logit("got ".($info[3]==1?"std":"ng") ." key (uid) 0x$lastkey for $name") d731 15 a745 1 &logit("ignoring uid without valid address") d748 23 d774 4 d842 1 a842 1 logit("ignoring unknown config entry \"$_\""); d845 4 d907 5 a911 5 (logit("bad agent-executable '$newconf{agent}', disabling agent support"), $newconf{agentpath}=0) if ($newconf{agentpath} && $newconf{agentpath}=~/^(\S+)/ && ! -x $1); (logit("bad client-executable '$newconf{clientpath}', disabling agent support"), $newconf{clientpath}=0) if ($newconf{clientpath} && $newconf{clientpath}=~/^(\S+)/ && ! -x $1); d931 1 a931 1 open(F,"$newconf{gpgpath} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc |") || bailout("cant run $newconf{gpgpath} to list sec keys: $!","$newconf{tempdir}/subproc"); d935 2 a936 1 next if ($list[0] ne "sec" || $list[3] ne "1"); d942 1 d950 1 a950 1 my $res=0xffff & system("$newconf{pgppath} -kv $newconf{stdkey} $home/.pgp/secring.pgp 2>$newconf{tempdir}/subproc"); a955 1 # fixme: output gpg result d965 1 a965 1 || bailout("cant run $newconf{pgppath} to list sec keys: $!","$newconf{tempdir}/subproc"); d976 2 d986 2 a987 1 next if ($list[0] ne "sec" || $list[3] ne "17"); d993 2 d1000 2 d1006 1 d1060 1 d1081 1 a1081 1 $res=0xfff & system("$config{clientpath} delete $id >$config{tempdir}/subproc 2>&1"); a1126 11 if ($enc) { # some sanity checking bailout("empty recipient list passed") if (!@@recips); foreach (@@recips) { bailout("oops. unknown encryption key $_") if (!$keys{$type}->{$_}); } } d1174 1 a1174 1 ."-u $config{stdkey} -seat -o $outfile $infile" d1207 1 a1207 1 unlink($outfile) if ($outfile); d1233 2 a1234 3 return 1 if (grep(/^\[GNUPG:\] BAD_PASSPHRASE/,@@result)); d1245 1 a1245 1 unlink("$outfile.inter1"); d1247 1 a1247 1 & system("$config{gpgpath} --batch -q --store -z 0 -o $outfile.inter1 " d1255 2 a1256 2 unlink("$outfile.inter2"); open(H,"|$config{gpgpath} --no-literal --store --compress-algo 1 " d1264 1 a1264 1 bailout("error $? running gpg","$config{tempdir}/subproc") if ($?); d1267 2 a1268 1 $cmd="$config{gpgpath} --no-literal --encrypt --rfc1991 --cipher-algo idea " d1275 1 a1275 1 bailout("error $res running gpg","$config{tempdir}/subproc") if ($res); d1310 1 a1310 2 # apply default if necessary $actions{$addr}=$config{"default"} if (! exists $actions{$addr}); d1413 3 a1415 6 foreach () { push @@lotsaoutput,$_; } close DF; } @ 2.0 log @first non-syntax errored version of kuvert 1.1.0 no testing done yet, though @ text @d23 1 a23 1 # $Id: kuvert,v 1.27 2002/10/27 13:45:50 az Exp az $ d151 2 a152 2 exec "$config{agent}" || &bailout("cant exec $config{agent}: $!"); d230 1 a230 3 eval {cleanup("$config{tempdir}",0);}; bailout("problem cleaning $config{tempdir}: $@@") if ($@@); d244 1 a244 1 # dies on errors d399 1 a399 1 # dies on errors d492 1 a492 1 # dies on errors d506 1 a506 1 # dies on error, no retval d512 1 a512 1 opendir(F,$what) || die "cant opendir $what: $!\n"; d516 2 a517 2 (-d "$what/$name")?cleanup("$what/$name",1): (unlink("$what/$name") || die "cant unlink $what/$name: $!\n"); d520 1 a520 1 $remove_what && (rmdir("$what") || die "cant rmdir $what: $!\n"); d549 1 a549 1 my $res=0xffff & system "$config{client} delete $_"; d587 3 a589 1 @@tmp=`$config{pgp} -kv 2>$config{tempdir}/subproc`; d596 1 a596 1 if ($userspec =~ /<(.+)>/) d619 1 a619 1 if (/^\s+.*<(\S+)>\s*$/) d634 3 a636 1 @@tmp=`$config{gpg} -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$config{tempdir}/subproc`; d660 1 a660 1 if ($info[9] =~ /<(.+)>/) d742 1 a742 1 # dies on major problems d752 2 a753 2 pgp=>"/usr/bin/pgp", gpg=>"/usr/bin/gpg", d757 2 a758 2 client=>undef, agent=>undef, d865 5 a869 4 $newconf{agent}=0) if ($newconf{agent} && $newconf{agent}=~/^(\S+)/ && ! -x $1); (logit("bad client-executable '$newconf{client}', disabling agent support"), $newconf{client}=0) if ($newconf{client} && $newconf{client}=~/^(\S+)/ && ! -x $1); &bailout("bad executable '$newconf{pgp}' -- exiting") d871 2 a872 2 && (!$newconf{pgp} || $newconf{pgp}=~/^(\S+)/ && ! -x $1)); &bailout("bad executable '$newconf{gpg}' -- exiting") d874 1 a874 1 && ( !$newconf{gpg} || $newconf{gpg}=~/^(\S+)/ && ! -x $1)); d876 1 a876 1 $newconf{use_agent}=$newconf{client} && $newconf{agent}; d883 1 a883 1 my $res=0xffff & system("$newconf{gpg} -q --batch --list-secret-keys --with-colons $newconf{ngkey} >$newconf{tempdir}/subproc 2>&1"); d888 1 a888 1 open(F,"$newconf{gpg} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc |") || bailout("cant run $newconf{gpg} to list sec keys: $!","$newconf{tempdir}/subproc"); d905 1 a905 1 my $res=0xffff & system("$newconf{pgp} -kv $newconf{stdkey} $home/.pgp/secring.pgp 2>$newconf{tempdir}/subproc"); d910 1 a910 1 my $res=0xffff & system("$newconf{gpg} -q --batch --list-secret-keys --with-colons $newconf{stdkey} >$newconf{tempdir}/subproc 2>&1"); d920 2 a921 2 open(F,"$newconf{pgp} -kv $home/.pgp/secring.pgp 2>$newconf{tempdir}/subproc |") || bailout("cant run $newconf{pgp} to list sec keys: $!","$newconf{tempdir}/subproc"); d935 2 a936 2 open(F,"$newconf{gpg} -q --batch --list-secret-keys --with-colons 2>$newconf{tempdir}/subproc|") || bailout("cant run $newconf{gpg} to list sec keys: $!\n","$newconf{tempdir}/subproc"); d992 1 a992 1 # no return value, dies on problems d1013 1 a1013 1 # retval: none, changes %secrets, dies on major errors d1028 1 a1028 1 $res=0xfff & system("$config{client} delete $id >$config{tempdir}/subproc 2>&1"); d1038 1 a1038 1 $res = 0xffff & system("$config{client} put $id >$config{tempdir}/subproc 2>&1"); d1069 1 a1069 1 # returns: 0 if ok, 1 if bad passphrase, dies on other errors d1089 1 a1089 1 $cmd="|$config{client} get ". d1102 1 a1102 1 $cmd.="|PGPPASSFD=0 $config{pgp} +batchmode -u $config{stdkey} -sbat"; d1106 1 a1106 1 $cmd.="|$config{gpg} -q -t --batch --armor --passphrase-fd 0 --status-fd 1 --default-key"; d1131 1 a1131 1 $cmd.="|PGPPASSFD=0 $config{pgp} +batchmode " d1140 1 a1140 1 $cmd.="|$config{gpg} -q -t --batch --armor --passphrase-fd 0 " d1155 1 a1155 1 $cmd.="|$config{gpg} --batch -q --detach-sign " d1206 1 a1206 1 & system("$config{gpg} --batch -q --store -z 0 -o $outfile.inter1 " d1215 1 a1215 1 open(H,"|$config{gpg} --no-literal --store --compress-algo 1 " d1217 1 a1217 1 || bailout("cant open pipe to $config{gpg}: $!"); d1226 1 a1226 1 $cmd="$config{gpg} --no-literal --encrypt --rfc1991 --cipher-algo idea " d1356 1 a1356 1 die($msg.'\n'); @ 1.27 log @unified keyring handling @ text @d23 1 a23 1 # $Id: kuvert,v 1.26 2002/09/25 12:12:32 az Exp az $ a29 1 a31 1 d33 1 d35 1 a35 8 my %options; if (!getopts("dkrnv",\%options) || @@ARGV) { 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"; exit 1; } d38 1 a38 7 if ($options{'v'}) { print STDERR "kuvert $version\n"; exit 0; } d40 1 a40 17 my($name,$home)=(getpwuid($<))[0,7]; # where is our in-queue my $queuedir="$home/.kuvert_queue"; # which mta to use my $mta="/usr/lib/sendmail -om -oi -oem"; # where to put temp files for parsing mime my $tempdir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$<.$$"; # 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 d42 4 a45 35 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 my $progname="kuvert"; # 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'; # cat, needed if !use_pgp 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 d47 1 d49 4 a52 20 # 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; d54 18 a71 2 # set this to 1 for more verbose debugging output to syslog my $debug=0; d73 6 a78 18 # 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); # usually this program logs to syslog, but it can log to a file as well my ($lf,$logfile); $debug=1 if ($options{"d"}); # 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'); d80 4 a83 4 open(PIDF,"$pidf") || &bailout("cant open $pidf: $!"); $pid=; close(PIDF); chomp $pid; d85 1 a85 1 &bailout("no valid pid found, cant kill any process.") d87 5 a91 5 &bailout("cant kill -$sig $pid: $!") if (!kill $sig, $pid); unlink $pidf if ($options{"k"}); exit 0; } d93 2 a94 2 &bailout("no configuration file, can't start!") if (! -r $config); d96 13 a108 1 logit("version $version starting"); d110 1 a110 9 # and now for some real work... if (-f "$pidf") # retain content of pidf, in case we cant lock it { open(PIDF,"+<$pidf") || &bailout("cant open <+$pidf: $!"); } else { open(PIDF,">$pidf") || &bailout("cant open >$pidf: $!"); } d112 4 a115 2 &bailout("cant lock $pidf ($!), another process running?, exiting") if (!flock(PIDF,LOCK_NB|LOCK_EX)); d117 3 a119 2 # read the config, setup the queuedir and tempdir &read_config; d121 43 a163 10 # cleanup tempdir my $res; &bailout("cant clean $tempdir: $res") if ($res=cleanup($tempdir,0)); # 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; &bailout("secrets could not be initialized properly: $res") if ($res); a164 3 if (!$options{"d"} && !$options{"n"}) { my $res=fork; d166 8 a173 5 &bailout("fork failed: $!") if ($res == -1); exit 0 if ($res); } d175 6 a180 14 # 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... # now read the keyrings &read_keyrings; # 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; d182 7 a188 5 # the main loop, left only via signal handler handle_term while (1) { &bailout("cant open $queuedir: $!") if (!opendir(D,"$queuedir")); d190 2 a191 2 my $file; foreach $file (readdir(D)) d193 18 a210 1 my $res; d212 25 a236 7 # 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)) { a237 2 logit("$file is locked, skipping."); next; d239 4 a242 32 #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); &handle_term("debug mode") if ($options{"d"}); sleep($interval); d245 2 a246 2 # returns 0 if ok # stuff in the temp directory is removed by the main loop a249 3 my ($res); my @@sent; d253 2 a254 4 # set output to tempdir $parser->output_dir($tempdir); # everything less than 100k goes to core mem $parser->output_to_core(100000); d257 2 d260 1 a260 1 my $in_ent = $parser->read(\$fh); d262 3 a264 7 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); } d267 2 a268 2 my $custom_conf=lc($in_ent->head->get($conf_header)); $in_ent->head->delete($conf_header); a275 2 # extract a possible resend-request-header # if a/v, call $mta immediately d277 2 a278 1 if ($custom_conf eq "none" || $in_ent->head->get($resend_indicator)) d280 2 a281 8 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"); } d284 1 a284 1 $res=&send_entity($in_ent,"-t"); d286 1 a286 8 if ($res) { return "mail was not sent to anybody: $res"; } else { return 0; } d289 2 a290 2 my (@@recip_none,@@recip_sign_std,@@recip_sign_ng, @@recip_crypt_std,@@recip_crypt_ng,@@recip_all); d292 8 a299 3 # note: bcc handling is not implemented. map { push @@recip_all, lc($_->address); } Mail::Address->parse($in_ent->head->get("To"), $in_ent->head->get("Cc")); d304 2 a305 4 if (!@@recip_all) { return "no recipients found! the mail headers seem to be garbled."; } d308 1 a308 1 my %actions=findaction($custom_conf,@@recip_all); d310 2 a311 6 # 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); d313 7 d321 2 a322 7 # 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) d324 10 a333 2 $in_ent->purge; # only if the sending went wrong return ("mail was not sent to anybody: $res"); d335 21 a355 1 push @@sent,@@recip_none; d358 3 a360 5 # shortcut if just recipients without crypt/sign # and no other recipients are given return 0 if (!@@recip_sign_std && !@@recip_sign_ng && !@@recip_crypt_std && !@@recip_crypt_ng); d362 7 a368 5 # 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; d370 14 a383 4 # content-* stays with the entity and the rest moves to orig_header foreach $headername ($in_ent->head->tags) { if ($headername !~ /^content-/i) d385 3 a387 2 # remove the stuff from the entity $in_ent->head->delete($headername); d391 4 a394 2 # remove this stuff from the orig_header $orig_header->delete($headername); a396 103 # 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) { return ("no std key known, can't sign! mail was sent to ". (@@sent?join(",",@@sent):"nobody") .",\nnot to anybody else") if (!$std_defkey); 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) { return ("no ng key known, can't sign! mail was sent to ". (@@sent?join(",",@@sent):"nobody") .",\nnot to anybody else") if (!$ng_defkey); 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; return ("no std key known, can't encrypt! mail was sent to ". (@@sent?join(",",@@sent):"nobody") .",\nnot to anybody else") if (!$std_defkey); 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; return ("no ng key known, can't encrypt! mail was sent to ". (@@sent?join(",",@@sent):"nobody") .",\nnot to anybody else") if (!$ng_defkey); 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; d399 3 a401 1 # return 0 if ok, errortext otherwise d404 2 a405 2 my ($ent,$ent_file,$rec,$cmd,$micalg,$header,$type)=@@_; my $res; d409 1 a409 1 # make a private copy of the passed header and set this one d419 1 a419 1 $newent->head->mime_attr("content-Type.Micalg" => "pgp-$micalg"); d423 1 a423 1 "You'll need GPG or PGP to check the signature.\n"]); d428 6 a433 7 # make sure outfile is not existing unlink("$tempdir/$tempfile_out"); # generate the signature $res=&$cmd($ent_file,"$tempdir/$tempfile_out"); return $res if ($res); d436 2 a437 2 Path => "$tempdir/$tempfile_out", Filename => "signature.$type", a439 1 d441 1 a441 1 return &send_entity($newent,@@{$rec}); d444 2 a445 1 # return 0 if ok, errortext otherwise d448 2 a449 2 my ($ent,$ent_file,$rec,$rec_keys,$cmd,$header)=@@_; my $res; d453 1 a453 1 # make a private copy of the passed header and set this one d465 2 a466 2 "It has been encrypted conforming to RFC2015.\n", "You'll need PGP or GPG to view the content.\n"]); d473 7 a479 7 # 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); d482 1 a482 1 Path => "$tempdir/$tempfile_out", d488 1 a488 1 return &send_entity($newent,@@{$rec}); a490 20 # log the msg(s) to syslog or the logfile sub logit { my $msg = shift(@@_); 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; } } d494 1 a494 1 # returns 0 if ok or an errortext d499 2 a500 2 open(TOMTA,("|$mta ".join(" ",@@args))) || return "cant open pipe to $mta: $!"; d503 1 a503 3 return "error when calling $mta: $!" if ($?); return ""; d508 1 a508 1 # returns: "" or errormsg d514 1 a514 1 opendir(F,$what) || return "cant opendir $what: $!"; d517 3 a519 11 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: $!"; } d522 1 a522 4 if ($remove_what) { rmdir("$what") || return "cant rmdir $what: $!"; } a529 1 my $res; d532 2 a533 2 $res=&cleanup($tempdir,1); logit("problem cleaning up $tempdir: $res") d535 24 a558 4 $res=&wipe_keys; logit("problem doing the module cleanup routine: $res") if ($res); close $lf if ($lf); d579 1 a579 1 %stdkeys=(); d581 1 a581 1 if ($use_pgp) d589 1 a589 1 @@tmp=`$PGP -kv 2>$tempdir/subprocess`; d607 1 a607 1 $stdkeys{$name}="0x$key"; d622 1 a622 1 $stdkeys{$name}="0x$lastkey"; d628 1 a628 1 logit("reading combined keyring.") if (!$use_pgp); d630 1 a630 1 %ngkeys=(); d634 1 a634 1 @@tmp=`$GPG -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tempdir/subprocess`; d645 1 a645 1 if ($use_pgp && $info[3] eq "1") a651 1 # fixme lowprio: more general unquote d685 1 a685 1 $ngkeys{$name}="0x$lastkey"; d724 1 a724 1 $ngkeys{$name}="0x$lastkey"; d739 3 d744 1 a744 1 my (@@tmp,$lastkey,$mtaopt); d746 19 a764 3 # get the list of special adresses and adress-regexps &bailout("cant open $config: $!\n") if (!open (F,$config)); d766 2 a768 4 %config=(); $config{default}='none'; @@configkeys=(); a772 76 # 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; } d774 1 a774 2 if (/^TEMPDIR\s+(\S+)\s*$/) d776 13 a788 3 logit("set tempdir to $1") if ($options{"d"}); $tempdir=$1; next; d790 1 a790 2 if (/^LOGFILE\s+(\S+)\s*$/) d792 1 a792 11 # 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; } d794 1 a794 4 if (/^(\S+)\s+(\S+)\s*$/) { my ($key,$action)=(lc($1),lc($2)); if ($action=~/^(none|std(sign)?|ng(sign)?|fallback)(-force)?$/) d796 2 a797 3 $config{$key}=$action; push @@configkeys, $key; logit("got conf $action for $key") if ($options{"d"}); d801 1 a801 1 logit("ignoring bad action \"$action\" for $key"); a804 1 d807 5 d813 1 a813 1 if (!-d $queuedir) d815 3 a817 3 unlink "$queuedir"; &bailout("cant mkdir $queuedir: $!") if (!mkdir($queuedir,0700)); d820 1 a820 1 elsif ((stat($queuedir))[4] != $<) d822 1 a822 1 &bailout("$queuedir is not owned by you - refusing to run"); d824 1 a824 1 elsif ((stat($queuedir))[2]&0777 != 0700) d826 1 a826 1 &bailout("$queuedir does not have mode 0700 - refusing to run"); d829 2 a830 2 # gen tempdir for storing mime-stuff if (!-d $tempdir) d832 2 a833 2 unlink "$tempdir"; if (!mkdir($tempdir,0700)) d835 1 a835 1 &bailout("cant mkdir $tempdir: $!"); d838 1 a838 1 elsif ((stat($tempdir))[4] != $<) d840 1 a840 1 &bailout("$tempdir is not owned by you - refusing to run"); d842 1 a842 1 elsif ((stat($tempdir))[2]&0777 != 0700) d844 1 a844 1 &bailout("$tempdir does not have mode 0700 - refusing to run"); d846 4 d851 45 a895 16 # consistency checks $use_agent=$client && $agent; $secret_on_demand=0 if (!$use_agent); # sanity checks &bailout("bad ng executable '$GPG' -- exiting") if (! -x $GPG); &bailout("bad std executable '$PGP' -- exiting") if ($use_pgp && ! -x $PGP); if ($mtaopt && $mtaopt =~ /^(\S+)/) { &bailout("bad MTA '$mtaopt' -- exiting") if (! -x $1); $mta=$mtaopt; d897 2 a898 2 if ($use_agent) d900 6 a905 1 foreach my $x ($client,$agent) d907 4 a910 2 &bailout("bad agent executable '$x' -- exiting") if (! -x $x); d913 44 d962 1 d981 3 a983 2 $entity->head->mime_attr("content-transfer-encoding" => "quoted-printable"); d988 2 a989 2 # notify the sender of the problem d994 10 a1003 7 open(F,"|$mta -t") || return; print F "From: $name\nTo: $name\nSubject: $progname Mail Send Failure\n\n"; 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"; d1007 6 a1012 3 # sign a infile and write it to outfile # args: infile,outfile sub std_sign d1014 5 a1018 60 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,@@_); } # 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 d1020 1 a1020 157 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=; 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=; 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=; 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 # 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 ($?) d1022 7 a1028 10 open F,"$tempdir/subprocess"; my @@result=; 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); } d1030 10 a1039 11 # 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=; close F; return "error running gpg literal conversion: $res\n" .join("\n",@@result); d1041 1 a1041 6 # 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) d1043 15 a1057 21 open F,"$tempdir/subprocess"; my @@result=; 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-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=; close F; return "error running gpg encryption: $res\n" .join("\n",@@result); d1059 1 a1059 26 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=; 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); d1061 1 d1064 4 a1067 6 # 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 d1069 2 a1070 3 my ($id)=@@_; my ($res,$phrase); d1072 1 a1072 1 if ($use_agent) d1074 3 a1076 9 # 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}); # now let the secret client handle the situation: # it asks for the secret and stores it $res = 0xffff & system "$cmd"; if ($res) d1078 2 a1079 5 open F,"$tempdir/subprocess"; my @@result=; close F; return "$client returned error code $res\n" .join("\n",@@result); a1080 1 return 0; d1082 3 a1084 29 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) d1086 2 a1087 11 # 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; } } a1088 1 # else we ask gpg to show the secring and use the first rsa key d1091 1 a1091 14 @@tmp=`$GPG -q --batch --list-secret-keys --with-colons 2>$tempdir/subprocess`; 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; } } d1094 2 a1095 3 # now, get the ng key @@tmp=`$GPG -q --batch --list-secret-keys --with-colons 2>$tempdir/subprocess`; foreach (@@tmp) d1097 1 a1097 5 @@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 d1099 1 a1099 3 $ngkey="0x$list[4]"; &logit("defaultkey for ng is $ngkey") if ($debug); last; d1101 1 a1101 30 } 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; # set the std keys if no overrides given and keys were found $std_defkey=$stdkey if (!defined($std_defkey) && $stdkey); $ng_defkey=$ngkey if (!defined($ng_defkey) && $ngkey); return "no default key for std known" if (!defined $std_defkey); return "no default key for ng known" if (!defined $ng_defkey); # 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"}) d1103 2 a1104 5 # 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 d1106 1 a1106 11 # get the socketname $res=; # 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"}); d1109 2 a1110 4 # the child that should exec the quintuple-agent { exec "$agent" || &bailout("cant exec $agent: $!"); d1113 3 a1115 8 } if (!$secret_on_demand) { # get the std passphrase and verify it, # but only if we're doing std pgp at all # i.e. keyid!=0 if ($std_defkey) d1117 1 a1117 10 do { $res=&askput_secret($std_defkey); bailout("could not read passphrase for $std_defkey: $res") if ($res); $res=std_sign(undef,undef); print "wrong passphrase, try again.\n" if ($res); } while ($res); d1119 1 a1119 4 # get the ng passphrase and verify it # again, only if ng pgp/gpg requested/possible if ($ng_defkey) d1121 1 a1121 10 do { $res=&askput_secret($ng_defkey); bailout("could not read passphrase for $ng_defkey: $res") if ($res); $res=ng_sign(undef,undef); print "wrong passphrase, try again.\n" if ($res); } while ($res); d1124 1 a1124 11 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) d1126 1 a1126 1 if ($private_agent) d1128 3 a1130 4 # kill the private agent process $res = kill('TERM',$private_agent); &logit("problem killing $private_agent: $!") if (!$res); wait; d1134 2 a1135 1 if ($std_defkey) d1137 5 a1141 3 $res = 0xffff & system "$client delete $std_defkey"; &logit("problem deleting secret for $std_defkey: $res") if ($res); d1143 1 a1143 1 if ($ng_defkey) d1145 11 a1155 3 $res = 0xffff & system "$client delete $ng_defkey"; &logit("problem deleting secret for $ng_defkey: $res") if ($res); a1158 2 return ""; } d1160 3 d1164 4 a1167 9 # requests the passphrase from the agent and runs it # 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; d1169 5 a1173 1 while (1) d1175 9 a1183 2 # let the sign subroutine check for validity if ($key eq $std_defkey) d1185 6 a1190 1 $res=std_sign(undef,undef); d1192 4 a1195 9 else { $res=ng_sign(undef,undef); } # ok? then exit return 0 if (!$res); # otherwise nuke the key and redo this system("$client delete $key"); d1197 35 a1231 1 exit 1; # must not reach here a1233 1 d1235 1 a1235 1 # input: addresses and custom-header d1238 2 a1239 1 # resulting actions are: ng, ngsign, std, stdsign, none. a1240 1 # fixme: uses globals stdkeys, ngkeys, options d1243 2 a1244 2 my ($custom,@@addrs,@@affected)=@@_; my (%actions,$addr); d1246 2 a1247 2 # lookup addresses in config foreach $addr (@@addrs) d1249 1 a1249 2 # go through the configkeys foreach (@@configkeys) d1251 1 a1251 1 if ($addr =~ /$_/i) d1253 2 a1254 3 $actions{$addr}=$config{$_}; logit("found directive: $addr -> $actions{$addr}") if ($options{"d"}); d1261 1 a1261 2 logit("custom conf header: overrides $addr -> $custom") if ($options{"d"}); d1269 9 a1277 2 # now check the found actions: anyone with -force options? foreach $addr (@@addrs) d1281 1 a1281 2 logit("found force directive: $addr -> $actions{$addr}") if ($options{"d"}); d1286 1 a1286 1 @@affected = grep($actions{$_} ne "none",@@addrs); d1293 1 a1293 1 $force="stdsign" if (grep(!exists $stdkeys{$_}, @@affected)); d1297 1 a1297 1 $force="ngsign" if (grep(!exists $ngkeys{$_}, @@affected)); d1304 2 a1305 2 if (grep(!exists $ngkeys{$_} && !exists $stdkeys{$_}, @@affected)); d1310 1 a1310 2 logit("final force directive: $force") if ($options{"d"}); d1315 2 a1316 2 # finally check the actions for fallback, ng or std and expand that foreach $addr (@@addrs) d1320 2 a1321 2 ($ngkeys{$addr} && ($actions{$addr}="ng")) || ($stdkeys{$addr} && ($actions{$addr}="std")) d1326 1 a1326 1 $actions{$addr}="ngsign" if (!$ngkeys{$addr}); d1330 1 a1330 1 $actions{$addr}="stdsign" if (!$stdkeys{$addr}); d1332 8 a1339 1 logit("final action: $addr -> $actions{$addr}") if ($options{"d"}); d1345 3 a1347 1 # does not return. one arg: the message to spit out d1350 1 a1350 1 my ($msg)=@@_; d1352 1 a1352 1 logit($msg); d1355 38 d1394 1 @ 1.26 log @fixed idea-problem @ text @d23 1 a23 1 # $Id: kuvert,v 1.25 2002/09/19 16:43:25 az Exp az $ d700 2 d704 162 a865 4 logit("reading std keyring."); %stdkeys=&std_listkeys; logit("reading ng keyring."); %ngkeys=&ng_listkeys; a1107 6 # 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; } a1140 53 # 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=(); # this does not care if pgp is not existent...but then, we're not # needing the pgp keyring @@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; } a1409 231 } # list keys # returns: hash of address,key sub gpg_listkeys_norsa { my (%ngkeys,$lastkey,@@tmp,@@info,$now); my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); $now=time; # 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`; 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) { # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring DSA key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } $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="" if (!$name); &logit("ignoring uid $name, belongs to rsa key") if ($debug); } else { if ($name) { # 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; } $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); my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); $now=time; # 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`; 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]; # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring RSA key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } 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="" if (!$name); &logit("ignoring uid $name, belongs to dsa/elg key") if ($debug); } else { if ($name) { # 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; } $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; @ 1.25 log @fixed loop bug, added proper default action @ text @d23 1 a23 1 # $Id: kuvert,v 1.24 2002/09/19 16:25:46 az Exp az $ d1268 1 a1268 2 $cmd="$GPG --no-options --load-extension idea " ."--no-literal --encrypt --rfc1991 --cipher-algo idea " @ 1.24 log @added better secretondemand handling @ text @d23 1 a23 1 # $Id: kuvert,v 1.23 2002/09/19 14:58:21 az Exp az $ d718 2 d838 1 d840 1 a840 1 close F; d842 22 a863 2 # generate queuedir if not existing if (!-d $queuedir) d865 1 a865 30 unlink "$queuedir"; &bailout("cant mkdir $queuedir: $!") if (!mkdir($queuedir,0700)); } # check queuedir owner & perm elsif ((stat($queuedir))[4] != $<) { &bailout("$queuedir is not owned by you - refusing to run"); } elsif ((stat($queuedir))[2]&0777 != 0700) { &bailout("$queuedir does not have mode 0700 - refusing to run"); } # gen tempdir for storing mime-stuff if (!-d $tempdir) { unlink "$tempdir"; if (!mkdir($tempdir,0700)) { &bailout("cant mkdir $tempdir: $!"); } } elsif ((stat($tempdir))[4] != $<) { &bailout("$tempdir is not owned by you - refusing to run"); } elsif ((stat($tempdir))[2]&0777 != 0700) { &bailout("$tempdir does not have mode 0700 - refusing to run"); d868 9 a876 1 d880 1 a880 1 @ 1.23 log @added MTA option @ text @d23 1 a23 1 # $Id: kuvert,v 1.22 2002/09/19 09:51:25 az Exp az $ d152 1 a152 1 open(PIDF,"$pidf") || die "cant open $pidf: $!\n"; d157 1 a157 1 die "no valid pid found, cant kill any process.\n" d159 2 a160 4 if (!kill $sig, $pid) { die "cant kill -$sig $pid: $!\n"; } d165 2 a166 6 if (! -r $config) { logit("no configuration file, can't start!"); die("no configuration file, can't start!\n"); exit 1; } d173 1 a173 1 open(PIDF,"+<$pidf") || die "cant open <+$pidf: $!\n"; d177 1 a177 6 open(PIDF,">$pidf") || die "cant open >$pidf: $!\n"; } if (!flock(PIDF,LOCK_NB|LOCK_EX)) { logit("cant lock $pidf ($!), another process running?, exiting"); die "cant lock $pidf ($!), another process running?, exiting\n"; d180 5 a184 4 # get the list of known keys and the configuration-stuff, # setup the queuedir and tempdir # the hup-handler does this handle_reload(); d188 2 a189 5 if ($res=cleanup($tempdir,0)) { logit("cant clean $tempdir: $res"); die "cant clean $tempdir: $res\n"; } d195 1 a195 1 die "secrets could not be initialized properly: $res\n" if ($res); d201 1 a201 1 die "fork failed: $!\n" d211 2 d225 2 a226 5 if (!opendir(D,"$queuedir")) { logit("cant open $queuedir: $!"); die "cant open $queuedir: $!"; } d691 1 d694 16 d713 86 a798 76 if (!open (F,$config)) { logit("cant open $config: $!\n"); die "can't open $config: $!\n"; } else { logit("reading config file"); %config=(); @@configkeys=(); while () { 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; } a799 6 if (/^QUEUEDIR\s+(\S+)\s*$/) { logit("set queuedir to $1") if ($options{"d"}); $queuedir=$1; next; } d801 6 a806 6 if (/^INTERVAL\s+(\d+)\s*$/) { logit("set interval to $1") if ($options{"d"}); $interval=$1; next; } d808 13 d822 4 a825 1 if (/^TEMPDIR\s+(\S+)\s*$/) d827 3 a829 3 logit("set tempdir to $1") if ($options{"d"}); $tempdir=$1; next; d831 1 a831 2 if (/^LOGFILE\s+(\S+)\s*$/) d833 1 a833 28 # close old logfile if there is one close $lf if ($logfile && $logfile ne $1); $logfile=$1; # we append to the logfile if (!open($lf,">>$logfile")) { logit("cant open logfile $logfile: $!"); die("cant open logfile $logfile: $!\n"); } $lf->autoflush(1); logit("set logfile to $1") if ($options{"d"}); next; } if (/^(\S+)\s+(\S+)\s*$/) { my ($key,$action)=(lc($1),lc($2)); if ($action=~/^(none|std(sign)?|ng(sign)?|fallback)(-force)?$/) { $config{$key}=$action; push @@configkeys, $key; logit("got conf $action for $key") if ($options{"d"}); } else { logit("ignoring bad action \"$action\" for $key"); } d836 1 d843 2 a844 5 if (!mkdir($queuedir,0700)) { logit("cant mkdir $queuedir: $!"); die "cant mkdir $queuedir: $!\n"; } d849 1 a849 2 logit("$queuedir is not owned by you - refusing to run"); die "$queuedir is not owned by you - refusing to run"; d853 1 a853 2 logit("$queuedir does not have mode 0700 - refusing to run"); die "$queuedir does not have mode 0700 - refusing to run"; d862 1 a862 2 logit("cant mkdir $tempdir: $!"); die "cant mkdir $tempdir: $!\n"; d867 1 a867 2 logit("$tempdir is not owned by you - refusing to run"); die "$tempdir is not owned by you - refusing to run"; d871 1 a871 2 logit("$tempdir does not have mode 0700 - refusing to run"); die "$tempdir does not have mode 0700 - refusing to run"; d880 2 a881 5 if (! -x $GPG) { logit("bad ng executable '$GPG' -- exiting"); die "bad ng executable '$GPG' -- exiting\n"; } d883 2 a884 5 if ($use_pgp && ! -x $PGP) { logit("bad std executable '$PGP' -- exiting"); die "bad std executable '$PGP' -- exiting\n"; } d888 3 a890 9 if (! -x $1) { logit("bad MTA '$mtaopt' -- exiting"); die "bad MTA '$mtaopt' -- exiting\n"; } else { $mta=$mtaopt; } d897 2 a898 5 if (! -x $x) { logit("bad agent executable '$x' -- exiting"); die "bad agent executable '$x' -- exiting\n"; } a900 6 logit("reading std keyring."); %stdkeys=&std_listkeys; logit("reading ng keyring."); %ngkeys=&ng_listkeys; return; d1550 1 d1554 5 d1561 1 a1561 1 $res = 0xffff & system "$client put $id 2>$tempdir/subprocess"; d1567 1 a1567 1 return "secret-client returned error code $res\n" d1660 1 a1660 2 # set the std std keys if no overrides given and keys were returned # by the lookup a1668 1 d1700 1 a1700 1 || die "cant exec $agent: $!\n"; d1704 2 a1705 5 elsif ($secret_on_demand) { return "secret_on_demand without agent-support is not possible."; } if (!$secret_on_demand || !$ENV{"DISPLAY"}) d1715 2 a1716 1 return $res if ($res); d1731 2 a1732 1 return $res if ($res); a1751 13 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); } d1759 15 d1779 1 a1779 1 # requests the passphrase from secret agent and runs it d1803 1 a1803 1 # otherwise nuke the key in order to make d1911 5 a1915 1 d1917 3 @ 1.22 log @some more sanity checks @ text @d23 1 a23 1 # $Id: kuvert,v 1.21 2002/09/19 09:13:13 az Exp az $ d549 2 a550 2 "It has been signed conforming to RFC2015.\n", "You'll need PGP or GPG to check the signature.\n"]); d707 1 a707 1 my (@@tmp,$lastkey); d768 6 d897 1 d903 13 d917 1 a917 1 if ($use_agent) @ 1.21 log @fixed missing config startup, fixed $TMPDIR handling @ text @d23 1 a23 1 # $Id: kuvert,v 1.20 2002/04/27 15:49:50 az Exp az $ d99 1 a99 1 # cat d169 2 a170 2 logit("no configuration file, can't start"); die("no configuration file, can't start"); d884 24 @ 1.20 log @fixed stupid typo @ text @d23 1 a23 1 # $Id: kuvert,v 1.19 2002/04/26 02:11:33 az Exp az $ d63 1 a63 1 my $tempdir="/tmp/kuvert.$<.$$"; d167 7 d713 1 @ 1.19 log @fixed ng-sign typo @ text @d23 1 a23 1 # $Id: kuvert,v 1.18 2002/04/25 14:31:58 az Exp az $ d1871 1 a1871 1 || ($ngkeys{$addr} && ($actions{$addr}="std")) @ 1.18 log @fixed -force handling added immutability of none better logging in debug mode @ text @d23 1 a23 1 # $Id: kuvert,v 1.17 2002/03/05 13:18:49 az Exp az $ d1842 1 a1842 1 $force="std-sign" if (grep(!exists $stdkeys{$_}, @@affected)); d1846 1 a1846 1 $force="ng-sign" if (grep(!exists $ngkeys{$_}, @@affected)); d1850 2 a1851 2 # fallback-logic: ng-crypt or std-crypt, otherwise ng-sign # -force: ng- or std-crypt for all, otherwise ng-sign @ 1.17 log @fixed send_bounce finally @ text @d23 1 a23 1 # $Id: kuvert,v 1.16 2002/03/05 13:02:53 az Exp az $ d314 1 a314 1 my $custom_conf=$in_ent->head->get($conf_header); d319 4 a322 1 d352 3 a354 2 # save all recipients, necessary for override-handling map { push @@recip_all, lc($_->address); } Mail::Address->parse($in_ent->head->get("To"), a356 128 # check if there is one with an override in there # but only if there's no custom header already if (!$custom_conf) { foreach (@@recip_all) { if (grep($_,@@configkeys)) { if ($config{$_} =~ /^((std|ng)(sign)?|none|fallback)-force$/) { $custom_conf=$config{$_}; logit("found override $custom_conf for $_"); last; # more than one override -> undefined... } } } } # handle -force options: $custom_conf =~ s/^(none|stdsign|ngsign)-force$/$1/; # fallback to signing if not all recipients have keys of any kind if ($custom_conf eq "fallback-force") { $custom_conf="fallback"; $custom_conf="ngsign" if (grep(!exists $ngkeys{$_} && !exists $stdkeys{$_}, @@recip_all)); } elsif ($custom_conf eq "ng-force") { $custom_conf="ng"; $custom_conf="ngsign" if (grep(!exists $ngkeys{$_}, @@recip_all)); } elsif ($custom_conf eq "std-force") { $custom_conf="std"; $custom_conf="stdsign" if (grep(!exists $stdkeys{$_}, @@recip_all)); } foreach my $tmp (@@recip_all) { my $key=""; my $value=""; # if there is a custom configuration header, # set its content for all the recipients if ($custom_conf) { $value=lc($custom_conf); logit("found custom conf header, set $tmp -> $value") if ($options{"d"}); } else { # traverse the config an find first match foreach (@@configkeys) { if ($tmp =~ /$_/i) { $key=$_; logit("addr $tmp matches special case $_ -> $config{$key}") if ($options{"d"}); last; } } } # if we've got no config for this address, # we use the default configuration, if a/v # if there is no default config, we do not sign/crypt at all. # if value is set, dont set the key!! $key="default" if (!$key && !$value); # try ng enc, then std enc, else ng sign if (lc($config{$key}) eq "fallback" || ( $custom_conf && $value eq "fallback" )) { if ($ngkeys{$tmp}) { push @@recip_crypt_ng,$tmp; } elsif ($stdkeys{$tmp}) { push @@recip_crypt_std,$tmp; } else { push @@recip_sign_ng,$tmp; } } elsif (lc($config{$key}) eq "ngsign" || ( $custom_conf && $value eq "ngsign" )) # ng, but signonly { push @@recip_sign_ng,$tmp; } elsif (lc($config{$key}) eq "ng" || ( $custom_conf && $value eq "ng" )) # ng-keys, but encr if possible { my $ref=\@@recip_sign_ng; $ref=\@@recip_crypt_ng if ($ngkeys{$tmp}); push @@$ref,$tmp; } elsif (lc($config{$key}) eq "stdsign" || ( $custom_conf && $value eq "stdsign" )) # std, but signonly { push @@recip_sign_std,$tmp; } elsif (lc($config{$key}) eq "std" || ( $custom_conf && $value eq "std")) # consider only std-keys { my $ref=\@@recip_sign_std; $ref=\@@recip_crypt_std if ($stdkeys{$tmp}); push @@$ref,$tmp; } else # everything else means no sign/crypt at all { push @@recip_none,$tmp; } } d360 1 a360 2 if (!@@recip_crypt_ng && !@@recip_crypt_std && !@@recip_sign_ng && !@@recip_sign_std && !@@recip_none) d365 10 d814 11 a824 3 $config{lc($1)}=$2; push @@configkeys, lc($1); logit("got conf $2 for $1") if ($options{"d"}); d1785 102 a1886 1 @ 1.16 log @changed address format for bounce @ text @d23 1 a23 1 # $Id: kuvert,v 1.15 2002/02/16 12:02:54 az Exp az $ d1025 1 a1025 1 print F "From: $name ($progname)\nTo: $name\nSubject: Mail Send Failure\n\n"; @ 1.15 log @fixed version generation @ text @d23 1 a23 1 # $Id: kuvert,v 1.14 2002/02/05 23:44:47 az Exp az $ d1025 1 a1025 1 print F "From: $progname <$name>\nTo: <$name>\nSubject: Mail Send Failure\n\n"; @ 1.14 log @fixed version @ text @d23 1 a23 1 # $Id: kuvert,v 1.13 2002/01/30 14:23:21 az Exp az $ d44 2 a45 2 # manually updated...not perfect my $version="1.0.7"; @ 1.13 log @added version and version output at start @ text @d23 1 a23 1 # $Id: kuvert,v 1.12 2002/01/30 13:36:38 az Exp az $ d45 1 a45 1 my $version="1.0.5"; @ 1.12 log @added interval option @ text @d23 1 a23 1 # $Id: kuvert,v 1.11 2002/01/27 12:32:31 az Exp az $ d37 1 a37 1 if (!getopts("dkrn",\%options) || @@ARGV) d39 2 a40 2 print "usage: $0 [-n] [-d] | [-k] | [-r] \n-k: kill running $0\n" ."-d: debug mode\n-r: reload keyrings and configfile\n-n don't fork\n"; d44 9 d166 2 @ 1.11 log @fixed subtle bug with handling of disabled std pgp @ text @d23 1 a23 1 # $Id: kuvert,v 1.10 2002/01/02 06:59:22 az Exp az $ d883 8 @ 1.10 log @fixed output format for revoked or invalid stuff @ text @d23 1 a23 1 # $Id: kuvert,v 1.9 2002/01/02 06:42:48 az Exp az $ d821 1 a821 1 if (/^NGKEY\s+(\S.+)$/) d828 1 a828 1 if (/^STDKEY\s+(\S.+)$/) @ 1.9 log @fixed usage message @ text @d23 1 a23 1 # $Id: kuvert,v 1.8 2002/01/02 06:39:34 az Exp az $ a1410 9 # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring DSA ". ($info[0] eq "pub"? "key 0x$info[4]":"uid 0x$lastkey")." reason: " .$badcauses{$info[1]}); next; } d1436 8 d1472 8 a1527 8 # ignore expired, revoked and other bad keys if (defined $badcauses{$info[1]}) { &logit("ignoring RSA key 0x$info[4], reason: " .$badcauses{$info[1]}); next; } d1547 8 d1586 9 @ 1.8 log @fixed handling of revoked keys added -force actions @ text @d23 1 a23 1 # $Id: kuvert,v 1.7 2001/12/12 13:31:02 az Exp az $ d40 1 a40 1 ."-d: debug mode\n-r: reload keyrings and configfile\n-n don't fork"; @ 1.7 log @fixed handling revoked/disabled keys @ text @d23 1 a23 1 # $Id: kuvert,v 1.6 2001/11/25 11:39:53 az Exp az $ d337 4 a340 1 @@recip_crypt_std,@@recip_crypt_ng); d342 42 a383 2 foreach (Mail::Address->parse($in_ent->head->get("To"), $in_ent->head->get("Cc"))) a384 1 my $tmp=lc($_->address); d476 1 a476 1 return "no recipients found! the mail header seems to be garbled."; d1414 2 a1415 1 &logit("ignoring DSA key 0x$info[4], reason: " a1416 1 undef $lastkey; # uids have no expiry, still BSTS... a1525 1 undef $lastkey; # uids have no expiry, still BSTS... @ 1.6 log @added option -n fixed debug mode @ text @d23 1 a23 1 # $Id: kuvert,v 1.5 2001/11/11 11:41:05 az Exp az $ a33 1 use Time::Local; d176 1 a176 1 handle_hup(); d1342 2 d1369 2 a1370 2 # ignore expired keys if ($info[6] && $info[6]=~/^(\d+)-(\d+)-(\d+)$/) d1372 4 a1375 7 # yyyy-mm-dd if (timegm(0,0,0,$3,$2-1,$1-1900)<$now) { &logit("ignoring expired DSA key 0x$info[4]"); undef $lastkey; # uids have no expiry, still BSTS... next; } d1452 2 d1479 2 a1480 2 # ignore expired keys if ($info[6] && $info[6]=~/^(\d+)-(\d+)-(\d+)$/) d1482 4 a1485 7 # yyyy-mm-dd if (timegm(0,0,0,$3,$2-1,$1-1900)<$now) { &logit("ignoring expired RSA key 0x$info[4]"); undef $lastkey; # uids have no expiry, still BSTS... next; } @ 1.5 log @added logging to file @ text @d23 1 a23 1 # $Id: kuvert,v 1.4 2001/11/11 10:28:53 az Exp az $ d38 1 a38 1 if (!getopts("dkr",\%options) || @@ARGV) d40 2 a41 2 print "usage: $0 [-d] | [-k] | [-r] \n-k: kill running $0\n" ."-d: debug mode\n-r: reload keyrings and configfile\n"; d142 1 a142 1 my $sig=($options{"r"}?'HUP':'TERM'); d193 1 a193 1 if (!$options{"d"}) d208 2 a209 2 # install the hup-handler $SIG{'HUP'}=\&handle_hup; d211 1 d271 1 d761 1 a761 1 sub handle_hup @ 1.4 log @fixed tempdir, queuedir generation sendmail errormode changed to -oem fixed handling for no gpg or no pgp @ text @d23 1 a23 1 # $Id: kuvert,v 1.3 2001/11/10 04:55:38 az Exp az $ d35 1 d132 3 d673 1 a673 1 # log the msg(s) to syslog d678 14 a691 4 setlogsock('unix'); openlog($progname,"pid,cons","mail"); syslog("notice","$msg"); closelog; d754 1 d845 17 @ 1.3 log @generate an error message if there is no recipient to be found @ text @d23 1 a23 1 # $Id: kuvert,v 1.2 2001/11/06 13:00:27 az Exp az $ d51 1 a51 1 my $mta="/usr/lib/sendmail -om -oi -oee"; d73 1 a73 1 my $progname="kuvert V1.0.0"; d429 1 a429 1 return "no recipients found! header seems to be garbled."; d503 4 d520 4 d539 4 d559 4 d762 1 d769 1 a822 22 # generate queuedir if not existing if (!-d $queuedir) { unlink "$queuedir"; if (!mkdir($queuedir,0700)) { logit("cant mkdir $queuedir: $!"); die "cant mkdir $queuedir: $!\n"; } } # check queuedir owner & perm elsif ((stat($queuedir))[4] != $<) { logit("$queuedir is not owned by you - refusing to run"); die "$queuedir is not owned by you - refusing to run"; } elsif ((stat($queuedir))[2]&0777 != 0700) { logit("$queuedir does not have mode 0700 - refusing to run"); die "$queuedir does not have mode 0700 - refusing to run"; } a829 21 # gen tempdir for storing mime-stuff if (!-d $tempdir) { unlink "$tempdir"; if (!mkdir($tempdir,0700)) { logit("cant mkdir $tempdir: $!"); die "cant mkdir $tempdir: $!\n"; } } elsif ((stat($tempdir))[4] != $<) { logit("$tempdir is not owned by you - refusing to run"); die "$tempdir is not owned by you - refusing to run"; } elsif ((stat($tempdir))[2]&0777 != 0700) { logit("$tempdir does not have mode 0700 - refusing to run"); die "$tempdir does not have mode 0700 - refusing to run"; } d841 43 d987 2 d1312 3 a1314 1 @@tmp=`$GPG -q --batch --list-keys --with-colons --no-expensive-trust-checks`; d1423 3 a1425 1 @@tmp=`$GPG -q --batch --list-keys --with-colons --no-expensive-trust-checks`; d1592 1 a1592 1 @@tmp=`$GPG -q --batch --list-secret-keys --with-colons`; d1609 1 a1609 1 @@tmp=`$GPG -q --batch --list-secret-keys --with-colons`; d1637 2 a1638 2 $std_defkey=$stdkey if (!$std_defkey && $stdkey); $ng_defkey=$ngkey if (!$ng_defkey && $ngkey); d1641 1 a1641 1 if (!$std_defkey); d1643 1 a1643 1 if (!$ng_defkey); a1674 1 # but must not let quintuple-agent fork... d1676 1 a1676 1 exec "$agent","--nofork" d1687 14 a1700 8 # get the std passphrase and verify it do { $res=&askput_secret($std_defkey); return $res if ($res); $res=std_sign(undef,undef); print "wrong passphrase, try again.\n" if ($res); a1701 1 while ($res); d1704 2 a1705 1 do d1707 9 a1715 5 $res=&askput_secret($ng_defkey); return $res if ($res); $res=ng_sign(undef,undef); print "wrong passphrase, try again.\n" if ($res); a1716 1 while ($res); d1730 12 a1741 6 $res = 0xffff & system "$client delete $std_defkey"; &logit("problem deleting secret for $std_defkey: $res") if ($res); $res = 0xffff & system "$client delete $ng_defkey"; &logit("problem deleting secret for $ng_defkey: $res") if ($res); @ 1.2 log @added --no-expensive-trust-checks for speeding up the keyring checks @ text @d23 1 a23 1 # $Id: kuvert,v 1.1 2001/11/06 12:53:15 az Exp az $ d422 11 d447 2 a448 1 # shortcut if no other recipients are given @ 1.1 log @Initial revision @ text @d23 1 a23 1 # $Id: guard,v 2.10 2001/09/21 00:01:16 az Exp $ d1280 1 a1280 1 @@tmp=`$GPG -q --batch --list-keys --with-colons`; d1389 1 a1389 1 @@tmp=`$GPG -q --batch --list-keys --with-colons`; @ kuvert/RCS/kuvert_mta_wrapper.man,v0000444000000000000000000000457007405655246014614 0ustar head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2001.12.12.13.24.54; author az; state Exp; branches; next 1.2; 1.2 date 2001.11.06.13.16.15; author az; state Exp; branches; next 1.1; 1.1 date 2001.11.06.13.13.31; author az; state Exp; branches; next ; desc @@ 1.3 log @improved manpage, kept mdoc format @ text @.Dd October 25, 2001 .Dt KUVERT_MTA_WRAPPER 1 .Os Unix .Sh NAME kuvert-mta-wrapper \- wrapper around your MTA for mail submission to .Xr kuvert 1 .Sh SYNOPSIS .Nm kuvert-mta-wrapper .Op Fl options .Op Ar args .Sh DESCRIPTION .Nm kuvert_mta_wrapper submits an email either directly to your MTA or enqueues it for .Xr kuvert 1 for further processing. .Nm kuvert_mta_wrapper should be called by your MUA instead of your usual MTA in order to enable kuvert to intercept and process the outgoing mails. Please see your MUA's documentation about how to override the MTA to be used. .Pp The decision whether queueing or calling the MTA directly is based on the following factors: .Bl -enum .It If there are options given other than .Fl "bm", .Fl "f", .Fl "i", .Fl "t", .Fl "v", .Fl "oi", .Fl "od", .Fl "oe", the standard MTA .Pa /usr/lib/sendmail is executed with the options and arguments given. The result code in this case is the one the MTA returns. .It If there is a configuration file .Pa ~/.kuvert (See .Xr kuvert "1" for possible configuration directives) the mail is queued for .Xr kuvert "1". The options and arguments are ignored. If there are problems, an error message is sent to syslog and -1 is returned. .El .Sh FILES .Bl -tag .It Pa ~/.kuvert configuration file for .Xr kuvert "1" and .Xr kuvert_mta_wrapper "1". .It Pa ~/.kuvert_queue the default queue directory for .Xr kuvert "1" if the configuration file does not specify an alternative. .El .Sh SEE ALSO .Xr kuvert 1 .Sh AUTHORS .An Alexander Zangerl .Sh BUGS The list of allowed options and the MTA for fallback are set at compile time. .Nm kuvert_mta_wrapper does log only to syslog at the moment. @ 1.2 log @fixed typo @ text @d12 2 a13 1 kuvert_mta_wrapper submits an email either directly to your MTA or d16 3 a18 1 for further processing. kuvert_mta_wrapper should be called by your MUA d28 8 a35 8 .Op Fl "bm", .Op Fl "f", .Op Fl "i", .Op Fl "t", .Op Fl "v", .Op Fl "oi", .Op Fl "od", .Op Fl "oe", d64 2 @ 1.1 log @Initial revision @ text @d31 2 a32 2 .Op Fl "d", .Op Fl "e", @ kuvert/RCS/README,v0000444000000000000000000001542712244634541011051 0ustar head 1.5; access; symbols; locks; strict; comment @# @; 1.5 date 2013.11.25.11.49.53; author az; state Exp; branches; next 1.4; 1.4 date 2010.09.16.05.14.25; author az; state Exp; branches; next 1.3; 1.3 date 2008.06.29.11.25.44; author az; state Exp; branches; next 1.2; 1.2 date 2002.09.19.14.36.35; author az; state Exp; branches; next 1.1; 1.1 date 2002.09.19.09.57.04; author az; state Exp; branches; next ; desc @@ 1.5 log @updated text a bit @ text @this is kuvert, a wrapper around sendmail or other MTAs that does gpg signing/signing+encrypting transparently, based on the content of your public keyring(s) and your preferences. how it works: ------------- you need to configure your MUA to submit mails to kuvert instead of directly. you configure kuvert either to present an SMTP server to your MUA, or you make your MUA to use kuvert_submit instead of executing /usr/sbin/sendmail. kuvert_submit will spool the mail in kuvert's queue iff there is a suitable configuration file. kuvert is the tool that takes care of mangling the email. it reads the queue periodically and handles emails in the queue: signing or encrypting the mail, then handing it over to /usr/lib/sendmail or an external SMTP server for transport. (why a queue? because i thought it might be useful to make sure that none of your emails leaves your system without kuvert handing it. you might be very paranoid, and kill kuvert whenever you leave your box (and remove the keyrings as well).) installation: ------------- on debian systems you simply install the kuvert package, construct a suitable .kuvert configuration file and off you go. an example config file is provided at /usr/share/doc/kuvert/examples/dot-kuvert. on other systems you need to do the following: you need perl perl 5.004+, gpg and a raft of perl modules: MIME::Parser, Mail::Address, Net::SMTPS, Sys::Hostname, Net::Server::Mail, Authen::SASL, IO::Socket::INET, Filehandle, File::Slurp, File::Temp, Fcntl and Time::HiRes. some of those are part of a standard perl intall, others you'll have to get from your nearest CPAN archive and install. optional: get linux-kernel keyutils package, the gpg-agent or some other passphrase cache of your choice. run make, make install DESTDIR=/ as root -> kuvert, kuvert_submit, the manpages and one helper module will be installed in /usr/bin, /usr/share/man/man1 and /usr/share/perl5/Net/Server/Mail/ESMTP/, respectively. configuration: -------------- read the manpages for kuvert(1) and kuvert_submit(1) and consult the example config file "dot-kuvert". you will need to create your own config file as ~/.kuvert. sorry, no autoconfig here: this step is too crucial for a mere robot to perform. then start kuvert and inject a testmail, look at the logs to check if everything works correctly. (historical note: kuvert came into existence in 1996 as pgpmail and was used only privately until 99, when it was extended and renamed to guard. some of my friends started using this software, and in 2001 it was finally re-christened kuvert, extended even further and debianized. in 2008 it received a major overhaul to also provide inbound smtp as submission mechanism, outbound smtp transport and better controllability via email addresses. until 2008 kuvert supported pgp2.x.) please report bugs to me, Alexander Zangerl, . The original source can always be found at: http://www.snafu.priv.at/kuvert/ Copyright (C) 1999-2013 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @ 1.4 log @updated with new dependency @ text @d35 1 a35 1 MIME::Parser, Mail::Address, Net::SMTP, Sys::Hostname, Net::Server::Mail, d72 1 a72 1 Copyright (C) 1999-2008 Alexander Zangerl @ 1.3 log @updated for version 2 @ text @d36 2 a37 1 IO::Socket::INET, Filehandle, File::Slurp, File::Temp, Fcntl and Time::HiRes. d40 2 a41 2 optional: get robert bihlmeyer's quintuple-agent, the linux-kernel keyutils package, the gpg-agent or some other passphrase cache of your choice. @ 1.2 log @more info @ text @d2 1 a2 1 does pgp/gpg signing/signing+encrypting transparently, based d8 5 a12 5 you have to get your MUA to use kuvert_mta_wrapper instead of executing /usr/lib/sendmail (or similar). kuvert_mta_wrapper will spool the mail in kuvert's queue, iff there is a suitable configuration file and the parameters passed to kuvert_mta_wrapper are consistent with sending mail via sendmail. d16 2 a17 1 the mail, then handing it over to /usr/lib/sendmail for transport. a23 11 how to use kuvert in a nutshell: -------------------------------- you'll need a MTA which provides /usr/lib/sendmail with "common" options (see the manpage for which are necessary). also you'll need a MUA which can be instructed not to run /usr/lib/sendmail. in a pinch you could rename /usr/lib/sendmail to something else, and install kuvert_mta_wrapper as /usr/lib/sendmail. if you choose so, make double sure that your ~/.kuvert lists the name of the real sendmail binary as MTA. d27 4 a30 1 on debian systems you may install just the kuvert package. d34 7 a40 4 you need perl perl 5.004+, MIME-tools-4.124.tar.gz or later, MIME-Base64-2.11.tar.gz or later, and MailTools-1.13.tar.gz or later from your nearest CPAN archive and install them. get gpg and/or pgp2.x. optional: get robert bihlmeyer's quintuple-agent, compile and install it. d43 3 a45 2 -> kuvert, kuvert_mta_wrapper and the two manpages will be installed in /usr/bin and /usr/share/man/man1. d50 4 a53 3 read the manpages for kuvert(1) and kuvert_mta_wrapper(1), instruct your MUA to use kuvert_mta_wrapper, make the config file ~/.kuvert. d55 1 a55 1 finally start kuvert and inject a testmail, look at the logs to check d62 3 a64 1 and debianized.) d71 1 a71 1 Copyright (C) 1999-2002 Alexander Zangerl d74 2 a75 3 it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. @ 1.1 log @Initial revision @ text @d5 18 d26 12 a37 2 on debian systems you may install just the kuvert package and the rest of the installation will be taken care of automatically. d50 3 d54 5 a58 1 configure kuvert and go on from there. d71 1 a71 1 Copyright (C) 1999-2001 Alexander Zangerl @ kuvert/RCS/kuvert_submit.pod,v0000444000000000000000000000545511157374257013607 0ustar head 1.4; access; symbols; locks; strict; comment @# @; 1.4 date 2009.03.16.06.58.55; author az; state Exp; branches; next 1.3; 1.3 date 2008.06.07.03.02.35; author az; state Exp; branches; next 1.2; 1.2 date 2008.06.07.02.53.20; author az; state Exp; branches; next 1.1; 1.1 date 2008.06.07.02.52.21; author az; state Exp; branches; next ; desc @@ 1.4 log @added -bv detection @ text @# -*- mode: perl;-*- =pod =head1 NAME kuvert_submit - MTA wrapper for mail submission to kuvert(1) =head1 SYNOPSIS kuvert_submit [sendmail-options] [recipients...] =head1 DESCRIPTION Kuvert_submit submits an email either directly to L or enqueues it for L for further processing. kuvert_submit should be called by your MUA instead of your usual MTA to enable kuvert to intercept and process the outgoing mails. Please see your MUA's documentation about how to override the MTA to be used. Kuvert_submit transparently invokes C directly if it cannot find a ~/.kuvert configuration file, or if the -bv option is given. Otherwise, it enqueues the email in the queue directory specified in the configuration file. If that fails or if the configuration file is invalid, kuvert_submit prints an error message to STDERR and terminates with exit code 1. On successful submission, kuvert_submit terminates with exit code 0. Kuvert_submit also logs messages to syslog with the facility "mail". =head1 OPTIONS If it runs the MTA directly then kuvert_submit passes all options through to /usr/sbin/sendmail. Otherwise, it ignores all options except -f and -t (and -bv which triggers a direct sendmail pass-through). =over =item -f Sets the envelope sender. Kuvert_submit passes this on to kuvert. =item -t Tells an MTA to use the recipients in the mail instead of any commandline arguments. If -t is not given then kuvert_submit passes the recipients from the commandline on to kuvert. =back =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. If not present, kuvert_submit calls /usr/sbin/sendmail directly. =item ~/.kuvert_queue The default queue directory. =back =head1 SEE ALSO L =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2008 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut @ 1.3 log @reworded title line @ text @d21 2 a22 1 if it cannot find a ~/.kuvert configuration file. Otherwise, it enqueues d34 1 a34 1 -f and -t. @ 1.2 log @added modeline @ text @d6 1 a6 1 kuvert_submit - wrapper around your MTA for mail submission to kuvert(1) @ 1.1 log @Initial revision @ text @d1 1 @ kuvert/RCS/kuvert_submit.c,v0000444000000000000000000005145111157374151013235 0ustar head 2.1; access; symbols; locks; strict; comment @ * @; 2.1 date 2009.03.16.06.57.45; author az; state Exp; branches; next 2.0; 2.0 date 2008.06.01.05.15.35; author az; state Exp; branches; next 1.9; 1.9 date 2007.08.12.03.18.39; author az; state Exp; branches; next 1.8; 1.8 date 2007.06.23.03.14.46; author az; state Exp; branches; next 1.7; 1.7 date 2003.05.28.02.29.21; author az; state Exp; branches; next 1.6; 1.6 date 2003.03.28.10.55.39; author az; state Exp; branches; next 1.5; 1.5 date 2002.11.14.14.24.49; author az; state Exp; branches; next 1.4; 1.4 date 2002.11.14.13.20.49; author az; state Exp; branches; next 1.3; 1.3 date 2001.11.06.13.22.28; author az; state Exp; branches; next 1.2; 1.2 date 2001.11.06.13.14.28; author az; state Exp; branches; next 1.1; 1.1 date 2001.11.06.13.00.44; author az; state Exp; branches; next ; desc @@ 2.1 log @added -bv detection @ text @/* * $Id: kuvert_submit.c,v 2.0 2008/06/01 05:15:35 az Exp az $ * * this file is part of kuvert, a wrapper around your mta that * does pgp/gpg signing/signing+encrypting transparently, based * on the content of your public keyring(s) and your preferences. * * copyright (c) 1999-2008 Alexander Zangerl * * 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. * */ #include #include #include #include #include #include #include #include #include #include #define CONFFILE "/.kuvert" #define DEFAULT_QUEUEDIR "/.kuvert_queue" #define BUFLEN 65536 #define FALLBACKMTA "/usr/sbin/sendmail" #define BAILOUT(a,...) {fprintf(stderr,"%s: ",argv[0]); fprintf(stderr, a "\n",##__VA_ARGS__);syslog(LOG_ERR,a,##__VA_ARGS__); exit(1);} int main(int argc,char **argv) { struct passwd *pwentry; /* fixme sizes */ char filen[256],buffer[BUFLEN],dirn[256]; int res,c,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; int direct=1,norecips=0,testmode=0,i; /* determine whether to queue stuff or to call sendmail directly: if there is no proper config file for kuvert in $HOME or if given -bv go direct, otherwise we enqueue. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); for(i=1;ipw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no "CONFFILE" config file, running sendmail"); } else { direct=0; /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) { p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncasecmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } } fclose(cf); } } /* direct to sendmail requested? */ if (direct) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); cpw_dir,DEFAULT_QUEUEDIR) ==-1) BAILOUT("overlong dirname, suspicous.",NULL); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* scan the arguments for the LSB-mandated options: we ignore any options but -f, -t. /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:t"))!=-1) { if (c=='?') continue; /* we simply ignore uninteresting options */ else if (c=='f') { /* pass the intended envelope sender */ fprintf(out,"X-Kuvert-From: %s\n",optarg); } else if (c=='t') { /* no recipients given, so we don't need to pass any recips */ norecips=1; } } if (!norecips && optindpw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no "CONFFILE" config file, running sendmail"); } else { direct=0; /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) { p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncasecmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) d94 6 a99 4 p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) d101 2 a102 3 dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) d104 16 a119 5 if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; a120 4 /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; d122 1 a123 1 fclose(cf); @ 1.9 log @added stdlib to silence compiler complaints about exit @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.8 2007/06/23 03:14:46 az Exp az $ d8 1 a8 1 * copyright (c) 1999-2003 Alexander Zangerl d40 1 a40 1 #define FALLBACKMTA "/usr/lib/sendmail" d49 1 a49 1 int res,c,fallback=0,spaceleft; d54 1 d57 2 a58 5 directly: if there is a proper config file of kuvert in $HOME, and if the flags/args given are "consistent" with a call to sendmail for mail submission, do queue stuff; otherwise exec sendmail. */ a59 5 /* scan the arguments for options: we understand about: no options, non-option-args, --, -bm, -f, -i, -t, -v, -m, -oi, -d*, -e*. everything else means some special instruction to sendmail, so we exec sendmail. */ d61 20 a80 6 /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:itvb:mo:"))!=-1 && !fallback) { switch (c) d82 3 a84 15 case 'v': case 'f': case 'i': case 't': case 'm': /* deprecated option 'metoo', but nmh uses this... */ break; /* these options are ok and supported */ case 'b': /* just -bm is ok, other -b* are bad */ if (!optarg || *optarg != 'm') { fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); } d86 2 a87 40 case 'o': /* -oi, -oe*, -od* are ok */ if (!optarg || (*optarg != 'i' && *optarg != 'e' && *optarg != 'd')) { fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); } break; default: /* well, there's an option we do not know, lets bail out */ fallback=1; syslog(LOG_INFO,"option '-%c' mandates fallback", c=='?'?optopt:c); break; } } if (!fallback) { /* options seem ok, look for config file in $HOME */ pwentry=getpwuid(getuid()); if (!pwentry) BAILOUT("getpwuid failed: %s",strerror(errno)); /* open and scan the conffile for an queue-file definition if there is no conffile, kuvert wont work ever */ if (snprintf(filen,sizeof(filen),"%s%s",pwentry->pw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no .kuvert config file, fallback"); fallback=1; } else { /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) d89 4 a92 6 p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) d94 3 a96 2 p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) d98 5 a102 16 if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; d104 4 a108 1 fclose(cf); d110 1 d113 2 a114 2 /* fallback to sendmail requested? */ if (fallback) d132 1 a132 1 syslog(LOG_INFO,"will exec MTA as '%s'",buffer); d135 1 a135 1 BAILOUT("execv FALLBACKMTA failed: %s",strerror(errno)); d173 1 a173 1 d187 31 a217 1 /* and put the data there */ @ 1.8 log @fixed *stupid* initialisation mistake @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.7 2003/05/28 02:29:21 az Exp az $ d35 1 @ 1.7 log @fixed variadic macro issue that confuses gcc 3.3 @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.6 2003/03/28 10:55:39 az Exp az $ d48 1 a48 1 int res,c,fallback,spaceleft; @ 1.6 log @copy date updated @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.5 2002/11/14 14:24:49 az Exp az $ d41 1 a41 1 #define BAILOUT(a,...) {fprintf(stderr, "argv[0] " ##a "\n",__VA_ARGS__);syslog(LOG_ERR,a,__VA_ARGS__); exit(1);} @ 1.5 log @removed argv-saving: does not make sense, too error-prone (sendmail-options!) @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.4 2002/11/14 13:20:49 az Exp az $ d8 1 a8 1 * copyright (c) 1999-2002 Alexander Zangerl @ 1.4 log @some more error-handling argv saving in queuefile added @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.3 2001/11/06 13:22:28 az Exp az $ a236 12 /* now save argv in the first line for kuvert, zero-delimited. i think that \0 should not be too common in commandlines for sendmail... */ for(c=0;c d38 1 a38 1 #define BUFLEN 10240 d45 28 a72 38 struct passwd *pwentry; char filen[256],buffer[BUFLEN],dirn[256]; int res,c,fallback,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; /* determine whether to queue stuff or to call sendmail directly: if there is a proper config file of kuvert in $HOME, and if the flags/args given are "consistent" with a call to sendmail for mail submission, do queue stuff; otherwise exec sendmail. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); /* scan the arguments for options: we understand about: no options, non-option-args, --, -bm, -f, -i, -t, -v, -m, -oi, -d*, -e*. everything else means some special instruction to sendmail, so we exec sendmail. */ /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:itvb:mo:"))!=-1 && !fallback) { switch (c) { case 'v': case 'f': case 'i': case 't': case 'm': /* deprecated option 'metoo', but nmh uses this... */ break; /* these options are ok and supported */ case 'b': /* just -bm is ok, other -b* are bad */ if (!optarg || *optarg != 'm') d74 32 a105 3 fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); d107 13 a119 5 break; case 'o': /* -oi, -oe*, -od* are ok */ if (!optarg || (*optarg != 'i' && *optarg != 'e' && *optarg != 'd')) d121 3 a123 3 fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); d125 1 a125 31 break; default: /* well, there's an option we do not know, lets bail out */ fallback=1; syslog(LOG_INFO,"option '-%c' mandates fallback", c=='?'?optopt:c); break; } } if (!fallback) { /* options seem ok, look for config file in $HOME */ pwentry=getpwuid(getuid()); if (!pwentry) BAILOUT("getpwuid failed: %s",strerror(errno)); /* open and scan the conffile for an queue-file definition if there is no conffile, kuvert wont work ever */ snprintf(filen,sizeof(filen),"%s%s",pwentry->pw_dir,CONFFILE); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no .kuvert config file, fallback"); fallback=1; } else { /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) d127 8 a134 4 p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; d136 44 a179 22 if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } a180 20 fclose(cf); } } /* fallback to sendmail requested? */ if (fallback) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); c 0; spaceleft-=strlen(argv[c++])) { strncat(buffer,argv[c],spaceleft); --spaceleft && cpw_dir,DEFAULT_QUEUEDIR); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ d224 2 a225 2 /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); d227 32 a258 17 /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* and put the data there */ do { res=fread(buffer,1,BUFLEN,stdin); fwrite(buffer,1,res,out); } while (res==BUFLEN); d260 9 a268 7 fflush(out); if (flock(fileno(out),LOCK_UN)) { BAILOUT("flock (unlock) failed: %s\n",strerror(errno)); } fclose(out); return 0; a269 10 @ 1.2 log @added -oi, -d*, -e* to the list of allowed options @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.1 2001/11/06 13:00:44 az Exp az $ d85 1 a85 1 syslog(LOG_INFO,"option '-%c %s' mandates fallback", d90 1 a90 1 /* -oi, -e*, -d* are ok */ d95 1 a95 1 syslog(LOG_INFO,"option '-%c %s' mandates fallback", d124 1 a124 4 /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) d126 3 a128 6 p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) d130 6 a135 4 p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) d137 2 a138 3 dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) d140 16 a155 5 if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; a156 4 /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; d158 1 a159 1 fclose(cf); @ 1.1 log @Initial revision @ text @d2 1 a2 1 * $Id: uard_mta_wrapper.c,v 2.4 2001/10/07 12:32:28 az Exp az $ d63 1 a63 1 -bm, -f, -i, -t, -v, -m. everything else means some special d69 1 a69 1 while ((c=getopt(argc,argv,"f:itvb:m"))!=-1 && !fallback) d86 11 a96 1 c,optarg); @ kuvert/RCS/dot-kuvert,v0000444000000000000000000000334412244634632012214 0ustar head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2013.11.25.11.50.50; author az; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @# ~/.kuvert: example configuration file for kuvert v2 # options are given without leading whitespace # which key to sign with by default defaultkey 0x1234abcd # logging to syslog, which facility? defaults to no syslog syslog mail # no separate logfile logfile "" # who gets error reports mail-on-error you@@some.domain # where to spool mails and temporary files queuedir /home/az/kuvert_queue tempdir /tmp/kuvert_temp # how often to check the queue, in seconds interval 60 # add an x-mailer header? identify f # add the explanatory mime preamble? preamble f # how to submit outbound mail: # # 1. via smtp # settings: msserver, msport, ssl, # ssl-cert, ssl-key, ssl-ca; # authenticating as msuser, mspass # # msserver some.server.com # msport 587 # ssl starttls # ssl-key mycerts/my.key.pem # ssl-cert mycerts/my.cert.pem # msuser smtp-username # mspass smtp-password # mspass-from-query-secret f # # 2. by using the msp program # msp /usr/sbin/sendmail -om -oi -oem can-detach t # maport 2587 # ma-user yourname # ma-pass somethingSECRET defaultaction fallback-all alwaystrust t use-agent t query-secret /usr/bin/q-agent get %s flush-secret /usr/bin/q-agent delete %s # action specifications for recipients # are given with some leading whitespace # multiple keys for somebody and you want a specific one? somebody@@with.many.keys fallback,0x1234abcd # those don't want gpg-signed stuff @@somewhere.com none # signed but not encrypted (he|they|others)@@there.com signonly # majordomo and similar mailinglist systems get plain mail (majordomo|-request)@@ none @ kuvert/RCS/kuvert.man,v0000444000000000000000000005621710332600332012173 0ustar head 1.24; access; symbols; locks; strict; comment @# @; 1.24 date 2005.11.04.06.34.02; author az; state Exp; branches; next 1.23; 1.23 date 2003.02.21.11.44.42; author az; state Exp; branches; next 1.22; 1.22 date 2003.02.16.13.26.33; author az; state Exp; branches; next 1.21; 1.21 date 2003.02.16.13.25.21; author az; state Exp; branches; next 1.20; 1.20 date 2003.01.21.12.02.29; author az; state Exp; branches; next 1.19; 1.19 date 2003.01.12.14.15.56; author az; state Exp; branches; next 1.18; 1.18 date 2003.01.11.13.57.25; author az; state Exp; branches; next 1.17; 1.17 date 2002.11.16.10.06.14; author az; state Exp; branches; next 1.16; 1.16 date 2002.09.25.12.13.20; author az; state Exp; branches; next 1.15; 1.15 date 2002.09.19.16.04.14; author az; state Exp; branches; next 1.14; 1.14 date 2002.09.19.14.55.15; author az; state Exp; branches; next 1.13; 1.13 date 2002.09.19.14.48.45; author az; state Exp; branches; next 1.12; 1.12 date 2002.09.19.14.32.55; author az; state Exp; branches; next 1.11; 1.11 date 2002.09.19.09.50.55; author az; state Exp; branches; next 1.10; 1.10 date 2002.04.25.14.24.33; author az; state Exp; branches; next 1.9; 1.9 date 2002.04.25.13.59.41; author az; state Exp; branches; next 1.8; 1.8 date 2002.04.25.08.55.04; author az; state Exp; branches; next 1.7; 1.7 date 2002.01.31.10.46.02; author az; state Exp; branches; next 1.6; 1.6 date 2002.01.30.14.24.53; author az; state Exp; branches; next 1.5; 1.5 date 2002.01.30.13.38.42; author az; state Exp; branches; next 1.4; 1.4 date 2002.01.03.10.13.12; author az; state Exp; branches; next 1.3; 1.3 date 2002.01.02.05.38.39; author az; state Exp; branches; next 1.2; 1.2 date 2001.12.12.13.21.32; author az; state Exp; branches; next 1.1; 1.1 date 2001.11.11.11.34.31; author az; state Exp; branches; next ; desc @@ 1.24 log @no more use_agent, client_path, agent_path config added getsecret and delsecret config options @ text @.Dd February 16, 2003 .Dt KUVERT 1 .Os Unix .Sh NAME kuvert \- automatically sign and/or encrypt mail based on the recipients .Sh SYNOPSIS .Nm kuvert .Op Fl d .Op Fl b .Op Fl r | Fl k | Fl n | Fl v .Sh DESCRIPTION .Nm kuvert reads mails from its queue, analyzes the recipients, decides to whom it should encrypt and/or sign the mail using the PGP-MIME framework defined in RFC3156 and sends the mail using your real MTA. kuvert can use both old-style 2.x .Xr pgp 1 and modern .Xr gpg 1 at the same time, or can coerce gpg into producing pgp-2.x-compatible signatures/encrypted data. .Pp The mail submission into the queue is usually done by .Xr kuvert_mta_wrapper "1". .Pp The option .Fl r causes an already running .Nm kuvert process to reload the configuration file and the keyring(s). This is done by sending a SIGUSR1 to the running process. .Pp The option .Fl d activates debugging output to syslog. .Nm kuvert does not fork when in debugging mode and processes just the first mail in the queue, after which it terminates. .Pp If the option .Fl b is given, then .Nm kuvert will send an error mail to the user whenever fatal errors are encountered. .Pp The option .Fl k makes .Nm kuvert kill an already running process. .Pp With the option .Fl n .Nm kuvert does not .Xr fork 2 but keeps running in the foreground. .Pp The option .Fl v makes .Nm kuvert output its version number and exit immediately. .Pp At startup .Nm kuvert reads the keyring(s) and the configuration file, then usually forks and runs the queue once every 60 seconds. Whenever there is a file with a name consisting of digits only in the queue, .Nm kuvert will parse the mime structure into a temporary directory using .Xr MIME::Parser "3pm". .Pp .Nm kuvert then decides whether the mail shall be left as is, clear-text signed or signed and encrypted according to RFC3156. This decision is done independently for every recipient of the mail and works as follows: .Bl -bullet .It If a public key of the recipient is known, sign and encrypt. .It If no public key of the recipient is known, just sign. .El .Pp There are some options governing or overriding this basic setup, see the section .Sx CONFIGURATION for details. .Pp Please note that .Nm kuvert uses the .Ql To: , .Ql Cc: and .Ql Bcc: headers to determine the recipients. Recipients listed in a .Ql Bcc: header are handled separately from all other recipients and do not affect the choice of actions for other recipients (ie. handling of .Ar -force options). Additionally, if there is a .Ql Resend-To: header, .Nm kuvert will do no signing/encryption and send the mail just as it is to the recipients indicated in the .Ql Resend-To: header. .Pp Afterwards the parsed MIME entity is amended with the signature or replaced with the encrypted data and is sent off using the MTA. The temporary directory is cleaned and .Nm kuvert processes either the next queued message or waits for new messages. .Pp If there are problems, kuvert disables further processing of the respective queuefile (it adds a .Ql \&. at the beginning of the filename, thus making the file ignored for further queue-runs) and sends an error message back to the sender. .Sh CONFIGURATION .Nm kuvert needs some configuration directives in its configuration file .Pa ~/.kuvert . This file is read at startup and whenever you have .Nm kuvert reread things using .Fl r. Empty lines and lines starting with .Ql # are ignored, as well as lines containing directives .Nm kuvert can not interpret. .Pp Directives can either be: .Bl -bullet .It a directive name followed by whitespace and then a value, .It or a regular expression matching an email address, followed by whitespace and an action keyword. .El .Pp The list of directives kuvert understands: .Bl -tag .It Ar PGPPATH Pa path defines the old-style compatible pgp executable to be used. Default: .Pa /usr/bin/pgp .It Ar GPGPATH Pa path defines the gnupg-compatible new-style pgp executable to be used. Default: .Pa /usr/bin/gpg .It Ar USEPGP number if number is not 0, kuvert will use the old-style pgp executable to generate old-style encryption/signatures. If it is 0, .Nm kuvert will use gpg in a compatibility mode to produce the old-style stuff. Please note: gpg needs the gnupg-extension gpg-idea for this compatibility mode. You also have to setup gpg to automatically load this extension. Default: 0 .It Ar MTA Pa path-and-args defines the Mail Transfer Agent .Nm kuvert should use. The MTA must read the mail text from stdin, support the flag .Fl t , and also support multiple recipients given in separate arguments. Default: /usr/lib/sendmail -om -oi -oem .It Ar SECRETONDEMAND number If SECRETONDEMAND is 1, .Nm kuvert will ask for the key passphrases on demand and just before signing. If SECRETONDEMAND is 0, then .Nm kuvert will query for passphrases on startup and store them itself (which is not very secure). SECRETONDEMAND is automatically set to 0 if GETSECRET or DELSECRET are not set. Default: 0 .It Ar GETSECRET Pa path-and-args .It Ar DELSECRET Pa path-and-args define what program to run to deal with externally stored passphrases, if SECRETONDEMAND is set; ignored otherwise. The path-and-args must contain "%s" which will be replaced with the key id in question. The program must print the passphrase on its standard output. GETSECRET is executed to retrieve a passphrase, while DELSECRET is used to delete passphrases. Default: none .It Ar ALWAYSTRUST number if 1, add the alwaystrust parameter to gpg's invocation. See .Xr gpg "1" for details about this parameter. Default: 0 .It Ar LOGFILE Pa path sets the file .Nm kuvert logs its actions to. The logs are appended to that file. Default: .Nm kuvert usually logs to syslog. .It Ar QUEUEDIR Pa path sets the directory where kuvert_mta_wrapper and .Nm kuvert put the queue of mails to be processed. Default: .Pa ~/.kuvert_queue .It Ar TEMPDIR Pa path sets the directory .Nm kuvert uses for temporary storage of the parts of the parsed MIME entity. .Em Attention: This directory is cleaned after every mail handled and every file in there is removed! Default: .Pa /tmp/kuvert... .It Ar INTERVAL number sets the queue check interval. the unit of measurement is seconds. Default: 60 seconds .It Ar IDENTIFY number if non-zero, .Nm kuvert adds a .Ql X-mailer header to all mails it processes. Default: 0 .It Ar NGKEY keyid sets the owner's key id for new-style pgp/gpg. To disable new-style pgp/gpg completely, set the keyid to "0". Default: the first private DSA key found is used. .It Ar STDKEY keyid sets the owner's key id for old-style pgp. To disable old-style pgp completely, set the keyid to 0. Default: the first private RSA key found is used. .It Ar DEFAULT action specifies the action to be taken for unspecified recipient addresses. See the next paragraphs for an explanation of the .Ar action argument. .El .Pp All lines not starting with the pound sign .Ql # or a recognized directive are interpreted as a .Xr perl 1 regular expression followed by whitespace and an action keyword. .Pp The regular expressions are applied to the email address of the recipients of the mail, and the action keyword describes how to modify .Nm kuvert Ns \&'s behavious for a recipient. .Pp The regular expression has to be written without the bracketing .Ql / Ns -characters. The regular expressions are evaluated case-insensitively, and in the order given in the configuration file. The first matching regexp ends the evaluation sequence. .Pp The default action is to do not encrypt or sign at all, so you should set a default that is reasonable for you by using the .Ql DEFAULT directive. .Pp The known action keywords are: .Bl -tag .It Ar none Send it as it is, do not sign or encrypt at all. The MIME structure of the mail is not changed in whatever way before sending. This is the default action. .Pp This option is .Em slightly special: An explicitly set action of .Ql none is .Em not affected or overridden by any of the .Ar -force options or by the override header. .It Ar std Use just old-style pgp. If there is an old-style key known, encrypt and sign using this old-style key and the owner's old-style key, otherwise just sign using the owner's old-style key. .It Ar ng Use just new-style pgp, similar to the above. .It Ar stdsign Never encrypt, just sign using the owner's old-style key. .It Ar ngsign Never encrypt, just sign using the owner's new-style key. .It Ar fallback Encrypt with new-style, old-style or sign with new-style (or std-style if no new-style private key is available). If there is a new-style key of the recipient known, encrypt and sign with this key, else if there is an old-style key, encrypt and sign with this key. Otherwise just sign with the owner's new-style key or (as last resort) the old-style key. .It Ar none-force Force no encryption/signing for all recipients of this mail. .It Ar fallback-force Force a fallback-type action for the recipients of this mail: encrypt and sign with new-style or old-style pgp if keys for .Em all affected recipients are available or sign with new-style pgp. Recipients with an action set to .Ql none are .Em not affected by fallback-force. Also note that a mixture of old-style and new-style encryption is possible with fallback-force. .It Ar ngsign-force "," stdsign-force Sign only for all affected recipients, with new-style or old style pgp respectively. Again recipients with action .Ql none are .Em not affected. .It Ar ng-force Encrypt and sign for all recipients of this mail if there is a new-style key available for all of them, otherwise just sign for all of them using new-style pgp. The difference between this action and .Ar fallback-force is that there's no mixing of old-style and new-style pgp possible here. Again recipients with action .Ql none are .Em not affected. .It Ar std-force like .Ar ng-force "," but with old-style pgp. Again recipients with action .Ql none are .Em not affected. .El .Pp Additionally, you can specify an override for a single mail by adding a header to the mail of the form .Ql X-Kuvert: Ar action where action is one of the action keywords just listed above. This override will be applied to all recipients of the given mail and will override all action specifications given in the configuration file, except the explicit .Ql none Ns s. Before final sending an email .Nm kuvert will remove any existing override header from the email. .Pp The various .Ar -force actions are intended for users who want to avoid sending cleartext (signed) and encrypted variants of the same mail to different recipients: You can either turn off encryption or signing completely, or use the maximum amount of privacy that is possible for a given set of recipients by checking for keys for everybody before deciding whether to encrypt or just sign. .Pp The special handling for .Ql none does break this paradigma a bit, but is necessary to make any .Ar -force option a safe choice for your .Ql DEFAULT action: Otherwise .Nm kuvert would send stuff signed or encrypted to recipients you know to be completely unable/unwilling to accept signed or encrypted mail (like mail robots). Therefore these were made unaffected (and disregarded) by the .Ar -force options. .Pp .Sy Please note: the first occurrence of a -force action overrides all possible other occurrences! .Sh FILES .Bl -tag .It Pa ~/.kuvert configuration file for .Nm kuvert and .Xr kuvert_mta_wrapper "1". .It Pa ~/.kuvert_queue the default queue directory for .Nm kuvert if the configuration file does not specify an alternative. .It Pa /tmp/kuvert.pid. holds the pid of a running process. .El .Sh SEE ALSO .Xr kuvert_mta_wrapper "1", .Xr q-agent "1", .BR gpg "1", .BR pgp "1", RFC3156, RFC2015, RFC2440 .Sh AUTHORS .An Alexander Zangerl .Sh BUGS Currently .Nm kuvert needs something sendmail-like in .Pa /usr/lib/sendmail that understands .Fl t, .Fl om, .Fl oi and .Fl "oem". .Pp Multiple -force actions won't work. @ 1.23 log @added x-mailer option @ text @a175 6 .It Ar AGENTPATH Pa path defines the quintuple-agent binary (see .Xr q-agent "1") to be used, if available. Default: none .It Ar CLIENTPATH Pa path defines the quintuple-client binary (see .Xr q-client "1") to be used, if available. Default: none d177 1 a177 2 SECRETONDEMAND is disabled if agent, client or X11-display are not available. Otherwise, if SECRETONDEMAND is 1, d183 11 a193 2 will query for passphrases as early as possible (but still use the agent to store the passphrases if possible). Default: 0 d195 1 a195 1 if 1, add the alwaystrust parameter to gpg's invocation. see a389 1 .Xr q-agent "1", @ 1.22 log @fixed formatting @ text @d219 6 @ 1.21 log @added -b @ text @d8 2 a9 1 .Op Fl d | Fl b d36 4 a44 3 .Nm kuvert does not fork when in debugging mode and processes just the first mail in the queue, after which it terminates. @ 1.20 log @added final fallback to fallback option @ text @d1 1 a1 1 .Dd April 25, 2002 d8 1 a8 1 .Op Fl d d35 5 @ 1.19 log @some fixes, bcc handling mentioned @ text @d277 1 a277 1 Encrypt with new-style, old-style or just sign with new-style. d280 1 a280 1 key. Otherwise just sign with the owner's new-style key. @ 1.18 log @updated rfc2015->3156 @ text @d87 2 a88 1 .Ql From: d90 10 a99 3 .Ql Cc: headers to determine the recipients. Additionally, if there is a d343 2 a344 2 oes break this paradigma a bit, but is necessary to make the d346 1 a346 1 a safe choice for your a381 1 The MTA to be used is set in the program itself. a391 3 .Pp .Ql Bcc: is not interpreted by kuvert at the moment. @ 1.17 log @clarified SECRETONDEMAND option and X11-DISPLAY @ text @d14 1 a14 1 defined in RFC2015 and sends the mail using your real MTA. kuvert can use d70 1 a70 1 or signed and encrypted according to RFC2015. d370 1 a370 1 RFC2015, RFC2440 @ 1.16 log @fixed idea-problem @ text @d168 1 a168 3 SECRETONDEMAND is ignored if agent and client are not available, as .Nm kuvert has to store passphrases itself then. d176 1 a176 1 store the passphrases). Default: 0 @ 1.15 log @clarified secretondemand @ text @d151 2 a152 1 the gnupg-extension gpg-idea for this compatibility mode. @ 1.14 log @finetuning @ text @d167 4 a170 1 if 1 and if agent and client are available, d173 2 a174 2 key passphrases on demand, just when signing. If one of these requirements is not given, d176 2 a177 2 will ask for the passphrases at startup and will store them itself. Default: 0 @ 1.13 log @clarified mta option @ text @d156 1 a156 1 should use. The MTA must support the flag @ 1.12 log @added MTA option @ text @d157 2 a158 1 .Fl t . @ 1.11 log @some clarifications regarding the config file @ text @d153 6 @ 1.10 log @fixed missing none-not-overrideable info @ text @d29 1 a29 1 process to reload the configuration file. d60 1 a60 1 reads the keyring(s) and the config file (if available), d115 1 a115 1 allows for configuration with a configuration file d117 1 a117 1 This file is read at startup or whenever you have d119 2 a120 2 reread the files using .Fl "r". @ 1.9 log @added some info about the regexp. added info about force disregarding none. @ text @d248 1 a248 1 options. @ 1.8 log @manpage improved @ text @d1 1 a1 1 .Dd January 31, 2002 d104 2 a105 2 processes either a next message or waits for new messages. d107 2 a108 1 If there are problems, kuvert puts a dot d110 2 a111 1 at the beginning of the filename of the current message d132 1 a132 5 or a regular expression matching an email address, followed by whitespace and an action keyword. The regular expression may be any regular expression .Xr perl 1 supports, but has to be written without the bracketing .Ql / Ns -characters. d202 1 a202 1 See the next paragraph for an explanation of the d210 7 a216 3 are interpreted as a perl regular expression followed by whitespace and an action keyword. The regular expression is applied to the email address of the recipient of the mail, and the action keyword describes how to d219 9 a227 1 \&'s behavious for this recipient. d235 14 a258 4 .It Ar none Send it as it is, do not sign or encrypt at all. The MIME structure of the mail is not changed in whatever way before sending. This is the default action. d267 10 a276 4 Force a fallback-type action for all recipients of this mail: encrypt and sign with new-style or old-style pgp if keys for .Em all recipients are available or sign with new-style pgp. Note that a mixture of old-style and new-style encryption is possible here. d278 4 a281 1 Sign only for all recipients, with new-style or old style pgp respectively. d289 4 d297 4 d309 3 a311 1 file. Before final sending an email d323 18 a340 1 .Sy Please note: the first occurrence of a -force action overrides all possible other occurrences @ 1.7 log @updated gpg-idea stuff @ text @d107 4 a110 3 If there are problems, kuvert prepends a dot .Ql \&. to the current message to and sends an error message back to the sender. @ 1.6 log @added info about -v @ text @d1 1 a1 1 .Dd December 12, 2001 d152 1 a152 1 the gnupg-extensions rsa and idea for this compatibility mode. @ 1.5 log @added interval option @ text @d9 1 a9 1 .Op Fl r | Fl k | Fl n d51 6 @ 1.4 log @fixed empty lines in back @ text @d187 3 @ 1.3 log @added -force semantics @ text @a314 2 @ 1.2 log @improved manpage, kept mdoc format @ text @d234 20 d266 9 d312 5 a316 1 is not interpreted by kuvert at the moment.@ 1.1 log @Initial revision @ text @d1 1 a1 1 .Dd November 11, 2001 d9 1 a9 2 .Op Fl r .Op Fl k d11 1 a11 1 .Xr kuvert 1 d15 6 a20 2 both old-style 2.x pgp and modern gnupg at the same time, or can coerce gnupg into producing pgp-2.x-compatible signatures/encrypted data. d26 5 a30 2 .Op Fl r causes an already running kuvert process to reload the configuration file. d33 4 a36 3 .Op Fl d activates debugging output to syslog. kuvert does not fork when in debugging mode and processes just the d40 4 a43 2 .Op Fl k makes kuvert kill an already running kuvert process. d45 10 a54 1 At startup kuvert reads the keyring(s) and the config file (if available), d56 4 a59 2 Whenever there is a file with a name consisting only of digits in the queue, kuvert will parse the mime structure into a temporary directory using d62 2 a63 1 kuvert then decides whether the mail shall be left as is, clear-text signed d76 1 a76 1 .Sx Configuration d79 14 a92 4 Please note that kuvert uses the "From:" and "Cc:" headers to determine the recipients. Additionally, if there is a "Resend-To:" header, kuvert will do no signing/encryption and send the mail just as it is to the recipients indicated in the "Resend-To:" header. d96 3 a98 1 The temporary directory is cleaned and kuvert processes either a d101 3 a103 2 If there are problems, kuvert renames the current message to ".number" and sends an error message back to the sender. d105 5 a109 2 kuvert allows for configuration with a configuration file ~/.kuvert. This file is read at startup or whenever you have kuvert d111 1 a111 1 .Op Fl "r". d113 4 a116 2 "#" are ignored, as well as lines containing directives kuvert can not interpret. d125 3 a127 1 supports, but has to be written without the bracketing "/"-characters. d132 1 a132 1 .It Li PGPPATH path d134 3 a136 2 Default: /usr/bin/pgp .It Li GPGPATH path d138 3 a140 2 Default: /usr/bin/gpg .It Li USEPGP number d142 3 a144 1 old-style encryption/signatures. If it is 0, kuvert will use gpg in a d147 2 a148 1 .It Li AGENTPATH path d151 1 a151 1 .It Li CLIENTPATH path d154 4 a157 2 .It Li SECRETONDEMAND number if 1 and if agent and client are available, kuvert will ask for the d159 3 a161 1 is not given, kuvert will ask for the passphrases at startup and will store d163 1 a163 1 .It Li ALWAYSTRUST number d166 17 a182 8 .It Li LOGFILE path sets the file kuvert logs its actions to. The logs are appended to that file. Default: kuvert usually logs to syslog. .It Li QUEUEDIR path sets the directory where kuvert_mta_wrapper and kuvert put the queue of mails to be processed. Default: ~/.kuvert_queue .It Li TEMPDIR path sets the directory kuvert uses for temporary storage of the parts of the d184 4 a187 3 Attention: This directory is cleaned after every mail handled and every file in there is removed! Default: /tmp/kuvert... .It Li NGKEY keyid d189 4 a192 4 completely, set the keyid to "0". Default: the first private RSA key found is used. .It Li STDKEY keyid sets the owner's key id for old-style pgp. To disable old-style pgp completely, set the keyid to "0". Default: the first private DSA key found is used. .It Li DEFAULT action d194 3 a196 1 See next paragraph for an explanation of the action argument. d199 3 a201 1 All lines not starting with the pound sign or a recognized directive d205 3 a207 1 modify kuvert's behavious for this recipient. d209 3 a211 1 set a default that is reasonable for you by using the DEFAULT directive. d215 1 a215 1 .It Li std d219 1 a219 1 .It Li ng d221 1 a221 1 .It Li stdsign d223 1 a223 1 .It Li ngsign d225 1 a225 1 .It Li none d229 1 a229 1 .It Li fallback d238 2 a239 1 "X-Kuvert: action" where action is one of the action keywords just listed d242 3 a244 1 file. Before final sending an email kuvert will remove d249 3 a251 1 configuration file for kuvert and d255 2 a256 1 .Xr kuvert "1" if the configuration file does not specify an alternative. d258 1 a258 1 holds the pid of a running kuvert process. d262 5 a266 1 .Xr q-agent 1 d271 8 a278 5 Currently kuvert needs something .Xr sendmail "1"-like in /usr/lib/sendmail that understands .Op Fl t .Op Fl om .Op Fl oi d280 1 a280 1 .Op Fl "oem". d282 2 a283 1 Bcc: is not interpreted by kuvert at the moment.@ kuvert/RCS/kuvert_mta_wrapper.c,v0000444000000000000000000004011010657476017014251 0ustar head 1.9; access; symbols; locks; strict; comment @ * @; 1.9 date 2007.08.12.03.18.39; author az; state Exp; branches; next 1.8; 1.8 date 2007.06.23.03.14.46; author az; state Exp; branches; next 1.7; 1.7 date 2003.05.28.02.29.21; author az; state Exp; branches; next 1.6; 1.6 date 2003.03.28.10.55.39; author az; state Exp; branches; next 1.5; 1.5 date 2002.11.14.14.24.49; author az; state Exp; branches; next 1.4; 1.4 date 2002.11.14.13.20.49; author az; state Exp; branches; next 1.3; 1.3 date 2001.11.06.13.22.28; author az; state Exp; branches; next 1.2; 1.2 date 2001.11.06.13.14.28; author az; state Exp; branches; next 1.1; 1.1 date 2001.11.06.13.00.44; author az; state Exp; branches; next ; desc @@ 1.9 log @added stdlib to silence compiler complaints about exit @ text @/* * $Id: kuvert_mta_wrapper.c,v 1.8 2007/06/23 03:14:46 az Exp az $ * * this file is part of kuvert, a wrapper around your mta that * does pgp/gpg signing/signing+encrypting transparently, based * on the content of your public keyring(s) and your preferences. * * copyright (c) 1999-2003 Alexander Zangerl * * 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. * */ #include #include #include #include #include #include #include #include #include #include #define CONFFILE "/.kuvert" #define DEFAULT_QUEUEDIR "/.kuvert_queue" #define BUFLEN 65536 #define FALLBACKMTA "/usr/lib/sendmail" #define BAILOUT(a,...) {fprintf(stderr,"%s: ",argv[0]); fprintf(stderr, a "\n",##__VA_ARGS__);syslog(LOG_ERR,a,##__VA_ARGS__); exit(1);} int main(int argc,char **argv) { struct passwd *pwentry; /* fixme sizes */ char filen[256],buffer[BUFLEN],dirn[256]; int res,c,fallback=0,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; /* determine whether to queue stuff or to call sendmail directly: if there is a proper config file of kuvert in $HOME, and if the flags/args given are "consistent" with a call to sendmail for mail submission, do queue stuff; otherwise exec sendmail. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); /* scan the arguments for options: we understand about: no options, non-option-args, --, -bm, -f, -i, -t, -v, -m, -oi, -d*, -e*. everything else means some special instruction to sendmail, so we exec sendmail. */ /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:itvb:mo:"))!=-1 && !fallback) { switch (c) { case 'v': case 'f': case 'i': case 't': case 'm': /* deprecated option 'metoo', but nmh uses this... */ break; /* these options are ok and supported */ case 'b': /* just -bm is ok, other -b* are bad */ if (!optarg || *optarg != 'm') { fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); } break; case 'o': /* -oi, -oe*, -od* are ok */ if (!optarg || (*optarg != 'i' && *optarg != 'e' && *optarg != 'd')) { fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); } break; default: /* well, there's an option we do not know, lets bail out */ fallback=1; syslog(LOG_INFO,"option '-%c' mandates fallback", c=='?'?optopt:c); break; } } if (!fallback) { /* options seem ok, look for config file in $HOME */ pwentry=getpwuid(getuid()); if (!pwentry) BAILOUT("getpwuid failed: %s",strerror(errno)); /* open and scan the conffile for an queue-file definition if there is no conffile, kuvert wont work ever */ if (snprintf(filen,sizeof(filen),"%s%s",pwentry->pw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no .kuvert config file, fallback"); fallback=1; } else { /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) { p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } } fclose(cf); } } /* fallback to sendmail requested? */ if (fallback) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); cpw_dir,DEFAULT_QUEUEDIR) ==-1) BAILOUT("overlong dirname, suspicous.",NULL); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* and put the data there */ do { res=fread(buffer,1,BUFLEN,stdin); if (!res && ferror(stdin)) BAILOUT("fread failure: %s",strerror(errno)); if (fwrite(buffer,1,res,out)!=res && ferror(out)) BAILOUT("fwrite failure: %s",strerror(errno)); } while (res==BUFLEN); if (fflush(out)==EOF) BAILOUT("fflush failed: %s",strerror(errno)); if (flock(fileno(out),LOCK_UN)) { BAILOUT("flock (unlock) failed: %s",strerror(errno)); } if (fclose(out)==EOF) BAILOUT("fclose failed: %s",strerror(errno)); return 0; } @ 1.8 log @fixed *stupid* initialisation mistake @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.7 2003/05/28 02:29:21 az Exp az $ d35 1 @ 1.7 log @fixed variadic macro issue that confuses gcc 3.3 @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.6 2003/03/28 10:55:39 az Exp az $ d48 1 a48 1 int res,c,fallback,spaceleft; @ 1.6 log @copy date updated @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.5 2002/11/14 14:24:49 az Exp az $ d41 1 a41 1 #define BAILOUT(a,...) {fprintf(stderr, "argv[0] " ##a "\n",__VA_ARGS__);syslog(LOG_ERR,a,__VA_ARGS__); exit(1);} @ 1.5 log @removed argv-saving: does not make sense, too error-prone (sendmail-options!) @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.4 2002/11/14 13:20:49 az Exp az $ d8 1 a8 1 * copyright (c) 1999-2002 Alexander Zangerl @ 1.4 log @some more error-handling argv saving in queuefile added @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.3 2001/11/06 13:22:28 az Exp az $ a236 12 /* now save argv in the first line for kuvert, zero-delimited. i think that \0 should not be too common in commandlines for sendmail... */ for(c=0;c d38 1 a38 1 #define BUFLEN 10240 d45 28 a72 38 struct passwd *pwentry; char filen[256],buffer[BUFLEN],dirn[256]; int res,c,fallback,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; /* determine whether to queue stuff or to call sendmail directly: if there is a proper config file of kuvert in $HOME, and if the flags/args given are "consistent" with a call to sendmail for mail submission, do queue stuff; otherwise exec sendmail. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); /* scan the arguments for options: we understand about: no options, non-option-args, --, -bm, -f, -i, -t, -v, -m, -oi, -d*, -e*. everything else means some special instruction to sendmail, so we exec sendmail. */ /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:itvb:mo:"))!=-1 && !fallback) { switch (c) { case 'v': case 'f': case 'i': case 't': case 'm': /* deprecated option 'metoo', but nmh uses this... */ break; /* these options are ok and supported */ case 'b': /* just -bm is ok, other -b* are bad */ if (!optarg || *optarg != 'm') d74 32 a105 3 fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); d107 13 a119 5 break; case 'o': /* -oi, -oe*, -od* are ok */ if (!optarg || (*optarg != 'i' && *optarg != 'e' && *optarg != 'd')) d121 3 a123 3 fallback=1; syslog(LOG_INFO,"option '-%c%s' mandates fallback", c,optarg ? optarg : ""); d125 1 a125 31 break; default: /* well, there's an option we do not know, lets bail out */ fallback=1; syslog(LOG_INFO,"option '-%c' mandates fallback", c=='?'?optopt:c); break; } } if (!fallback) { /* options seem ok, look for config file in $HOME */ pwentry=getpwuid(getuid()); if (!pwentry) BAILOUT("getpwuid failed: %s",strerror(errno)); /* open and scan the conffile for an queue-file definition if there is no conffile, kuvert wont work ever */ snprintf(filen,sizeof(filen),"%s%s",pwentry->pw_dir,CONFFILE); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no .kuvert config file, fallback"); fallback=1; } else { /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) d127 8 a134 4 p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; d136 44 a179 22 if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } a180 20 fclose(cf); } } /* fallback to sendmail requested? */ if (fallback) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); c 0; spaceleft-=strlen(argv[c++])) { strncat(buffer,argv[c],spaceleft); --spaceleft && cpw_dir,DEFAULT_QUEUEDIR); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ d224 2 a225 2 /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); d227 32 a258 17 /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* and put the data there */ do { res=fread(buffer,1,BUFLEN,stdin); fwrite(buffer,1,res,out); } while (res==BUFLEN); d260 9 a268 7 fflush(out); if (flock(fileno(out),LOCK_UN)) { BAILOUT("flock (unlock) failed: %s\n",strerror(errno)); } fclose(out); return 0; a269 10 @ 1.2 log @added -oi, -d*, -e* to the list of allowed options @ text @d2 1 a2 1 * $Id: kuvert_mta_wrapper.c,v 1.1 2001/11/06 13:00:44 az Exp az $ d85 1 a85 1 syslog(LOG_INFO,"option '-%c %s' mandates fallback", d90 1 a90 1 /* -oi, -e*, -d* are ok */ d95 1 a95 1 syslog(LOG_INFO,"option '-%c %s' mandates fallback", d124 1 a124 4 /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) d126 3 a128 6 p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) d130 6 a135 4 p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) d137 2 a138 3 dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) d140 16 a155 5 if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; a156 4 /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; d158 1 a159 1 fclose(cf); @ 1.1 log @Initial revision @ text @d2 1 a2 1 * $Id: uard_mta_wrapper.c,v 2.4 2001/10/07 12:32:28 az Exp az $ d63 1 a63 1 -bm, -f, -i, -t, -v, -m. everything else means some special d69 1 a69 1 while ((c=getopt(argc,argv,"f:itvb:m"))!=-1 && !fallback) d86 11 a96 1 c,optarg); @ kuvert/kuvert_submit.pod0000444000000000000000000000414611157374252012705 0ustar # -*- mode: perl;-*- =pod =head1 NAME kuvert_submit - MTA wrapper for mail submission to kuvert(1) =head1 SYNOPSIS kuvert_submit [sendmail-options] [recipients...] =head1 DESCRIPTION Kuvert_submit submits an email either directly to L or enqueues it for L for further processing. kuvert_submit should be called by your MUA instead of your usual MTA to enable kuvert to intercept and process the outgoing mails. Please see your MUA's documentation about how to override the MTA to be used. Kuvert_submit transparently invokes C directly if it cannot find a ~/.kuvert configuration file, or if the -bv option is given. Otherwise, it enqueues the email in the queue directory specified in the configuration file. If that fails or if the configuration file is invalid, kuvert_submit prints an error message to STDERR and terminates with exit code 1. On successful submission, kuvert_submit terminates with exit code 0. Kuvert_submit also logs messages to syslog with the facility "mail". =head1 OPTIONS If it runs the MTA directly then kuvert_submit passes all options through to /usr/sbin/sendmail. Otherwise, it ignores all options except -f and -t (and -bv which triggers a direct sendmail pass-through). =over =item -f Sets the envelope sender. Kuvert_submit passes this on to kuvert. =item -t Tells an MTA to use the recipients in the mail instead of any commandline arguments. If -t is not given then kuvert_submit passes the recipients from the commandline on to kuvert. =back =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. If not present, kuvert_submit calls /usr/sbin/sendmail directly. =item ~/.kuvert_queue The default queue directory. =back =head1 SEE ALSO L =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2008 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut kuvert/debian/0000755000000000000000000000000012272163721010511 5ustar kuvert/debian/changelog0000644000000000000000000002277012272163705012375 0ustar kuvert (2.0.11) unstable; urgency=low * fixed clash between my lazy logfile handling and perl 5.18's increased fastidiousness (closes: #736978) -- Alexander Zangerl Wed, 29 Jan 2014 21:36:37 +1000 kuvert (2.0.10) unstable; urgency=medium * fixed one-character typo in control that marked dependency as optional (closes: #736774) -- Alexander Zangerl Mon, 27 Jan 2014 11:44:35 +1000 kuvert (2.0.9) unstable; urgency=low * now supports STARTTLS for outbound SMTP submission * lifted standards version, adjusted dependencies accordingly -- Alexander Zangerl Mon, 25 Nov 2013 20:44:19 +1000 kuvert (2.0.8) unstable; urgency=low * modified rules to support hardening build flags -- Alexander Zangerl Tue, 24 Sep 2013 13:32:35 +1000 kuvert (2.0.7) unstable; urgency=low * added timeout for gpg invocations -- Alexander Zangerl Tue, 04 Sep 2012 20:28:39 +1000 kuvert (2.0.6) unstable; urgency=low * updated standards version * cleaned up dependencies (closes: #665044) -- Alexander Zangerl Thu, 22 Mar 2012 22:09:12 +1000 kuvert (2.0.5) unstable; urgency=low * added option to control whether the explanatory mime preamble is generated or not. -- Alexander Zangerl Tue, 21 Feb 2012 11:44:03 +1000 kuvert (2.0.4) unstable; urgency=low * lifted standards version * added support for optional smtp authentication (for outbound mail) this requires authen::sasl which was added to the dependencies. -- Alexander Zangerl Thu, 16 Sep 2010 15:14:10 +1000 kuvert (2.0.3) unstable; urgency=low * fixed silly case-sensitivity bug: keys were downcased, but not email addresses, which may have caused kuvert to fall back to signing instead of encrypting. * lifted standards version -- Alexander Zangerl Tue, 20 Oct 2009 16:45:33 +1000 kuvert (2.0.2) unstable; urgency=low * modified kuvert_submit to honour the -bv option by running sendmail directly instead of enqueueing an email. -- Alexander Zangerl Mon, 16 Mar 2009 17:01:24 +1000 kuvert (2.0.1) unstable; urgency=low * fixed generation of default queue/tempdirs -- Alexander Zangerl Sun, 31 Aug 2008 16:39:52 +1000 kuvert (2.0.0) unstable; urgency=low * the next generation of kuvert: better SMTP support, no more pgp2, more precise control over keys, gpg agent support etc. * updated dependencies * updated standards version -- Alexander Zangerl Sun, 29 Jun 2008 21:25:51 +1000 kuvert (1.1.14) unstable; urgency=low * don't strip kuvert_mta_wrapper if DEB_BUILD_OPTIONS asks for that (closes: #437288) -- Alexander Zangerl Sun, 12 Aug 2007 13:24:20 +1000 kuvert (1.1.13) unstable; urgency=high * fixed stupid mistake wrt. variable init in kuvert_mta_wrapper which caused the wrapper to fall back to sendmail most of the time. -- Alexander Zangerl Sat, 23 Jun 2007 13:16:42 +1000 kuvert (1.1.12) unstable; urgency=low * the signature MIME-part is now tagged a bit more extensively (as per hint from Andreas Labres/Andreas Kreuzinger) -- Alexander Zangerl Sat, 23 Jun 2007 12:39:05 +1000 kuvert (1.1.11) unstable; urgency=low * lifted standards version -- Alexander Zangerl Tue, 10 Apr 2007 18:24:40 +1000 kuvert (1.1.10) unstable; urgency=low * added libproc-pid-file-perl dependency * deprecated AGENTPATH/CLIENTPATH configuration setting and cleaned up secret on demand stuff * lifted standards version -- Alexander Zangerl Fri, 4 Nov 2005 16:20:20 +1000 kuvert (1.1.9) unstable; urgency=high * the "don't trust input. do be conservative and paranoid." release. fixed a potential security problem re email addresses that are borderline rfc2822-compliant by calling the mta without shell interference. * updated homepage url -- Alexander Zangerl Sat, 26 Feb 2005 08:11:26 +1000 kuvert (1.1.8) unstable; urgency=low * lifted standards version * added homepage to description -- Alexander Zangerl Thu, 11 Dec 2003 12:52:40 +1000 kuvert (1.1.7) unstable; urgency=low * added example ~/.kuvert * kuvert now produces a blank ~/.kuvert if none is found on startup -- Alexander Zangerl Sun, 3 Aug 2003 11:53:19 +1000 kuvert (1.1.6) unstable; urgency=high * fixed bad problem with mixture of raw and signed/encr'd mails: the raw mail would lose most of its headers due to an overzealous "code improvement". problem was only present with multi-recipient mails. -- Alexander Zangerl Wed, 11 Jun 2003 19:45:51 +1000 kuvert (1.1.5) unstable; urgency=low * fixed variable length macro (problem only with gcc 3.3) (closes: #194944) * bumped standards version -- Alexander Zangerl Wed, 28 May 2003 20:12:44 +1000 kuvert (1.1.4) unstable; urgency=low * bumped standards version, now only suggests old-style pgp * fixed error behaviour (sent multiple error messages with -b) -- Alexander Zangerl Fri, 25 Apr 2003 17:34:18 +1000 kuvert (1.1.3) unstable; urgency=low * standards version lifted, debhelper cleanup -- Alexander Zangerl Sun, 9 Mar 2003 11:23:30 +1000 kuvert (1.1.2) unstable; urgency=low * added IDENTIFY directive: adds an X-Mailer header (closes: Bug#181868) -- Alexander Zangerl Sat, 22 Feb 2003 14:58:52 +1000 kuvert (1.1.1) unstable; urgency=low * fixed problem with duplicate entries in pidfile on unclean shutdown. * pidfile honors $TMPDIR now. * added option "-b": sends barfmail when fatal error encountered (closes: Bug179345) -- Alexander Zangerl Sun, 16 Feb 2003 23:44:15 +1000 kuvert (1.1.0) unstable; urgency=low * complete overhaul of the code, streamlined, better error handling * added new dependency on libterm-readkey-perl * fixed handling of idea-extension (closes: Bug#162279) * added Bcc handling (closes: Bug#162024) * removed /usr/doc transition stuff * fallback option now falls back down to std key if no ng private key av. -- Alexander Zangerl Tue, 21 Jan 2003 22:33:16 +1000 kuvert (1.0.13) unstable; urgency=low * default tempdir now honors $TMPDIR (closes: Bug#161326) * does not start anymore without config file (closes: Bug#161327) * some new sanity checks and clarifications in the manpages and README * added option MTA * SECRETONDEMAND option extended and clarified -- Alexander Zangerl Thu, 19 Sep 2002 18:04:18 +0200 kuvert (1.0.12) unstable; urgency=high * finally fixed typo trashing the handling of the 'fallback' action -- Alexander Zangerl Sun, 28 Apr 2002 01:50:15 +1000 kuvert (1.0.11) unstable; urgency=high * fixed typo that trashed handling of -force -- Alexander Zangerl Fri, 26 Apr 2002 12:11:54 +1000 kuvert (1.0.10) unstable; urgency=medium * manpage improvements * -force handling fixed (did not work as DEFAULT action) * -force semantics finetuned: action=none is not overrideable by -force or the override header -- Alexander Zangerl Fri, 26 Apr 2002 00:43:03 +1000 kuvert (1.0.9) unstable; urgency=low * moved into main/mail as per recent announcement -- Alexander Zangerl Sun, 24 Mar 2002 17:20:40 +1000 kuvert (1.0.8) unstable; urgency=low * changed mail addressing in send_bounce: no angle brackets. note: this does not fulfil rfc2822, 3.4.1, but as this is local mail it does not really concern me. (closes: Bug#136619) -- Alexander Zangerl Tue, 5 Mar 2002 23:19:00 +1000 kuvert (1.0.7) unstable; urgency=low * fixed dependency mailtools -> libmailtools-perl * fixed version-number generation -- Alexander Zangerl Sat, 16 Feb 2002 21:06:52 +1000 kuvert (1.0.6) unstable; urgency=low * gpg-idea has been removed due to patent problems (#126506), removing suggestion -- Alexander Zangerl Thu, 31 Jan 2002 20:46:13 +1000 kuvert (1.0.5) unstable; urgency=low * added config of queue check interval * added -v version command, added version logging at start -- Alexander Zangerl Thu, 31 Jan 2002 00:24:56 +1000 kuvert (1.0.4) unstable; urgency=low * fixed bug: handling of no-std-pgp works now -- Alexander Zangerl Sun, 27 Jan 2002 22:32:42 +1000 kuvert (1.0.3) unstable; urgency=low * added nofork option (-n) * fixed behaviour in debugging mode * improved manpage * improved handling of expired, revoked, invalid keys * added -force actions -- Alexander Zangerl Wed, 2 Jan 2002 16:43:30 +1000 kuvert (1.0.2) unstable; urgency=low * fixed tempdir and queuedir generation for default dirs * fixed handling of setups with pgp XOR gpg * changed sendmail error mode to -oem (mailback, but with return code) * some manpage clarifications * fixed reporting for garbled mails * added logging to file -- Alexander Zangerl Sun, 11 Nov 2001 19:24:05 +1000 kuvert (1.0.1) unstable; urgency=low * fixed problems with missing .kuvert config file. * added more options for sendmail to be recognized (-oi,-od*,-oe*) -- Alexander Zangerl Tue, 6 Nov 2001 23:23:39 +1000 kuvert (1.0.0) unstable; urgency=low * Initial Release. * Renamed from 'guard' to 'kuvert', debianized. -- Alexander Zangerl Sun, 21 Oct 2001 23:21:16 +1000 kuvert/debian/kuvert.docs0000644000000000000000000000000712244634472012705 0ustar README kuvert/debian/kuvert.examples0000644000000000000000000000001207713065122013562 0ustar dot-kuvertkuvert/debian/control0000644000000000000000000000155612271334750012124 0ustar Source: kuvert Section: mail Priority: extra Maintainer: Alexander Zangerl Build-Depends: debhelper (>= 8) Standards-Version: 3.9.5.0 Package: kuvert Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, gnupg (>= 1.0.6), sendmail | mail-transport-agent, libnet-smtps-perl, ${perl:Depends}, libmailtools-perl, libmime-tools-perl, libfile-slurp-perl, libnet-server-mail-perl, libauthen-sasl-perl Suggests: keyutils Homepage: http://www.snafu.priv.at/mystuff/kuvert/ Description: wrapper that encrypts or signs outgoing mail kuvert automatically signs and/or encrypts outgoing mail using the PGP/MIME standard (RFC3156), based on the availability of the recipient's key in your keyring. Other than similar wrappers, kuvert does not store key passphrases itself, ever. kuvert works as a wrapper around your MTA but can be fed mails via SMTP, too. kuvert/debian/copyright0000644000000000000000000000176411732613342012453 0ustar This is kuvert, written and maintained by Alexander Zangerl on Sun, 21 Oct 2001 23:21:16 +1000. The original source can always be found at: http://www.snafu.priv.at/kuvert/ Copyright (C) 1999-2001 Alexander Zangerl 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 (at your option) 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. kuvert/debian/NEWS0000644000000000000000000000273411031671276011217 0ustar kuvert (2.0.0) unstable; urgency=low kuvert has been completely revamped, with a number of new features and minus some legacy stuff. New: full inbound and outbound support for SMTP support for gpg-agent simplified action settings overridable keys and actions in the comments of From: and To: simpler submission wrapper program Gone: support for pgp2 (but RSA keys continue to work fine, with gpg) kuvert no longer stores passphrases itself, ever. Because of these changes the new config file format is different. The new kuvert will not run with an old-style config file, but does a rough auto-conversion on startup. It'll dump that skeleton in /tmp and tell you about it. You will need to go over that and the kuvert manpage and adjust the config to your liking. -- Alexander Zangerl Sun, 29 Jun 2008 21:32:14 +1000 kuvert (1.1.10) unstable; urgency=low The configuration options AGENTPATH and CLIENTPATH have been deprecated and support for a private q-agent process was dropped; while kuvert still suggests quintuple-agent, it is no longer favouring it and will work with any (reasonable) external passphrase store. kuvert will not start until you update your personal .kuvert configuration file and remove AGENTPATH/CLIENTPATH and use the new GETSECRET and DELSECRET directives if you want to use an external passphrase store. -- Alexander Zangerl Fri, 4 Nov 2005 16:43:14 +1000 kuvert/debian/compat0000644000000000000000000000000211732613003011700 0ustar 8 kuvert/debian/rules0000755000000000000000000000213011732614034011563 0ustar #!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 export DESTDIR=$(CURDIR)/debian/kuvert CFLAGS= -g ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif build: build-arch build-indep build-arch: build-stamp build-indep: build-stamp build-stamp: dh_testdir $(MAKE) CFLAGS="$(CFLAGS)" touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp $(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_prep dh_installdirs $(MAKE) install DESTDIR=$(DESTDIR) # Build architecture-independent files here. binary-indep: build install # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installdocs -n dh_installexamples dh_installchangelogs dh_strip dh_compress dh_fixperms dh_installdeb dh_perl dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install build-arch build-indep