
|
Chapter 20 The checkcompat() Cookbook
|
|
In this section we show several examples of possible uses for the
checkcompat
() routine. Among those we illustrate are the following:
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:
# 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]
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]
The source file for the database will look like this:
user@spam.host spam
user@bomb.host 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.
|