summaryrefslogtreecommitdiff
path: root/gnu/usr.sbin/sendmail/contrib/bounce-resender.pl
blob: 9253cddff28354a81641c74946b41fdf7acc2d75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#!/usr/local/bin/perl -w
#
# bounce-resender: constructs mail queue from bounce spool for
#  subsequent reprocessing by sendmail
#
# usage: given a mail spool full of (only) bounced mail called "bounces":
#        # mkdir -m0700 bqueue; cd bqueue && bounce-resender < ../bounces
#        # cd ..
#        # chown -R root bqueue; chmod 600 bqueue/*
#        # /usr/lib/sendmail -bp -oQ`pwd`/bqueue | more   # does it look OK?
#        # /usr/lib/sendmail -q -oQ`pwd`/bqueue -oT99d &  # run the queue
#
# ** also read messages at end! **
#
# Brian R. Gaeke <brg@EECS.Berkeley.EDU> Thu Feb 18 13:40:10 PST 1999
#
#############################################################################
# This script has NO WARRANTY, NO BUG FIXES, and NO SUPPORT.  You will
# need to modify it for your site and for your operating system, unless
# you are in the EECS Instructional group at UC Berkeley. (Search forward
# for two occurrences of "FIXME".)
#

$state = "MSG_START";
$ctr = 0;
$lineno = 0;
$getnrl = 0;
$nrl = "";
$uname = "PhilOS";  # You don't want to change this here.
$myname = $0;
$myname =~ s,.*/([^/]*),$1,;

chomp($hostname = `hostname`);
chomp($uname = `uname`);

# FIXME: Define the functions "major" and "minor" for your OS.
if ($uname eq "SunOS") {
	# from h2ph < /usr/include/sys/sysmacros.h on 
	# SunOS torus.CS.Berkeley.EDU 5.6 Generic_105182-11 i86pc i386 i86pc
    eval 'sub O_BITSMINOR () {8;}' unless defined(&O_BITSMINOR);
    eval 'sub O_MAXMAJ () {0x7f;}' unless defined(&O_MAXMAJ);
    eval 'sub O_MAXMIN () {0xff;}' unless defined(&O_MAXMIN);
	eval 'sub major {
	    local($x) = @_;
	    eval "((($x) >>  &O_BITSMINOR)   &O_MAXMAJ)";
	}' unless defined(&major);
	eval 'sub minor {
	    local($x) = @_;
	    eval "(($x)   &O_MAXMIN)";
	}' unless defined(&minor);
} else {
	die "How do you calculate major and minor device numbers on $uname?\n";
}

sub ignorance { $ignored{$state}++; }

sub unmunge {
	my($addr) = @_;
	$addr =~ s/_FNORD_/ /g;
	# remove (Real Name)
	$addr =~ s/^(.*)\([^\)]*\)(.*)$/$1$2/
		if $addr =~ /^.*\([^\)]*\).*$/;
	# extract <user@host> if it appears
	$addr =~ s/^.*<([^>]*)>.*$/$1/
		if $addr =~ /^.*<[^>]*>.*$/;
	# strip leading, trailing blanks
	$addr =~ s/^\s*(.*)\s*/$1/;
	# nuke local domain
    # FIXME: Add a regular expression for your local domain here.
	$addr =~
	 s/@(cory|po|pasteur|torus|parker|cochise|franklin).(ee)?cs.berkeley.edu//i;
	return $addr;
}

print STDERR "$0: running on $hostname ($uname)\n";

open(INPUT,$ARGV[0]) || die "$ARGV[0]: $!\n";

sub working { 
	my($now);
	$now = localtime;
	print STDERR "$myname: Working... $now\n";
}

&working();

