Hello folks,

Here's a suggested improvement to spamlogd(8) which keeps greytrap entries
tarpitted while they keep trying.

To this end I modified spamlogd.c so that a known greytrapped host is
updated as a greytrap entry in /var/db/spamd on every incoming connection to port 8025. This requires a pf(4) rule that logs incoming connections to this
port.

In spamd(8)'s default mode, also greylisted hosts connect to this port, so we have to look in the database and not interfere with the greylisting process.

In spamd(8)'s blacklist-only mode, this idea could be used to add/update
greytrap entries for all blacklisted hosts (so also those from spamd.conf(5)). However, these blacklists often contain false positives and legitimate hosts that are blacklisted for a short period, so this is probably not a good idea.

Thoughts?


$ diff -u spamlogd.c{.54,}
--- spamlogd.c.54       Wed Aug 21 18:13:30 2013
+++ spamlogd.c  Thu Aug 29 13:30:58 2013
@@ -21,7 +21,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */

-/* watch pf log for mail connections, update whitelist entries. */
+/* watch pf log for mail connections, update spamdb entries. */

 #include <sys/types.h>
 #include <sys/socket.h>
@@ -33,6 +33,7 @@
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
+#include <netinet/tcp.h>
 #include <arpa/inet.h>

 #include <net/pfvar.h>
@@ -64,6 +65,7 @@
 int greylist = 1;
 FILE *grey = NULL;

+u_short spamd_port;
 u_short sync_port;
 int syncsend;
 u_int8_t                flag_debug = 0;
@@ -74,13 +76,14 @@
 pcap_t                 *hpcap = NULL;
 struct syslog_data      sdata  = SYSLOG_DATA_INIT;
 time_t                  whiteexp = WHITEEXP;
+time_t                  trapexp = TRAPEXP;
 extern char            *__progname;

 void   logmsg(int , const char *, ...);
 void   sighandler_close(int);
 int    init_pcap(void);
void logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
-int    dbupdate(char *, char *);
+int    dbupdate(char *, char *, int);
 void   usage(void);

 void
@@ -110,9 +113,11 @@
 init_pcap(void)
 {
        struct bpf_program      bpfp;
-       char    filter[PCAPFSIZ] = "ip and port 25 and action pass "
-                   "and tcp[13]&0x12=0x2";
+       char    filter[PCAPFSIZ];

+ snprintf(filter, PCAPFSIZ, "ip and (port 25 or %d) and action pass "
+                   "and tcp[13]&0x12=0x2", spamd_port);
+
        if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
            errbuf)) == NULL) {
                logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
@@ -157,6 +162,11 @@
        const struct ip         *ip = NULL;
        const struct pfloghdr   *hdr;
        char                     ipstraddr[40] = { '\0' };
+       int                      white = 1;
+       unsigned int             off;
+       const struct tcphdr     *tcp;
+       unsigned int             iplen;
+       unsigned int             port;

        hdr = (const struct pfloghdr *)sp;
        if (hdr->length < MIN_PFLOG_HDRLEN) {
@@ -185,26 +195,34 @@
                else if (hdr->dir == PF_OUT && !flag_inbound)
                        inet_ntop(af, &ip->ip_dst, ipstraddr,
                            sizeof(ipstraddr));
+               off = ntohs(ip->ip_off);
+               if ((off & 0x1fff) == 0) {
+                       iplen = ip->ip_hl * 4;
+                       tcp = (const struct tcphdr *)(sp + hdrlen + iplen);
+                       port = ntohs(tcp->th_dport);
+                       if (port == spamd_port)
+                               white = 0;
+               }
        }

        if (ipstraddr[0] != '\0') {
-               if (hdr->dir == PF_IN)
-                       logmsg(LOG_DEBUG,"inbound %s", ipstraddr);
-               else
-                       logmsg(LOG_DEBUG,"outbound %s", ipstraddr);
-               dbupdate(PATH_SPAMD_DB, ipstraddr);
+               logmsg(LOG_DEBUG, "%s %s %s",
+                   hdr->dir == PF_IN ? "inbound" : "outbound",
+                   white ? "white" : "spamd",
+                   ipstraddr);
+               dbupdate(PATH_SPAMD_DB, ipstraddr, white);
        }
 }

 int
