In this section we show several examples of possible uses for the checkcompat () routine. Among those we illustrate are the following:
Accept mail only from our domain. (see Section 20.2.1, "Accept Mail Only From Our Domain" ).
Cause your workstation to refuse to work as a mail gateway (see Section 20.2.2, "Workstation Refuses to Act as a Mail Gateway" ).
Limit the size of guest account messages (see Section 20.2.3, "Limit the Size of Guest Messages" ).
Verify that identd information is correct (see Section 20.2.4, "Verify identd Information" ).
Prune
Received:
headers at a firewall (see
Section 20.2.5, "Prune Received: Headers at Firewall"
).
Reject mail from spamming or mail-bombing sites (see Section 20.2.6, "Reject Mail from Spamming or Mail-bombing Sites" ).
Note that in all of the following examples the numbers to the left indicate line numbers for discussion and are not a part of the code.
If your site lives behind a firewall , [2] you might want to use checkcompat () to configure the internal sendmail so that it accepts only mail that is generated locally. The external sendmail (outside the firewall or part of it) acts as a proxy. That is, it accepts external mail that is destined for internal delivery from the outside and forwards it to the internal sendmail . Because the external sendmail is part of the local domain, its envelope always appears to be local. Any external mail that somehow bypasses the firewall needs to be bounced. The way to do this in checkcompat () looks like this:
[2] A firewall is a machine that lies between the local network and the outside world. It intercepts and filters all network traffic and rejects any that are considered inappropriate.
# define OUR_NET_IN_HEX 0x7b2d4300 /* 123.45.67.0 in hex */ # define OUR_NETMASK 0xffffff00 checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { if (tTd(49, 1)) printf("checkcompat(to=%s, from=%s)\n", to->q_paddr, e->e_from.q_paddr); if (RealHostAddr.sa.sa_family == 0) { /* this is a locally submitted message */ return EX_OK; } if (RealHostAddr.sa.sa_family != AF_INET || (RealHostAddr.sin.sin_addr.s_addr & OUR_NETMASK)!= OUR_NET_IN_HEX) {usrerr("553 End run mail not allowed");
e->e_flags |= EF_NO_BODY_RETN;
to->q_status = "5.7.1";
return (EX_UNAVAILABLE);
} return (EX_OK); }
The
usrerr
() routine (line
21
) causes a warning to be printed at the sending site, and returning EX_UNAVAILABLE (line
24
) causes the mail message to be bounced. Bounced mail is sent back to the originating sender. A copy may also be sent to the local postmaster depending on the setting of
PostmasterCopy
(
P
) option (see
Section 34.8.46, PostmasterCopy (P)
).
The EF_NO_BODY_RETN (line 22 ) causes only the headers from the message to be returned in bounced mail, not the original message body. Other envelope flags of interest can be found in Table 37.3 of Section 37.5.12 .
The
to->q_status
(line
23
) conveys the DSN error status in the bounced mail message (see RFC1893). Here,
5.7.1
indicates a permanent failure (
5
) of policy status (
7
), where delivery is not authorized and the message is refused (
1
).
Also note that this code sample is only a suggestion. It doesn't take into account that
RealHostAddr
may contain
0x7f000001
(
127.0.0.1
for
localhost
).
If you've spent many months getting your workstation set up and running perfectly, you might not want outsiders using it as a knowledgeable mail relay. One way to prevent such unwanted use is to set up checkcompat () in conf.c so that it rejects any mail from outside your machine that is destined to another site outside your machine. A desirable side effect is that this will also prevent outsiders from directly posting into your internal mailing lists.
checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { if (tTd(49, 1)) printf("checkcompat(to=%s, from=%s)\n", to->q_paddr, e->e_from.q_paddr); if (RealHostAddr.sa.sa_family == 0) { /* this is a locally submitted message */ return (EX_OK); } /* only accept local delivery from outside */if (!bitnset(M_LOCALMAILER, to->q_mailer->m_flags))
{ usrerr("553 External gateway use prohibited"); e->e_flags |= EF_NO_BODY_RETN; to->q_status = "5.7.1"; return (EX_UNAVAILABLE); } return (EX_OK); }
Although
to
(line
16
) is really a linked list of recipients, we check only the current recipient to prevent spurious warnings. This is done because
checkcompat
() is called once for
every
recipient. The check in line
16
is to see whether
F=l
delivery agent flag is not set (see
Section 30.8.28, F=l (lowercase L)
) thus implying that the recipient is not local.
Note that this form of rejecting messages will not work on a mail hub. In that case more sophisticated checks need to be made. Among them are the following:
Check all the IP domains for your site. If you have only one, the check in
Section 20.2.1
will work. If you have several (as in an assortment of class
C
domains), the check will be more complex. If the connecting host is in your domain or one of your domains, you should accept the message.
The envelope sender's host (
e->e_from->q_host
) should be checked to see whether it is in the class
$=w
(see
Section 32.5.8, $=w
). You can use the
wordinclass
() routine (see
Section 20.3.8, wordinclass()
) to look it up. If it is in
$=w
, you should accept the message. This prevents a message from being forwarded through a workstation.
If the delivery agent for a recipient is
*include*
, the message is destined for a mailing list. You might wish to screen further at this point.
Suppose your site has reserved uid s numbered from 900 to 999 for guest users. Because guests are sometimes inconsiderate, you might want to limit the size of their messages and the number of simultaneous recipients they may specify. One way to do this is with the checkcompat () routine:
#define MAXGUESTSIZE 8000 #define MAXGUESTNRCP 4 checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { if (tTd(49, 1)) printf("checkcompat(to=%s, from=%s)\n", to->q_paddr, e->e_from.q_paddr); /* does q_uid contain a valid uid? - no external */if (! bitset(QGOODUID, e->e_from.q_flags))
return (EX_OK);if (e->e_from.q_uid < 900 || e->e_from.q_uid > 999)
return (EX_OK); if (e->e_msgsize > MAXGUESTSIZE) { syslog(LOG_NOTICE, "Guest %s attempted to send %d size", e->e_from.q_user, e->e_msgsize); usrerr("553 Message too large, %d max", MAXGUESTSIZE);e->e_flags |= EF_NO_BODY_RETN;
to->q_status = "5.7.1"; return (EX_UNAVAILABLE); } if (e->e_nrcpts > MAXGUESTNRCP) { syslog(LOG_NOTICE, "Guest %s attempted to send %d recipients", e->e_from.q_user, e->e_nrcpts); usrerr("553 Too many recipients for guest, %d max", MAXGUESTNRCP);e->e_flags &= ~EF_NO_BODY_RETN;
to->q_status = "5.7.1"; return (EX_UNAVAILABLE); } return (EX_OK); }
Note that
q_uid
will have a valid
uid
(QGOODUID will be set) only if the sender is local (line
14
). For external mail coming in, QGOODUID will be clear.
Also note that we specifically do not return the message body (EF_NO_BODY_RETN) if the message was returned because it was too large (line 24 ). But we do return the message body if the message was rejected for too many recipients (line 35 ). Other envelope flags of interest can be found in Table 37.3 of Section 37.5.12 .
When an outside host connects to the local
sendmail
via SMTP, its hostname is saved in the
$s
macro (see
Section 31.10.33, $s
). If the
Timeout.ident
option (see
Section 34.8.70, Timeout (r)
) is nonzero,
sendmail
uses the RFC1413 identification protocol to record the identity of the host at the other end, that is, the identity of the host that made the connection. That identity is recorded in the
$_
macro (see
Section 31.10.1, $-
).
If you are unusually picky about the identity of other hosts, you may wish to confirm that the host in
$s
is the same as the host in
$_
. One way to perform such a check is with the
checkcompat
() routine:
checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { char *s, *u, *v; int len; static char old_s[MAXHOSTNAMELEN]; if (tTd(49, 1)) printf("checkcompat(to=%s, from=%s)\n", to->q_paddr, e->e_from.q_paddr); /* if $s is localhost or in $=w, accept it */ if ((s = macvalue('s', e)) == NULL) return (EX_OK);if (strncasecmp(s, old_s, MAXHOSTNAMELEN-1) == 0)
return (EX_OK); else (void)sprintf(old_s, "%.*s", MAXHOSTNAMELEN-1, s);if (strcasecmp(s, "localhost") == 0)
return (EX_OK);if (wordinclass(s, 'w') == TRUE)
return (EX_OK); if ((u = macvalue('_', e)) == NULL) return (EX_OK); if ((u = strchr(u, '@')) == NULL) return (EX_OK); if ((v = strchr(u, ' ')) != NULL) *v = ' '; len = strlen(u); if (v != NULL) *v = ' ';if (strncasecmp(s, u, len) != 0)
{auth_warning(e, "$s=%s doesn't match $_=%.*s", s, len, u);
} return (EX_OK); }
First (line
16
) we check to see whether we have already checked this value of
$s
. If so, we don't check again because
checkcompat
() is called once for each recipient. If
$s
is new, we save a copy of its value for next time.
Then we make sure that the local host (no matter what its name) is acceptable (lines
20
and
22
). If this is an offsite host, we compare the values of
$s
and the host part of
$_
(line
35
). If they don't match, we insert an
X-Authentication-Warning:
header (line
37
). This keeps such warnings under the control of the
PrivacyOptions.authwarnings
(
p
) option (see
Section 34.8.47, PrivacyOptions (p)
).
In routing mail outward from a firewall (see
Section 20.2.1
), it may be advantageous to replace all the internal
Received:
headers with one master header.
A way to do this with
checkcompat
() looks like this:
# define OUR_NET_IN_HEX 0x7b2d4300 /* 123.45.67.0 in hex */ # define OUR_NETMASK 0xffffff00 # define LOOP_CHECK "X-Loop-Check" checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { HDR *h; int cnt;if (RealHostAddr.sa.sa_family == 0)
{ /* this is a locally submitted message */ return EX_OK; }if (RealHostAddr.sa.sa_family != AF_INET ||
(RealHostAddr.sin.sin_addr.s_addr & OUR_NETMASK) != OUR_NET_IN_HEX)
{ /* not received from the internal network */ return EX_OK; }if (hvalue(LOOP_CHECK, e->e_header) != NULL)
{ /* We've stripped them once already */ return EX_OK; }addheader(LOOP_CHECK, "", &e->e_header);
for (cnt = 0, h = e->e_header; h != NULL; h = h->h_link)
{if (strcasecmp(h->h_field, "received") != 0)
continue;if (cnt++ == 0)
continue;clrbitmap(h->h_mflags);
h->h_flags |= H_ACHECK;
} return (EX_OK); }
Because we are stripping the message of
Received:
headers, we need to be careful. We shouldn't do it if the message originated on the firewall machine (line
12
). We also shouldn't do it if the message originated from outside the internal (firewalled) network (lines
17
and
18
). To prevent possibly disastrous mail loops, we check for a special header (line
23
) and skip stripping again if that header is found. We then add that special header (line
120
), just in case the mail flows though this firewall again.
If it is okay to do so, we scan all the headers (line
30
) looking for all
Received:
headers (line
32
). We skip deleting the first one because it was placed there by the firewall (line
34
). We delete all the others by clearing their
?
flags
?
bits (line
36
) and setting the H_ACHECK flag (line
37
). See
Section 20.3.3,
for a general discussion of this technique.
Be aware that this is only one possible approach and that, depending on what other hosts on the Internet do to the message, this loop detection may break. A safer but more difficult approach is to rewrite the
Received:
headers themselves and to mask out sensitive information in them.
As the Internet grows, your site may become more and more subject to advertising and vengeful attacks from the outside. Advertising attacks are called "spams" and are symptomized by advertisers sending multiple copies of advertisements through your internal mail lists or to several of your users. Vengeful attacks are called "mail bombs" and usually are detected by your mail spool directory filling with a huge number of messages from a single sender. [3]
[3] Often in response to one of your users sending an offensive spam.
To limit your vulnerability to such events (and to others of a similar nature that may be invented in the future), you may screen mail from outside hosts using a combination of a database and checkcompat (). First we show you how to set up such a database, then we show you a checkcompat () routine for using it. [4]
[4] You may also screen sender addresses at the SMTP MAIL command with the new V8.8
check_mail
rule set (see Section 29.10.1, "The check_mail Rule Set" ). Although it can be easier to designcheck_mail
rules, the checkcompat () routine can be more powerful.
The source file for the database will look like this:
[email protected] spam [email protected] bomb
Here, each left-hand side entry is an email address with a user part, an
@
, and a host part. We will be screening on the basis of individual sender addresses rather than screening at a sitewide level. The right-hand side is either the word
spam
to represent a spamming sender or
bomb
to represent a mail-bombing sender.
If the source file is called /etc/mail/blockusers , the database will be created like this:
%makemap hash /etc/mail/blockusers.db < /etc/mail/blockusers
Here, we create a
hash
db
style database. For other available styles, see
Section 33.2, "Create Files with makemap"
.
Once the database is in place, your configuration file needs to be told of its existence. To do that, we use the
K
configuration command (see
Section 33.3, "The K Configuration Command"
):
Kbadusers hash -o /etc/mail/blockusers.db
For the m4 configuration technique you would place this declaration under the LOCAL_CONFIG line in your mc file (see Section 19.6.30, LOCAL-CONFIG ).
One possible checkcompat () routine to handle all this will look like this:
checkcompat(to, e) register ADDRESS *to; register ENVELOPE *e; { STAB *map; char *p; int ret = 0;map = stab("badusers", ST_MAP, ST_FIND);
if (map == (STAB *)NULL)return (EX_OK);
p = (*map->s_map.map_class->map_lookup)
(&map->s_map, e->e_from.q_paddr, NULL, &ret); if (p == NULL)return (EX_OK);
if (strcasecmp(p, "spam") == 0)
{usrerr("553 Spamming mail rejected from %s",
e->e_from.q_paddr); to->q_status = "5.7.1"; return (EX_UNAVAILABLE); }if (strcasecmp(p, "bomb") == 0)
{ usrerr("553 Message rejected from mail-bomber %s", e->e_from.q_paddr); e->e_flags &= ~EF_NO_BODY_RETN; to->q_status = "5.7.1"; return (EX_UNAVAILABLE); } return (EX_OK); }
Here we first look up the database named
badusers
in the symbol table (line
9
). It is okay for the database not to exist (line
11
). If the database exists, we look up the sender's address in it (line
12
). If the address is not found, all is okay (line
15
).
If the address was found in the database, we have a potential bad person. So we first check to see whether the address was marked as a
spam
(line
17
). If it was, we bounce it with an appropriate error message (line
19
).
We also bounce the message if it is a mail bomb (line 24 ). This is fraught with risk however. The bounced mail can fill up the outgoing queue, thereby accomplishing the bomber's ends in a different way. A better approach might be to drop the mail on the floor (see dropenvelope () in envelope.c ), but we leave this as an exercise for the reader.