9f7c30c24a31f9fb7a7c68393a19ee5dcc73839b
[PerlMail.git] / perlmail-sendmail
1 #! /usr/bin/perl
2
3 #       $Id$
4 # Copyright (C) 2002-2003 Jan Kratochvil <project-PerlMail@jankratochvil.net>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 use vars qw($VERSION);
22 $VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; };
23 use strict;
24 use warnings;
25
26 use File::Basename;
27 BEGIN {
28         my $self=$0;
29         $self=readlink $self while -l $self;
30         eval 'use lib $ENV{"PERLMAIL_BASEDIR"} || File::Basename::dirname($self);';
31         }
32 use PerlMail::Config;
33 use PerlMail::Lib;
34
35 require Getopt::Long;
36 use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG);
37 require MIME::Head;     # inherits Mail::Header
38 require Mail::Address;
39
40
41 sub sendmail_show { return "\"$sendmail_orig\" ".join(",",map("\"$_\"",@ARGV)); }
42
43 sub sendmail_orig_exec
44 {
45         exec {$sendmail_orig} $0,@ARGV or die "exec(".sendmail_show()."): $!";
46         die "NOTREACHED";
47 }
48
49 Getopt::Long::Configure(
50                 "no_ignorecase",
51                 "no_getopt_compat",
52                 "bundling",
53                 # FIXME: workaround: 'unknown options' are considered the same as 'arguments'
54                 # None of ($REQUIRE_ORDER, $PERMUTE, $RETURN_IN_ORDER) can help us.
55                 # No preprocessing possible as it is hard to find option arguments.
56                 "permute",
57                 "pass_through",
58                 );
59
60 my $opt_b;
61 my $opt_Q;
62 my $opt_q;
63 my $opt_t;
64 our $opt_f;     # not exported, just for local()
65 my $opt_perlmail_dry_run;
66 my @ARGV_save=@ARGV;    # for non-bm mode
67 die if !Getopt::Long::GetOptions(
68                 "b=s"              ,\$opt_b,
69                 "Q:s"              ,\$opt_Q,
70                 "q:s"              ,\$opt_q,
71                 "t"                ,\$opt_t,
72                 "f=s"              ,\$opt_f,
73                 "F=s"              ,\$opt_F,
74                 "perlmail-dry-run+",\$opt_perlmail_dry_run,
75                 );
76 if (0
77                 # RedHat sendmail-8.12.5-7/sendmail/main.c/\QDo a quick prescan of the argument list.\E
78                 || grep({ File::Basename::basename($0) eq $_; } "newaliases","mailq","smtpd","hoststat","purgestat")
79                 # -bm: Deliver mail in the usual way (default).
80                 || (defined($opt_b) && $opt_b ne "m")
81                 || defined $opt_q       # MD_QUEUERUN
82                 || defined $opt_Q       # MD_QUEUERUN
83                 ) {
84         @ARGV=@ARGV_save;
85         sendmail_orig_exec();
86         die "NOTREACHED";
87         }
88
89 # RedHat sendmail-8.9.3-20/src/main.c/main()/\Qif (FullName != NULL)\E
90 #   for $opt_F is implemented by Mail::Address in our &FromAddress
91
92 my $head=MIME::Head->new(\*STDIN);
93 # options leave in @ARGV, addresses to @addr:
94 my @args=@ARGV; # temporary
95 @ARGV=();       # options
96 my @addr=();    # addresses
97 push @{(/^-./ ? \@ARGV : \@addr)},$_ for (@args);
98 if ($opt_t) {
99         for my $addrobj (map({ Mail::Address->parse($_); } map({ ($head->get($_)); } @h_rcpt))) {
100                 if (!$addrobj->address()) {
101                         # bogus, shouldn't happen
102                         warn "->address() not found in \"".$addrobj->format()."\"";
103                         next;
104                 }
105                 push @addr,$addrobj;
106                 }
107         }
108
109 sub matches
110 {
111         return 
112 }
113
114 my $from_headername;
115 {
116         my $muttrc_From=parseone(scalar muttrc_get("from"));    # may get undef()!; parseone() may be redundant
117         $muttrc_From=$muttrc_From->address() if $muttrc_From;
118         $opt_f=undef() if defined($opt_f) && $muttrc_From && lc($opt_f) eq lc($muttrc_From);
119         for (@h_from) {
120                 $from_headername=$_;    # leave last item in $from_headername
121                 next if !(my @from_val=$head->get($from_headername));
122                 @from_val=map({ ($_->address()); } map({ (Mail::Address->parse($_)); } @from_val));
123                 $from_headername=undef() if !(1==@from_val && $muttrc_From && lc($from_val[0]) eq lc($muttrc_From));
124                 last;
125                 }       # fallthru with $from_headername remaining set if last headername did not exist
126         # now $from_headername contains the header name to be replaced w/substituted value
127         }
128
129 # to be utilized later by &FromAddress
130 $is_pgp=(1
131                 && do { local $_=$head->mime_attr("Content-Type");          $_ && ~m#^multipart/(?:signed|encrypted)$#; }
132                 && do { local $_=$head->mime_attr("Content-Type.protocol"); $_ && ~m#^application/pgp\b#; }
133                 );
134
135 my $exitcode=0;
136 # !defined($rcpt) if we have no recipients
137 # make the list unique to prevent dupes being normally filtered by sendmail(8)
138 # one '{' is block-wrapper, another '{' is hash-indirection!
139 # hash keys are just strings, never refs!
140 # unify the list as Mail::Address instances
141 my @rcpts=(!@addr ? (undef()) : values(%{{ map({
142                 my $obj=$_;
143                 $obj=parseone $obj if !ref $obj;
144                 (!defined $obj ? () : (lc($obj->address())=>$obj));
145                 } @addr) }}));
146
147 my $stdin_body=(@rcpts<=1 ? undef() : do {      # store input data only if it will be used multiple times
148                 local $/=undef();
149                 <STDIN>;
150                 });
151 for my $rcpt (@rcpts) {
152         local @ARGV=@ARGV;
153         local $opt_f=$opt_f;
154
155         if (defined $rcpt) {    # !defined($rcpt) if we have no recipients
156                 local $_;
157                 $opt_f=FromAddress($rcpt,1)->address() if !defined $opt_f;
158                 if ($from_headername) {
159                         if (my $fromaddr=FromAddress($rcpt,0)->format()) {
160                                 $head->replace($from_headername,$fromaddr);
161                                 }
162                         }
163                 }
164
165         1;      # drop '-bm' if present as it is default anyway
166         1;      # drop '-t' if present as we are looping now for it
167         push @ARGV,"-f",$opt_f if defined $opt_f;
168         # we don't handle "Full-Name" header thus pass "-F"
169         # "From/Resent-From" should be handled by our &FromAddress
170         push @ARGV,"-F",$opt_F if defined $opt_F;
171         push @ARGV,$rcpt->address() if defined $rcpt;
172         push @ARGV,@addr_addon;
173
174         local $SIG{"PIPE"}=sub { die "Got SIGPIPE from ".sendmail_show(); };
175         local *SENDMAIL;
176         if ($opt_perlmail_dry_run) {
177                 print sendmail_show()."\n";
178                 *SENDMAIL=\*STDOUT;
179                 }
180         else {
181                 defined (my $pid=open SENDMAIL,"|-") or die "Cannot fork to spawn ".sendmail_show().": $!";
182                 sendmail_orig_exec() if !$pid; # child
183                 }
184         $head->print(\*SENDMAIL);
185         print SENDMAIL "\n";    # MIME::Head->print() eats the empty line but it doesn't print it
186         if (defined($stdin_body)) {
187                 print SENDMAIL $stdin_body;
188                 }
189         else {
190                 local $_;
191                 while (<STDIN>) {
192                         print SENDMAIL $_;
193                         }
194                 }
195
196         next if $opt_perlmail_dry_run;  # don't close our STDOUT as it is aliased to *SENDMAIL
197         close SENDMAIL or warn "close(".sendmail_show()."): $?=".join(",",
198                         (!WIFEXITED($?)   ? () : ("EXITSTATUS(".WEXITSTATUS($?).")")),
199                         (!WIFSIGNALED($?) ? () : ("TERMSIG("   .WTERMSIG($?)   .")")),
200                         (!WIFSTOPPED($?)  ? () : ("STOPSIG("   .WSTOPSIG($?)   .")")),
201                         );
202         my $gotcode=(!WIFEXITED($?) ? 99 : WEXITSTATUS($?));
203         $exitcode=$gotcode if $gotcode>$exitcode;
204         }
205 exit $exitcode;