being more flexible than FEATURE(compat_check)

A user at ServerFault asked how to restrict a user to send mail only to local addresses. Normally in sendmail, user / sender filtering decisions are done using FEATURE(compat_check), but while it does provide flexibility on deciding on specific pairs which are entries in /etc/mail/access, for more flexible stuff you have to write your own version of the check_compat rule set.

check_compat‘s workspace is a string that contains the addresses given in the MAIL FROM: and RCPT TO: SMTP dialog, separated by a $|. Whenever one works with addreses in sendmail, one has to canonify them, but since whatever rule set is called within another rule set always takes one argument (workspace) we have to use macros to store the canonified addresses before proceeding to any pattern matching. So first we have to declare the macros in our sendmail.mc:

LOCAL_CONFIG
Kput macro
D{put1}empty1
D{put2}empty2

The above snippet has declared a map (named put) and two macros that we will use to store the canonified addresses (named put1 and put2) initialized to some non empty bogus value. Since the workspace for check_compat is in the form sender address $| recipient address, we canonify the recipient address first:

Scheck_compat
R$* $| $*               $: $1 $| $>canonify $2
R$* $| $*               $: $(put {put2} $@ $2 $) $1

Up to here the rule set puts the canonified mail address for the recipient in ${put2} and returns the sender address (the last $1 in the second line) for further processing. Therefore we are now ready to repeat the process and store the canonified sender address in ${put1}:

R$*             $: $>canonify $1
R$*             $: $(put {put1} $@ $1 $)

Macro operations return an empty string so now we have to retrieve the addresses from the macros and reconstruct a canonified workspace for any further processing:

R$*             $: $&{put1} $| $&{put2}

This results in the workspace now being in the canonified form of:

sender < @ sender . domain . > $| recipient < @ recipient . domain . >

regardless of the multitude of ways one can express an email address in. This is why we need canonification in the first place: There are many ways one can enter an address in MAIL FROM: and RCPT TO: and canonification returns an address in a single format that all the other rule sets can work with.

Now if someone wants to restrict where a user sends mail based on MAIL FROM: and the recipient domain, one can add the following lines in check_compat:

# Now we can filter on sender and recipient
Ruser < @ $=w . > $| $* < $=w . >        $#OK
Ruser < @ $=w . > $| $*                  $#discard $: $2

The above silently discards email not directed to the local domains (Class $=w). If you want to test your rule sets (sendmail -bt) you have to keep in mind that sendmail’s test mode interprets $| as two characters, so you have to use a “translate hack”:

LOCAL_RULESETS
STranslate
R$* $$| $*    $: $1 $| $2

Now you can check check_compat by typing:

# sendmail -bt
> Translate,check_compat sender@address,recipient@address

and watch what happens. As always keep in mind that in sendmail.mc the left hand side of the rules is separated from the right hand side with tabs, not spaces. So do not copy-paste. Type the code instead. Next you need to compile your sendmail.cf and restart sendmail. In Debian as root run sendmailconfig to do this.

My eyes hurt! Can it be done another way?

Of course! You can install MIMEDefang together with sendmail and modify filter_recipient to your liking. Depending your operating system / distribution you have to check whether you need to enable filter_recipient or not. In Debian you have to edit /etc/default/mimedefang and restart the MIMEDefang daemon. After enabling it, you need to add in /etc/mail/mimedefang-filter your version for filter_recipient:

sub filter_recipient {
  my ($recipient, $sender, $ip, $hostname, $first, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr) = @_;

  $sender =~ s/^\<//;
  $sender =~ s/\>$//;
  $sender = lc $sender;
  
  $recipient=~ s/^\<//;
  $recipient=~ s/\>$//;
  $recipient = lc $recipient;

  # Put your conditions here
  ...

  return('CONTINUE', "ok");
}

You need to reload mimedefang-filter after editing this, so as root run (in Debian) /etc/init.d/mimedefang reload and check your logfiles for any errors.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s