From 08463cf3bb94340b4a4bd08ae16919e96a483b38 Mon Sep 17 00:00:00 2001 From: short <> Date: Sun, 6 Oct 2002 19:42:36 +0000 Subject: [PATCH] First production sendmail(8) wrapper --- perlmail-sendmail | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100755 perlmail-sendmail diff --git a/perlmail-sendmail b/perlmail-sendmail new file mode 100755 index 0000000..59ea8b1 --- /dev/null +++ b/perlmail-sendmail @@ -0,0 +1,282 @@ +#! /usr/bin/perl +# +# $Id$ + +use vars qw($VERSION); +$VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; }; +use strict; +use warnings; + +require Getopt::Long; +use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG); +require Mail::Header; +require Mail::Address; +require File::Basename; + +my $sendmail_orig=(-x ($_="/usr/sbin/sendmail-orig") ? $_ : "/usr/sbin/sendmail"); +my $HOME="/home/lace"; +my $opt_F; +sub FromAddress +{ +my($rcpt,$iserror)=@_; + + return Mail::Address->new( + (defined $opt_F ? $opt_F : "Jan Kratochvil"), + (!$iserror ? 'rcpt' : 'rcpterr') + .'-' + .(defined($rcpt->user()) ? $rcpt->user() : "NOUSER") + .".AT." + .(defined($rcpt->host()) ? $rcpt->host() : "LOCAL") + .'@jankratochvil.net', + ); +} + +# RedHat sendmail-8.9.3-20/src/conf.c/HdrInfo[]/\Q/* destination fields */\E +# FIXME: Recognize "Resent-$_" headers for -t but when we are in 'resent' mode? +my @h_rcpt=( # case in-sensitive! + "To", + "Cc", + "Bcc", + "Apparently-To", + ); + +# ordering matters; first header found is substituted +# last header is subsituted if no one is found +my @h_from=( + "Resent-From", + "From", + ); + + +# FIXME: modularized unification with 'lacemail-accept' +# BEGIN lacemail-accept +our %muttrc_pending=(); +sub muttrc +{ +my($muttrc)=@_; + + $muttrc||="$HOME/.muttrc"; + $muttrc=~s/^\~/$HOME/; + do { warn "Looping muttrc, ignoring: $muttrc"; return (); } if $muttrc_pending{$muttrc}; + local $muttrc_pending{$muttrc}=1; + local *MUTTRC; + open MUTTRC,$muttrc or do { warn "open \"$muttrc\": $!"; return (); }; + local $/="\n"; + local $_; + my @r=(); + # far emulation mutt/init.c/mutt_parse_rc_line() + while () { + s/^[\s;]*//s; + s/[#;].*$//s; + s/\s*$//s; + next if !/^(\S+)\s*/s; + if ($1 eq "source") { + $_=$'; + do { warn "Wrong 'source' parameters at $muttrc:$.: $_"; next; } if !/^\S+$/; + push @r,muttrc($_); + next; + } + push @r,$_; + } + close MUTTRC or warn "close \"$muttrc\": $!"; + return wantarray() ? @r : join("",map("$_\n",@r)); +} + +my %mutteval_charmap=( # WARNING: Don't use "" or "0" here, see below for "|| warn"! + '\\'=>"\\", + 'r'=>"\r", + 'n'=>"\n", + 't'=>"\t", + 'f'=>"\f", + 'e'=>"\e", + ); +# mutt/init.c/mutt_extract_token() +sub mutteval +{ + local $_=$_[0]; + return $_ if !s/^"//; + do { warn "Missing trailing quote in: $_"; return $_; } if !s/"$//; + s/\\(.)/$mutteval_charmap{$1} || warn "Undefined '\\$1' sequence in: $_";/ges; + return $_; +} + +sub muttrc_get +{ +my(@headers)=@_; + + my @r=map({ (ref $_ ? $_ : qr/^\s*set\s+\Q$_\E\s*=\s*(.*?)\s*$/si); } @headers); + my %r=map(($_=>undef()),@r); + for (muttrc()) { + for my $ritem (@r) { + /$ritem/si or next; + $r{$ritem}=mutteval $1; + } + } + for my $var (grep { !defined($r{$_}) } @r) { + warn "Variable '$var' not found in muttrc"; + return undef(); + } + return wantarray() ? %r : $r{$r[0]}; +} +# END lacemail-accept + + +sub sendmail_show { return "\"$sendmail_orig\" ".join(",",map("\"$_\"",@ARGV)); } + +sub sendmail_orig_exec +{ + exec {$sendmail_orig} $0,@ARGV or die "exec(".sendmail_show()."): $!"; + die "NOTREACHED"; +} + +Getopt::Long::Configure( + "no_ignorecase", + "no_getopt_compat", + "bundling", + # FIXME: workaround: 'unknown options' are considered the same as 'arguments' + # None of ($REQUIRE_ORDER, $PERMUTE, $RETURN_IN_ORDER) can help us. + # No preprocessing possible as it is hard to find option arguments. + "permute", + "pass_through", + ); + +my $opt_b; +my $opt_t; +our $opt_f; +#my $opt_F; # declared before &FromAddress already +my $opt_lacemail_dry_run; +my @ARGV_save=@ARGV; # for non-bm mode +die if !Getopt::Long::GetOptions( + "b=s" ,\$opt_b, + "t" ,\$opt_t, + "f=s" ,\$opt_f, + "F=s" ,\$opt_F, + "lacemail-dry-run+",\$opt_lacemail_dry_run, + ); +if (0 + # RedHat sendmail-8.12.5-7/sendmail/main.c/\QDo a quick prescan of the argument list.\E + || grep({ File::Basename::basename($0) eq $_; } "newaliases","mailq","smtpd","hoststat","purgestat") + # -bm: Deliver mail in the usual way (default). + || (defined($opt_b) && $opt_b ne "m") + ) { + @ARGV=@ARGV_save; + sendmail_orig_exec(); + die "NOTREACHED"; + } + +# RedHat sendmail-8.9.3-20/src/main.c/main()/\Qif (FullName != NULL)\E +# for $opt_F is implemented by Mail::Address in our &FromAddress + +my $head=Mail::Header->new(\*STDIN); +# We may (=will) change the contents and send it multiple times +if (defined(my $msgid=$head->get("Message-ID"))) { + $head->delete("Message-ID"); + $head->replace("X-LaceMail-sendmail-Message-ID",$msgid); + } +# options leave in @ARGV, addresses to @addr: +my @args=@ARGV; # temporary +@ARGV=(); # options +my @addr=(); # addresses +push @{(/^-./ ? \@ARGV : \@addr)},$_ for (@args); +if ($opt_t) { + for my $addrobj (map({ Mail::Address->parse($_); } map({ ($head->get($_)); } @h_rcpt))) { + if (!$addrobj->address()) { + # bogus, shouldn't happen + warn "->address() not found in \"".$addrobj->format()."\""; + next; + } + push @addr,$addrobj; + } + } + +# return: Mail::Address instance or undef() +sub parseone +{ +my($line)=@_; + + return undef() if !defined $line; + my @r=Mail::Address->parse($line); + warn "Got ".scalar(@r)." addresses while wanting just one; when parsing: $line" if 1!=@r; + return $r[0]; +} + +sub matches +{ + return +} + +my $from_headername; +{ + my $muttrc_From=parseone(scalar muttrc_get("from")); # may get undef()!; parseone() may be redundant + $muttrc_From=$muttrc_From->address() if $muttrc_From; + $opt_f=undef() if defined($opt_f) && $muttrc_From && lc($opt_f) eq lc($muttrc_From); + my @from_val; + for (@h_from) { + $from_headername=$_; # leave last item in $from_headername + last if @from_val=$head->get($from_headername); + } + @from_val=map({ ($_->address()); } map({ (Mail::Address->parse($_)); } @from_val)); + $from_headername=undef() if !(1==@from_val && $muttrc_From && lc($from_val[0]) eq lc($muttrc_From)); + # now $from_headername contains the header name to be replaced w/substituted value + } + +my $exitcode=0; +my @rcpts=(@addr ? @addr : (undef())); # !defined($rcpt) if we have no recipients +my $stdin_body=(@rcpts<=1 ? undef() : do { # store input data only if it will be used multiple times + local $/=undef(); + ; + }); +for my $rcpt (@rcpts) { + local @ARGV=@ARGV; + local $opt_f=$opt_f; + + if (defined $rcpt) { # !defined($rcpt) if we have no recipients + local $_; + if (!ref $rcpt) { + $rcpt=parseone $rcpt; + next if !defined $rcpt; + } + $opt_f=FromAddress($rcpt,1)->address() if !defined $opt_f; + $head->replace($from_headername,FromAddress($rcpt,0)->format()) if $from_headername; + } + + 1; # drop '-bm' if present as it is default anyway + 1; # drop '-t' if present as we are looping now for it + push @ARGV,"-f",$opt_f if defined $opt_f; + # we don't handle "Full-Name" header thus pass "-F" + # "From/Resent-From" should be handled by our &FromAddress + push @ARGV,"-F",$opt_F if defined $opt_F; + push @ARGV,$rcpt->address() if defined $rcpt; + + local $SIG{"PIPE"}=sub { die "Got SIGPIPE from ".sendmail_show(); }; + local *SENDMAIL; + if ($opt_lacemail_dry_run) { + print sendmail_show()."\n"; + *SENDMAIL=\*STDOUT; + } + else { + defined (my $pid=open SENDMAIL,"|-") or die "Cannot fork to spawn ".sendmail_show().": $!"; + sendmail_orig_exec() if !$pid; # child + } + $head->print(\*SENDMAIL); + print "\n"; # Mail::Header->print() eats the empty line but it doesn't print it + if (defined($stdin_body)) { + print SENDMAIL $stdin_body; + } + else { + local $_; + while () { + print SENDMAIL $_; + } + } + + next if $opt_lacemail_dry_run; # don't close our STDOUT as it is aliased to *SENDMAIL + close SENDMAIL or warn "close(".sendmail_show()."): $?=".join(",", + (!WIFEXITED($?) ? () : ("EXITSTATUS(".WEXITSTATUS($?).")")), + (!WIFSIGNALED($?) ? () : ("TERMSIG(" .WTERMSIG($?) .")")), + (!WIFSTOPPED($?) ? () : ("STOPSIG(" .WSTOPSIG($?) .")")), + ); + my $gotcode=(!WIFEXITED($?) ? 99 : WEXITSTATUS($?)); + $exitcode=$gotcode if $gotcode>$exitcode; + } +exit $exitcode; -- 1.8.3.1