while (! eof INPUT) {
	# get a new line
	if ($state eq "IN_MESSAGE_HEADER") {
		# handle multi-line headers
		if ($nrl ne "" || $getnrl != 0) {
			$_ = $nrl;
			$getnrl = 0;
			$nrl = "";
		} else {
			$_ = <INPUT>; $lineno++;
		}
		unless ($_ =~ /^\s*$/) {
			while ($nrl eq "") {
				$nrl = <INPUT>; $lineno++;
				if ($nrl =~ /^\s+[^\s].*$/) { # continuation line
					chomp($_);
					$_ .= "_FNORD_" . $nrl;
					$nrl = "";
				} elsif ($nrl =~ /^\s*$/) { # end of headers
					$getnrl++;
					last;
				}
			}
		}
	} else {
		# normal single line
		if ($nrl ne "") {
			$_ = $nrl; $nrl = "";
		} else {
			$_ = <INPUT>; $lineno++;
		}
	}

	if ($state eq "WAIT_FOR_FROM") {
		if (/^From \S+.*$/) {
			$state = "MSG_START";
		} else {
			&ignorance();
		}
	} elsif ($state eq "MSG_START") {
		if (/^\s+boundary=\"([^\"]*)\".*$/) {
			$boundary = $1;
			$state = "GOT_BOUNDARY";
			$ctr++;
		} else {
			&ignorance();
		}
	} elsif ($state eq "GOT_BOUNDARY") {
		if (/^--$boundary/) {
			$next = <INPUT>; $lineno++;
			if ($next =~ /^Content-Type: message\/rfc822/) {
				$hour = (localtime)[2];
				$char = chr(ord("A") + $hour);
				$ident = sprintf("%sAA%05d",$char,99999 - $ctr);
				$qf = "qf$ident";
				$df = "df$ident";
				@rcpt = ();
				open(MSGHDR,">$qf") || die "Can't write to $qf: $!\n";
				open(MSGBODY,">$df") || die "Can't write to $df: $!\n";
				chmod(0600, $qf, $df);
				$state = "IN_MESSAGE_HEADER";
				$header = $body = "";
				$messageid = "bounce-resender-$ctr";
				$fromline = "MAILER-DAEMON";
				$ctencod = "7BIT";
				# skip a bit, brother maynard (boundary is separated from
				#  the header by a blank line)
				$next = <INPUT>; $lineno++;
				unless ($next =~ /^\s*$/) {
					print MSGHDR $next;
				}
			}
		} else {
			&ignorance();
		}

		$next = $char = $hour = undef;
	} elsif ($state eq "IN_MESSAGE_HEADER") {
		if (!(/^--$boundary/ || /^\s*$/)) {
			if (/^Message-[iI][dD]:\s+<([^@]+)@[^>]*>.*$/) {
				$messageid = $1;
			} elsif (/^From:\s+(.*)$/) {
				$fromline = $sender = $1;
				$fromline = unmunge($fromline);
			} elsif (/^Content-[Tt]ransfer-[Ee]ncoding:\s+(.*)$/) {
				$ctencod = $1;
			} elsif (/^(To|[Cc][Cc]):\s+(.*)$/) {
				$toaddrs = $2;
				foreach $toaddr (split(/,/,$toaddrs)) {
					$toaddr = unmunge($toaddr);
					push(@rcpt,$toaddr);
				}
			}
			$headerline = $_; 
			# escape special chars
			# (Perhaps not. It doesn't seem to be necessary (yet)).
            #$headerline =~ s/([\(\)<>@,;:\\".\[\]])/\\$1/g;
			# purely heuristic ;-)
            $headerline =~ s/Return-Path:/?P?Return-Path:/g;
			# save H-line to write to qf, later
			$header .= "H$headerline";

			$headerline = $toaddr = $toaddrs = undef;
		} elsif (/^\s*$/) {
			# write to qf
			($dev, $ino) = (stat($df))[0 .. 1];
			($maj, $min) = (major($dev), minor($dev));
			$time = time();
			print MSGHDR "V2\n";
			print MSGHDR "B$ctencod\n";
			print MSGHDR "S$sender\n";
			print MSGHDR "I$maj/$min/$ino\n";
			print MSGHDR "K$time\n";
			print MSGHDR "T$time\n";
			print MSGHDR "D$df\n";
			print MSGHDR "N1\n";
			print MSGHDR "MDeferred: manually-requeued bounced message\n";
			foreach $r (@rcpt) {
				print MSGHDR "RP:$r\n";
			}
			$header =~ s/_FNORD_/\n/g;
			print MSGHDR $header;
			print MSGHDR "HMessage-ID: <$messageid@$hostname>\n"
				if ($messageid =~ /bounce-resender/);
			print MSGHDR ".\n";
			close MSGHDR;

			# jump to state waiting for message body
			$state = "IN_MESSAGE_BODY";

			$dev = $ino = $maj = $min = $r = $time = undef;
		} elsif (/^--$boundary/) {
			# signal an error
			print "$myname: Header without message! Line $lineno qf $qf\n";

			# write to qf anyway (SAME AS ABOVE, SHOULD BE A PROCEDURE)
			($dev, $ino) = (stat($df))[0 .. 1];
			($maj, $min) = (major($dev), minor($dev));
			$time = time();
			print MSGHDR "V2\n";
			print MSGHDR "B$ctencod\n";
			print MSGHDR "S$sender\n";
			print MSGHDR "I$maj/$min/$ino\n";
			print MSGHDR "K$time\n";
			print MSGHDR "T$time\n";
			print MSGHDR "D$df\n";
			print MSGHDR "N1\n";
			print MSGHDR "MDeferred: manually-requeued bounced message\n";
			foreach $r (@rcpt) {
				print MSGHDR "RP:$r\n";
			}
			$header =~ s/_FNORD_/\n/g;
			print MSGHDR $header;
			print MSGHDR "HMessage-ID: <$messageid@$hostname>\n"
				if ($messageid =~ /bounce-resender/);
			print MSGHDR ".\n";
			close MSGHDR;

			# jump to state waiting for next bounce message
			$state = "WAIT_FOR_FROM";

			$dev = $ino = $maj = $min = $r = $time = undef;
		} else {
			# never got here
			&ignorance();
		}
	} elsif ($state eq "IN_MESSAGE_BODY") {
		if (/^--$boundary/) {
			print MSGBODY $body;
			close MSGBODY;
			$state = "WAIT_FOR_FROM";
		} else {
			$body .= $_;
		}
	}
	if ($lineno % 1900 == 0) { &working(); }
}

close INPUT;

foreach $x (keys %ignored) {
	print STDERR
		"$myname: ignored $ignored{$x} lines of bounce spool in state $x\n";
}
print STDERR
	"$myname: processed $lineno lines of input and wrote $ctr messages\n";
print STDERR
	"$myname: remember to chown the queue files to root before running:\n";
chomp($pwd = `pwd`);
print STDERR "$myname:      # sendmail -q -oQ$pwd -oT99d &\n";

print STDERR "$myname: to test the newly generated queue:\n";
print STDERR "$myname:      # sendmail -bp -oQ$pwd | more\n";

exit 0;