-dbupdate(char *dbname, char *ip)
+dbupdate(char *dbname, char *ip, int white)
 {
        HASHINFO        hashinfo;
        DBT             dbk, dbd;
        DB              *db;
        struct gdata    gd;
        time_t          now;
-       int             r;
+       int             r, mod;
        struct in_addr  ia;

        now = time(NULL);
@@ -224,7 +242,7 @@
        dbk.data = ip;
        memset(&dbd, 0, sizeof(dbd));

-       /* add or update whitelist entry */
+       /* add or update entry */
        r = db->get(db, &dbk, &dbd, 0);
        if (r == -1) {
                logmsg(LOG_NOTICE, "db->get failed (%m)");
@@ -237,27 +255,29 @@
                gd.first = now;
                gd.bcount = 1;
                gd.pass = now;
-               gd.expire = now + whiteexp;
-               memset(&dbk, 0, sizeof(dbk));
-               dbk.size = strlen(ip);
-               dbk.data = ip;
-               memset(&dbd, 0, sizeof(dbd));
-               dbd.size = sizeof(gd);
-               dbd.data = &gd;
-               r = db->put(db, &dbk, &dbd, 0);
-               if (r) {
-                       logmsg(LOG_NOTICE, "db->put failed (%m)");
-                       goto bad;
-               }
+               if (white) {
+                       gd.expire = now + whiteexp;
+                       mod = 1;
+               } else
+                       mod = 0;
        } else {
-               /* XXX - backwards compat */
-               if (gdcopyin(&dbd, &gd) == -1) {
+               if (dbd.size != sizeof(gd)) {
                        /* whatever this is, it doesn't belong */
                        db->del(db, &dbk, 0);
                        goto bad;
                }
-               gd.pcount++;
-               gd.expire = now + whiteexp;
+               memcpy(&gd, dbd.data, sizeof(gd));
+               if (white) {
+                       gd.expire = now + whiteexp;
+                       gd.pcount++;
+               } else {
+                       gd.expire = now + trapexp;
+                       gd.bcount++;
+                       gd.pcount = -1;
+               }
+               mod = 1;
+       }
+       if (mod) {
                memset(&dbk, 0, sizeof(dbk));
                dbk.size = strlen(ip);
                dbk.data = ip;
@@ -272,8 +292,12 @@
        }
        db->close(db);
        db = NULL;
-       if (syncsend)
-               sync_white(now, now + whiteexp, ip);
+       if (mod && syncsend) {
+               if (white)
+                       sync_white(now, now + trapexp, ip);
+               else
+                       sync_trapped(now, now + trapexp, ip);
+       }
        return (0);
  bad:
        db->close(db);
@@ -286,7 +310,7 @@
 {
        fprintf(stderr,
            "usage: %s [-DI] [-i interface] [-l pflog_interface] "
-           "[-W whiteexp] [-Y synctarget]\n",
+           "[-W whiteexp] [-T trapexp] [-Y synctarget]\n",
            __progname);
        exit(1);
 }
@@ -303,11 +327,14 @@
        char *sync_baddr = NULL;
        const char *errstr;

+       if ((ent = getservbyname("spamd", "tcp")) == NULL)
+               errx(1, "Can't find service \"spamd\" in /etc/services");
+       spamd_port = ntohs(ent->s_port);
        if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
errx(1, "Can't find service \"spamd-sync\" in /etc/services");
        sync_port = ntohs(ent->s_port);

-       while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) {
+       while ((ch = getopt(argc, argv, "DIi:l:T:W:Y:")) != -1) {
                switch (ch) {
                case 'D':
                        flag_debug = 1;
@@ -320,6 +347,14 @@
                        break;
                case 'l':
                        pflogif = optarg;
+                       break;
+               case 'T':
+                       /* limit trapexp to 2160 hours (90 days) */
+                       trapexp = strtonum(optarg, 1, (24 * 90), &errstr);
+                       if (errstr)
+                               usage();
+                       /* convert to seconds from hours */
+                       trapexp *= (60 * 60);
                        break;
                case 'W':
                        /* limit whiteexp to 2160 hours (90 days) */




$ diff -u spamlogd.8{.head,}
--- spamlogd.8.head     Fri Mar  4 21:01:49 2011
+++ spamlogd.8  Thu Aug 29 12:20:51 2013
@@ -25,6 +25,7 @@
 .Op Fl DI
 .Op Fl i Ar interface
 .Op Fl l Ar pflog_interface
+.Op Fl T Ar trapexp
 .Op Fl W Ar whiteexp
 .Op Fl Y Ar synctarget
 .Sh DESCRIPTION
@@ -40,13 +41,14 @@
 whitelist entries whenever a connection
 to port 25 is logged to the
 .Xr pflog 4
-interface.
-The source addresses of inbound connections are whitelisted
+interface, and existing trapped entries whenever a connection
+to port 8025 is logged.
+The source addresses of inbound connections are added or updated
 when seen by
 .Nm
 to ensure that their entries in
 .Pa /var/db/spamd
-do not expire if the connecting host continues to send legitimate mail.
+do not expire if the connecting host continues to send mail.
 The destination addresses of outbound connections are whitelisted
 when seen by
 .Nm
@@ -78,6 +80,12 @@
 interface to listen for connection notifications.
 The default is to watch for connections logged on
 .Dq pflog0 .
+.It Fl T Ar trapexp
+Adjust the time for
+.Ar trapexp
+in hours.
+The default is 24 hours (one day); maximum is 2160 hours
+(approximately 90 days).
 .It Fl W Ar whiteexp
 Adjust the time for
 .Ar whiteexp


--
Gemaakt met Opera's revolutionaire e-mailprogramma:
http://www.opera.com/mail/
(Remove the obvious prefix to reply privately.)

Reply via email to