1#!/usr/bin/perl
2# doublebounce.pl
3#
4# Return a doubly-bounced e-mail to postmaster.  Specific to sendmail,
5# updated to work on sendmail 8.12.6.
6#
7# Based on the original doublebounce.pl code by jr@terra.net, 12/4/97.
8# Updated by bicknell@ufp.org, 12/4/2002 to understand new sendmail DSN
9# bounces.  Code cleanup also performed, mainly making things more
10# robust.
11#
12# Original intro included below, lines with ##
13##	attempt to return a doubly-bounced email to a postmaster
14##	jr@terra.net, 12/4/97
15##
16##	invoke by creating an mail alias such as:
17##		doublebounce:	"|/usr/local/sbin/doublebounce"
18##	then adding this line to your sendmail.cf:
19##		O DoubleBounceAddress=doublebounce
20##
21##	optionally, add a "-d" flag in the aliases file, to send a
22##	debug trace to your own postmaster showing what is going on
23##
24##	this allows the "postmaster" address to still go to a human being,
25##	while bounce messages can go to this script, which will bounce them
26##	back to the postmaster at the sending site.
27##
28##	the algorithm is to scan the double-bounce error report generated
29##	by sendmail on stdin, for the original message (it starts after the
30##	second "Orignal message follows" marker), look for From, Sender, and
31##	Received headers from the point closest to the sender back to the point
32##	closest to us, and try to deliver a double-bounce report back to a
33##	postmaster at one of these sites in the hope that they can
34##	return the message to the original sender, or do something about
35##	the fact that that sender's return address is not valid.
36
37use Socket;
38use Getopt::Std;
39use File::Temp;
40use Sys::Syslog qw(:DEFAULT setlogsock);
41use strict;
42use vars qw( $opt_d $tmpfile);
43
44# parseaddr()
45#	parse hostname from From: header
46#
47sub parseaddr {
48  my($hdr) = @_;
49  my($addr);
50
51  if ($hdr =~ /<.*>/) {
52    ($addr) = $hdr =~ m/<(.*)>/;
53    $addr =~ s/.*\@//;
54    return $addr;
55  }
56  if ($addr =~ /\s*\(/) {
57    ($addr) = $hdr =~ m/\s*(.*)\s*\(/;
58    $addr =~ s/.*\@//;
59    return $addr;
60  }
61  ($addr) = $hdr =~ m/\s*(.*)\s*/;
62  $addr =~ s/.*\@//;
63  return $addr;
64}
65
66# sendbounce()
67#	send bounce to postmaster
68#
69#	this re-invokes sendmail in immediate and quiet mode to try
70#	to deliver to a postmaster.  sendmail's exit status tells us
71#	whether the delivery attempt really was successful.
72#
73sub send_bounce {
74  my($addr, $from) = @_;
75  my($st);
76  my($result);
77
78  my($dest) = "postmaster\@" . parseaddr($addr);
79
80  if ($opt_d) {
81    syslog ('info', "Attempting to send to user $dest");
82  }
83  open(MAIL, "| /usr/sbin/sendmail -oeq $dest");
84  print MAIL <<EOT;
85From: Mail Delivery Subsystem <mail-router>
86Subject: Postmaster notify: double bounce
87Reply-To: nobody
88Errors-To: nobody
89Precedence: junk
90Auto-Submitted: auto-generated (postmaster notification)
91
92The following message was received for an invalid recipient.  The
93sender's address was also invalid.  Since the message originated
94at or transited through your mailer, this notification is being
95sent to you in the hope that you will determine the real originator
96and have them correct their From or Sender address.
97
98The from header on the original e-mail was: $from.
99
100   ----- The following is a double bounce -----
101
102EOT
103
104  open(MSG, "<$tmpfile");
105  print MAIL <MSG>;
106  close(MSG);
107  $result = close(MAIL);
108  if ($result) {
109    syslog('info', 'doublebounce successfully sent to %s', $dest);
110  }
111  return $result;
112}
113
114sub main {
115  # Get our command line options
116  getopts('d');
117
118  # Set up syslog
119  setlogsock('unix');
120  openlog('doublebounce', 'pid', 'mail');
121
122  if ($opt_d) {
123    syslog('info', 'Processing a doublebounce.');
124  }
125
126  # The bounced e-mail may be large, so we'd better not try to buffer
127  # it in memory, get a temporary file.
128  $tmpfile = tmpnam();
129
130  if (!open(MSG, ">$tmpfile")) {
131    syslog('err', "Unable to open temporary file $tmpfile");
132    exit(75); # 75 is a temporary failure, sendmail should retry
133  }
134  print(MSG <STDIN>);
135  close(MSG);
136  if (!open(MSG, "<$tmpfile")) {
137    syslog('err', "Unable to reopen temporary file $tmpfile");
138    exit(74); # 74 is an IO error
139  }
140
141  # Ok, now we can get down to business, find the original message
142  my($skip_lines, $in_header, $headers_found, @addresses);
143  $skip_lines = 0;
144  $in_header = 0;
145  $headers_found = 0;
146  while (<MSG>) {
147    if ($skip_lines > 0) {
148      $skip_lines--;
149      next;
150    }
151    chomp;
152    # Starting message depends on your version of sendmail
153    if (/^   ----- Original message follows -----$/ ||
154        /^   ----Unsent message follows----$/ ||
155        /^Content-Type: message\/rfc822$/) {
156      # Found the original message
157      $skip_lines++;
158      $in_header = 1;
159      $headers_found++;
160      next;
161    }
162    if (/^$/) {
163      if ($headers_found >= 2) {
164         # We only process two deep, even if there are more
165         last;
166      }
167      if ($in_header) {
168         # We've found the end of a header, scan for the next one
169         $in_header = 0;
170      }
171      next;
172    }
173    if ($in_header) {
174      if (! /^[ \t]/) {
175        # New Header
176        if (/^(received): (.*)/i ||
177            /^(reply-to): (.*)/i ||
178            /^(sender): (.*)/i ||
179            /^(from): (.*)/i ) {
180          $addresses[$headers_found]{$1} = $2;
181        }
182        next;
183      } else {
184        # continuation header
185        # we should really process these, but we don't yet
186        next;
187      }
188    } else {
189      # Nothing to do if we're not in a header
190      next;
191    }
192  }
193  close(MSG);
194
195  # Start with the original (inner) sender
196  my($addr, $sent);
197  foreach $addr (keys %{$addresses[2]}) {
198    if ($opt_d) {
199      syslog('info', "Trying to send to $addresses[2]{$addr} - $addresses[2]{\"From\"}");
200    }
201    $sent = send_bounce($addresses[2]{$addr}, $addresses[2]{"From"});
202    last if $sent;
203  }
204  if (!$sent && $opt_d) {
205    if ($opt_d) {
206      syslog('info', 'Unable to find original sender, falling back.');
207    }
208    foreach $addr (keys %{$addresses[1]}) {
209      if ($opt_d) {
210        syslog('info', "Trying to send to $addresses[2]{$addr} - $addresses[2]{\"From\"}");
211      }
212      $sent = send_bounce($addresses[1]{$addr}, $addresses[2]{"From"});
213      last if $sent;
214    }
215    if (!$sent) {
216      syslog('info', 'Unable to find anyone to send a doublebounce notification');
217    }
218  }
219
220  unlink($tmpfile);
221}
222
223main();
224exit(0);
225
226