Inside sendmail is the often overlooked checkcompat() routine. It has existed since Version 3 and is intended to allow the site administrator to accept, reject, and log mail delivery attempts. Among its uses, and those we will illustrate, are the following:
Refuse to act as a mail gateway. Reject any mail that is both from and to any outside address.
Limit the size of guest messages. Screen messages based on user uid, and reject those for the class of users who are guests when the message is over a specified limit in bytes.
Verify identd(8) information. Compare the host information
in $s
to that in $_
and log a warning if they
differ.
Prune Received:
headers at a firewall. Make all outgoing
mail appear fresh as it exits the internal network via a firewall.
Reject mail from spamming or mail-bombing sites. Look up the sender in an external database and bounce the message if the sender is listed as "bad."
The checkcompat() routine is located in the file src/conf.c. That file contains comments describing one way to code checkcompat(). In this chapter we show you other ways to code it.
The checkcompat() routine is inherently "internal," meaning that it must understand internal data structures that may change. [1] Since you are modifying source code, you have to be prepared to read source code. In this chapter we offer examples of ways to use checkcompat(). Be aware that they are examples only, and you will need C programming skill to extend them to real-world situations.
[1] V8.8 sendmail also offers a
check_compat
rule set (see Section 29.10.4, "The check_compat Rule Set") that can perform some of the checkcompat() routine's functionality at the rule set level. This is one way to avoid having to program in the C language.
The checkcompat() routine is called for every delivery attempt to every recipient. When designing a checkcompat() routine of your own, be aware that a cascade of errors may propagate if you are not careful with your design. Logging a warning based on the sender, for example, may result in multiple warnings when there are multiple recipients.
When sendmail prepares to deliver mail, it first checks the
size of the mail message and rejects (bounces) it if it is
larger than the limit imposed by the M=
delivery agent equate (see Section 30.4.7, M=).
V8.8 sendmail then calls the check_compat
rule set
(see Section 29.10.4).
Next, all versions of sendmail call the checkcompat()
routine.
The checkcompat() routine lies in a unique position within the sendmail code. It is the one place where both the sender and recipient addresses are available at the same time. Since it precedes actual delivery, all the information needed for delivery is available to you for checking.
If checkcompat() returns EX_OK, as defined in <sysexits.h>, the mail message is considered okay and delivered. Otherwise, the message is bounced. If you wish the message to be requeued instead of bounced, you can return EX_TEMPFAIL.
Again note that the checkcompat() routine is called once for each recipient.
The checkcompat() is found in the C language source file src/conf.c. Inside that file you will find it declared like this:
checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e;
Here, to
is a pointer to a structure of typedef
ADDRESS that contains information about the recipient.
And e
is a pointer to a structure
(actually a linked list of structures) of typedef
ENVELOPE that contains information about the current envelope.
The members of the ADDRESS *to
structure are shown in
Table 20.1.
Note that these members are correct for V8.8 sendmail only.
Also note that the table shows only those members that may be
useful in a checkcompat() routine (see sendmail.h
for the other members of *to
).
Type | Member | Description |
---|---|---|
char * | q_paddr | The address in a form suitable for printing |
char * | q_user | The user part ($: ) from rule set 0 (see Section 29.6, "Rule Set 0") |
char * | q_ruser | The login name for this user, if known |
char * | q_host | The host part ($@ ) from rule set 0 (see Section 29.6) |
struct mailer * | q_mailer | The delivery agent ($# ) from rule set 0 (see Section 29.6) |
u_long | q_flags | Status flags (see Section 37.3.1, "The Output Produced by printaddr()" in Section 37.3.1) |
uid_t | q_uid | The uid of the q_ruser , if known |
gid_t | q_gid | The gid of the q_ruser , if known |
char * | q_home | The home directory (path), if delivery is local |
char * | q_fullname | The (gecos) full name of q_ruser , if known |
struct address * | q_next | Link to the next ADDRESS in the chain |
struct address * | q_alias | The alias that yielded this address |
char * | q_owner | The owner of q_alias |
The members of the ENVELOPE *e
structure are shown in
Table 20.2.
Note that these members are correct for V8.8 sendmail only.
Also note that the table shows only those members that may be
useful in a checkcompat() routine (see sendmail.h
for other members of *e
).
Type | Member | Description |
---|---|---|
HDR * | e_header | Linked list of headers |
time_t | e_ctime | Time message first placed into the queue |
ADDRESS | e_from | The sender |
ADDRESS * | e_sendqueue | Linked list of recipients |
long | e_msgsize | Size of the message in bytes |
long | e_flags | Envelope flags (see Table 37.3 in Section 37.5.12, -d2.1) |
int | e_nrcpts | Number of recipients |
short | e_hopcount | The hop count for the message |
The checkcompat() routine is a powerful internal hook inside sendmail. It is so internal and powerful, in fact, that if you are truly clever, you can even use checkcompat() to modify rewrite rules at runtime (scary but possible).
Over 100 global variables are used by V8.8 sendmail. They are all listed in sendmail.h and conf.c with "lite" comments. Global variables store information such as sendmail's option values, file descriptor values, macro values, class lists, and database access information. Any can be modified inside checkcompat(), but before attempting to do so, study the sendmail C source code to anticipate any unexpected side effects.
In general, you can use almost any of the global variables when designing your own checkcompat() routine. The four most interesting are the following:
RealHostAddr
The IP address of the sending host. This is a union of several sockaddr_ types depending on your selection of protocol type. This can be zero for locally submitted mail.
RealHostName
A string containing the definitive canonical name of the sending host. If it can't be resolved to a name, it will contain the host's IP number in text form, surrounded by square brackets.
LogLevel
This variable determines the amount of logging that sendmail does.
It is initially set with the LogLevel
(L
) option (see Section 34.8.33, LogLevel (L)).
You might want to use checkcompat() to detect questionable
connections and, if any are detected, to increase the value in LogLevel
to 12. This will cause
both sides of every subsequent SMTP connection to be logged.
MatchGecos
Whether or not unmatched local looking names are looked up in the passwd(5)
file is under the control of the MatchGECOS
(G
) option
(see Section 34.8.34, MatchGECOS (G)). Because this kind of lookup is expensive, you
might wish to enable it only during nonbusiness hours. One way to do this
would be by modifying the MatchGecos
variable inside checkcompat().