[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

The technical explanation of "::" incoming header pasting

There's a new feature in the remailing software.

Some people can't add arbitrary header fields because of mailer or
gateway restrictions.  This restricts them from using the remailer.  I
have added a facility to allow new header fields to be pasted onto the
end of a header when the mail arrives.  This effectively happens
before processing by the remailer software.  These new fields exist
during transit in the message body, where they remain untouched.  Only
after the message is delivered to my account does this operator
take effect.

Syntax: If the first line of the body is the two characters "::", then
the following lines are appended to the header, up to the next blank

Here's how it works.

First of all, here's my new .maildelivery file:

------- cut here -------
# field			pattern	action/	string 
#				result	(quote included spaces)
Request-Remailing-To	""	pipe R	"perl remailer/remail.perl" 
Request-Remailing-To	""	file R	remailer/archive
*			""	pipe R	"/usr/local/lib/mh/rcvtty -biff"
*			""	pipe ?	"perl remailer/incoming.header.perl"
------- cut here -------

Comments are indicated by #.  The Request-Remailing-To lines have been
there.  The second of the makes an archive for debugging purposes.  It
will go eventually.  The third field, "*", indicates all fields, it
runs 'rcvtty' on my mail; this replaces the function of biff, since
mail is getting piped to slocal now, disabling biff.

The last line is the important one.  It says "If the mail hasn't been
delivered by now, run the incoming header rewrite script on it.  If
that doesn't work, continue trying to deliver it."

Now here's the trick.  slocal has no way of taking the output of the
rewrite and continuing to process it.  (It should.  It would make this
whole job easy.)  So in order to continue processing, you need to
redeliver the mail.  You could invoke sendmail and mail it back to
yourself, but that would mangle the existing header.  So the thing to
do is to recursively invoke slocal from within the perl script.

Here's the perl script to do all this:

------- cut here -------
  # First read in the whole header.
  # We check for the Second-Pass: line to detect infinite loops.

while (<>) {
	last if /^$/ ;
	exit 1 if /^Second-Pass:/ ;
	$header .= $_ ;

  # We have just read the last line in the header.
  # Now we check to see if there is a pasting operator.

if ( ( $_ = <> ) && /^::$/ ) {
	while (<>) {
		last if /^$/ ;
		$header .= $_ ;
} else {
	# There is either an empty body or no pasting operator
	#   Thus exit with a return code of 1 to indicate that
	#   the mail has not been delivered.
		exit 1 ;

# There was a header pasting operator.
#   So we open 'slocal' as a pipe, effectively redelivering the mail
#   back to ourselves.

#open( OUTPUT, ">foo" ) ;
open( OUTPUT, "| /usr/local/lib/mh/slocal -user hughes" ) ;
select( OUTPUT ) ;

# print a "From " line to satisfy slocal

@weekdays = ( "Sun","Mon","Tue","Wed","Thu","Fri", "Sat" ) ;
@months = ( "Jan","Feb","Mar","Apr","May",
	"Jun","Jul","Aug","Sep","Oct","Nov","Dec" ) ;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime ;
printf "From hughes  %s %s ", @weekdays[ $wday ], @months[ $mon ] ;
printf "%2d %02d:%02d:%02d 19%d\n", $mday, $hour, $min, $sec, $year ;

# Now just print out the message

print $header ;
print "Second-Pass:\n" ;
print "\n" ; 
while (<>) {
} continue {
	print ;

------- cut here -------

Here's how the perl script works.

The first loop reads lines from the existing header.  When it sees a
blank line (regexp /^$/) it terminates the loop.  If it sees a field
"Second-Pass", it knows it has filtered this message before and exits
with a return code indicating that the mail has not been delivered.
The variable $header is appended with the current header line.
$header contains the whole header when the loop terminates.

Properly speaking, the Second-Pass test is not necessary to detect
infinite loops.  Since the pasting operator gets removed during the
rewrite, the script won't return an exit status of 0 more times than
the pasting operator appears.  But should something get screwed up,
such as a different module adding pasting commands (how? I don't know),
the Second-Pass test should prevent infinite recursion.

The next statement reads another line from the input file.  This line
is the first line of the message body.  If this line is the pasting
operator, then header lines are accumulated in $header as before until
a blank line.  The difference is that these header lines are being
read from the body of the message.  If there is no pasting operator,
the script exits undelivered.

At this point we now have to redeliver the message back to ourselves.
We first open slocal as the output pipe.

The next section is a kludge.  It turns out that slocal strips off the
out-of-band "From " (no colon) line that the mail delivery system
uses.  In other words, the message which slocal pipes into its pipes
is not identical to the message it itself received.  This means that
slocal cannot be directly recursed.  What this section does is to
create a "From " line to make slocal happy.  It calls localtime() and
then formats those numbers into the proper form.

It turns out that slocal will deliver this mail without the "From "
line, even to /usr/spool/mail, but it doesn't do so properly.  On my
system, in added some delimiters which I think I've tracked down to
the 'mtstailor' file, namely mmdelivery1 and mmdelivery2.  Since these
are not null on my system, there's some garbage added which screws up
separation of the spool file into messages.  Adding a "From " line
fixes that.  This misbehavior may not be so surprising, considering
that slocal was "meant" to be invoked only in a .forward file.

Now we print the variable $header which contains the whole header,
including newlines.  Using a single string removes the need for an
array.  We added the Second-Pass line and a blank line for the end of
the header.  The final loop prints out the rest of the message body.

There is another way to proceed to get the same functionality.  One
could write a filter to translate the first occurrence only of
\n\n::\n into \n. We could then pass the message through this filter
before slocal saw it.  And for now, that would do the same thing.

But suppose we want more that one rewrite rule active?  Then you would
only be able to apply each rewrite rule exactly once in fixed order.
You want to be able to rewrite a message and then apply all the
rewrite rules again.  

At least one other rewrite rule is planned: automatic decryption.
Since decrypting a message will completely change the body, and since
some of the header fields may need to be hidden, you have to be able
to decrypt the body and then paste on header lines.  But since you
need to indicate an encrypted body by a header line (well, not really,
but it's more reliable), and since some people can't add these header
lines, you need to paste lines before encryption as well.

Thus the rewrite rules need to be applied asyncronously and hence I'm
using a fairly complex slocal scheme to do a simple filter.
Eventually I hope to write an equivalent to slocal which knows about
message rewrites and simple filters, but that's for later.