graymilter with DNS based whitelists support – part 2

In part 1 I showed how one can use tcp_wrappers to patch graymilter in order to support dns based whitelists (as opposed to IP based whitelists that it supports by default).

But what if you need something faster than having graymilter open /etc/hosts.allow for every message passed to it in order to decide whether the sending host is whitelisted or not?

Basically you need a strcmp(3) function with a twist. While the strcmp(3) family of functions compare from left to right, you need the opposite:

#ifndef _RIGHT_STRCMP_C_
#define _RIGHT_STRCMP_C_ 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Think of this as a rightmost strcmp() */

static int
right_strcmp(char *x, char *y)
{
int xl, yl, l;
char *s;

        xl = strlen(x);
        yl = strlen(y);
        l = xl - yl;

        if (l < 0) {
                return l;
        } else {
                s = x + l;
                return (strncmp(s, y, yl));
        }
}

#endif /* _RIGHT_STRCMP_C_ */

Now you can include the file right_strcmp.c at the top of graymilter.c (remember right_strcmp() is declared as a static function). If you are using graymilter version 1.26 you then scroll down at line 680 and add this snippet of code:

$ diff graymilter.c graymilter.c-
76d75
< #include "right_strcmp.c"
680,692d678
<     if (right_strcmp(connhost, ".example.com") == 0) {
<           syslog(LOG_INFO, "accepting host %s", connhost);
<           return SMFIS_ACCEPT;
<     }
<     if (right_strcmp(connhost, ".example.org") == 0) {
<           syslog(LOG_INFO, "accepting host %s", connhost);
<           return SMFIS_ACCEPT;
<     }
<     if (right_strcmp(connhost, ".example.net") == 0) {
<           syslog(LOG_INFO, "accepting host %s", connhost);
<           return SMFIS_ACCEPT;
<     }
<

Yes, the above example hardcodes the domains into the graymilter executable. One is free to play around and write a simple function that reads a domain list from a file and wraps around right_strcmp() with the help of stdarg(3). That way you only have to write one if statement. This is left as an exercise to the reader.

(part 1) (part 3)

3 thoughts on “graymilter with DNS based whitelists support – part 2

  1. How much of a speed difference does right_strcmp() effect though? In machine language, two calls of strlen() and *then* a series of byte/character comparisons are likely to be *much* slower than an strcmp() call.

    If the callers of right_strcmp() already know how long the strings they pass are (i.e. because they keep around a ‘len’ for each string, to make appending easier), it would probably be faster to skip the strlen() calls and use a right_strcmp() function with a prototype of:

    int right_strcmp(const char *s1, size_t len1,
    const char *s2, size_t len2);

    This may be slightly faster, because it won’t have to call strlen() for both argument strings *and* compare each character starting from the end of the strings.

  2. @keramida:
    The above right_strcmp() is a slow one. However, for my needs email is network bound and not CPU bound (we are not talking of a right_strcmp() implementation for an embedded system).

    right_strcmp() operates on a small string (connhost) containing the connecting host’s hostname (RFC1035 states that it cannot be longer than 255 bytes) and a “mask”. Unfortunately connhost’s length is not computed elsewhere in graymilter.c.

    However, with the prototype function that you propose, for the needs of this graymilter patch calls to strlen() can be reduced to one instead of two prior to calling (your version of) right_strcmp().

    *and* compare each character starting from the end of the strings.

    I was thinking of timing the above right_strcmp() with an implementation that reverses the strings and str(n)cmp()’s them.

    However, you have to wait for part 3 to see why I abandoned any further work on right_strcmp() with graymilter.
    :)

Leave a comment