12.3 Write a Delivery Agent Script
The program that is driven by the prog delivery
agent can be a compiled executable binary, a shell script, or even a
perl(1) script. The limitation on the kind of
program that can be run is made by the sh(1)
shell (if sh -c is used in the
A=) or by execve(2) (if it is
launched directly from the P=). You need to read
the manuals on your system to determine your limitations. For
example, not all versions of sh(1) allow
constructs such as the following in scripts:
#!/usr/local/bin/perl
When this appears as the first line of a script, the
#! tells sh(1) or
execve(2) to run the program whose pathname
follows, to execute the commands in the script.
In writing a program for mail delivery using the
prog delivery agent, some unexpected problems can
arise. We will illustrate, using fragments from a Bourne shell
script.
12.3.1 Duplicates Discarded
When sendmail gathers its list of recipients, it
views a program to run as just another recipient. Before performing
any delivery, it sorts the list of recipients and discards any
duplicates. Ordinarily, this is just the behavior that is desired,
but discarding duplicate programs from the
aliases(5) file can cause some users to
lose mail. To illustrate, consider a program that notifies the system
administrator that mail has arrived for a retired user:
#!/bin/sh
/usr/ucb/mail -s gone postmaster
This script reads everything (the mail message) from its standard
input and feeds what it reads to the
/usr/ucb/mail program. The command-line
arguments to mail are a subject line of
gone and a recipient of
postmaster. Now consider two aliases that use this
program:
george: "|/usr/local/bin/gone"
ben: "|/usr/local/bin/gone"
When mail is sent to both george and
ben, sendmail aliases each to
the program |/usr/local/bin/gone. But because both
of the addresses are identical, sendmail
discards one.
To avoid this problem, design all delivery programs to require at
least one unique argument. For example, the previous program should
be rewritten to require the user's name as an
argument:
#!/bin/sh
if [ ${#} -ne 2 ]; then
echo $0 needs a username.
exit
fi
/usr/ucb/mail -s "$1 gone" postmaster
By requiring a username as an argument, the once-faulty aliases are
made unique:
george: "|/usr/local/bin/gone george"
ben: "|/usr/local/bin/gone ben"
Although the program paths are still the same, the addresses (name
and arguments together) are different, and neither is discarded.
12.3.2 Correct exit(2) Values
The sendmail program expects its
A= programs to exit with reasonable
exit(2) values. The values that it expects are
listed in <sysexits.h>. Exiting with
unexpected values causes sendmail to bounce mail
and gives an unclear message:
554 5.0.0 Unknown status val
Here, val is the unexpected error value.
To illustrate, consider the following rewrite of the previous script:
#!/bin/sh
EX_OK=0 # From <sysexits.h>
EX_USAGE=64 # From <sysexits.h>
if [ ${#} -ne 2 ]; then
echo $0 needs a username.
exit $EX_USAGE
fi
/usr/ucb/mail -s "$1 gone" postmaster
exit $EX_OK
Here, if the argument count is wrong, we exit with the value
EX_USAGE, thus producing a clearer (two-line) error message:
/usr/local/bin/gone needs a username.
/usr/local/bin/gone... Bad usage.
If all goes well, we then exit with EX_OK so that
sendmail knows the mail was successfully
delivered.
12.3.3 Is It Really EX_OK?
When sendmail sees that the
A= program exited with EX_OK, it assumes that the
mail message was successfully delivered. It is vital for programs
that deliver mail to exit with EX_OK only if delivery was 100%
successful. Failure to take precautions to detect every possible
error can result in lost mail and angry users. To illustrate,
consider the following common C-language statement:
(void)fclose(fp);
If the file that is being written to is remotely mounted, the written
data can be cached locally. All the preceding write statements will
have succeeded, but if the remote host crashes after the last write
(but before the close), some of the data can be lost. The
fclose(3) fails, but the
(void) prevents detection of that failure.
Even in writing small shell scripts, it is important to include error
checking. The following rewrite of our gone
program includes error checking but does not handle signals. We leave
that as an exercise for the reader:
#!/bin/sh
EX_OK=0 # From <sysexits.h>
EX_USAGE=64 # From <sysexits.h>
EX_TEMPFAIL=75 # From <sysexits.h>
if [ ${#} -ne 2 ]; then
echo $0 needs a username.
exit $EX_USAGE
fi
if /usr/ucb/mail -s "$1 gone" postmaster >/dev/null 2>&1
then
exit $EX_OK
fi
exit $EX_TEMPFAIL
Note that by using EX_TEMPFAIL we cause the message to be requeued if
this script fails. That way, a bug in the script can be fixed, and
the next queue run will succeed.
|