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