Chat
chat US Cluster chat chat Malaysia chat chat Singapore chat chat Backup chat
Internet Chat
chat Home chat chat Chat Help chat chat Chat Rules chat chat Members chat chat Games chat chat About Us chat  
"Fun & Friendly Chat & Games since 1995"
About Us
Alamak Offices
Contact Us
Free Speech & Privacy
What's Happening
Alamak News
Vpopmail smtpd-chkusr with TMDA Python

 Filtering Spam

Over the years my mailbox became totally unusuable, there seemed to be no way to block spammers and I was ubable to reply to my customers in a timely fashion. Finally I hit on TMDA which gave me enough breathing space to attempt to do more.

As an admin I started by switching to qmail and installing TMDA based on testimonials I'd seen on the wmail site.

I think I started by following a linux pop toaster page before i realized there was one for FreeBSD too! So when I setup my new machine I used Matt Simerson's FreeBSD Toaster at http://matt.simerson.net. I wasn't too happy with the location of the vpopmail directory so had edit some things in the toaster script and the qmail source, see below. Also if you move the vpopmail directory you will have to edit some things in the qmailadmin and sqwebmail if you choose to install those.

Chris Hardie Qmail Anti-Spam FreeBSD Toaster


After the changes I've made listed here the only thing I'd like to do is grab the IP addresses on all those invalid and double bounce emails (some still end up in postmaster), stick them in a mysql table, count bad vs good emails for each IP and Nameserver, and then block those with a high ratio of bad/good. Kind of like my own mini-rbl.

 TMDA Replies with qmail-smtpd-chkusr

TMDA is a whitelist based filtering agent which is the best thing I've found to block unwanted emails. Basically user@host sends you an email and TMDA sends a dated reply. user@host can then reply to the dated email address and be automatically added to your whitelist. Next time they email you they are not required to confirm. Since most spammers never confirm emails it blocks most of them and this spam prevention method once setup is hassle free.

Another way to get rid of spam is to drop all your aliases and forwards and just maintain one email box. This makes all the other addresses invalid. Qmail normally recieves and email then bounces the invalid ones but this takes up cpu and bandwidth and the orginally sender may never get the user doesn't exist message! There is a cool patch to qmail-smtpd which blocks emails before being received as the message header is being read.

The problem is smtpd-chkusr patch blocks TMDA style replies, I hacked on the smtpd-chkusr section of qmail-smtpd.c to fix this. If you have a non-standard location for vpopmail you'll also have to fix the include locations at the top of qmail-smtpd.c as well as one reference to the vpopmail directory in the Makefile.

/usr/local/src/mail/qmail-1.03
edit qmail-smtpd.c

#include "/d/vpopmail/include/vpopmail.h"
#include "/d/vpopmail/include/vauth.h"

/* some deleted stuff */

int realrcpt_check()
{   
  stralloc real_user = {0};
  stralloc real_path = {0};

/* some deleted stuff */

       case 4:
/* Check for tmda-reply don't laugh I am not a C programmer */
                /* Allocate real_path and real_user */
                if (!stralloc_ready (&real_path, 300)) die_nomem();
                if (!stralloc_ready (&real_user, user.len)) die_nomem();
                                
                /* get index of first dash */
                /* should really check all dashes ergo john-confirm john-doe-confirm etc */
                for ( count = 0; count < user.len; ++count ) {
                        if (*(user.s + count) == '-') {
                                break;
                        }
                }
    
                /* get real_user */
                if (!stralloc_catb (&real_user, user.s, count)) die_nomem();
                if (!stralloc_0 (&real_user)) die_nomem();

                if (!stralloc_copy (&real_path, &domain_path)) die_nomem();
                if (!stralloc_cats (&real_path, "/")) die_nomem();
                if (!stralloc_catb (&real_path, user.s, count)) die_nomem();
                if (!stralloc_0 (&real_path)) die_nomem();
                                
                /* make sure real_path and real_user are okay */
                if (access (real_path.s, F_OK) == 0) {
                        user_passwd = vauth_getpw (real_user.s, domain.s);
                        if (user_passwd != NULL) {
                                retstat = 1;
                                break; break; 
                                /* yeah I didn't check for .qmail bounce or .qmail-default, its a hack */
                       }
                }                         
        case 5:  
/* Let's check for mailing lists */

make qmail-smtpd
services stop
cp /var/qmail/bin/qmail-smtpd /var/qmail/bin/qmail-smtpd.bak
cp qmail-smtpd /var/qmail/bin
chown vpopmail:qnofiles /var/qmail/bin/qmail-smtpd
chmod 6555 /var/qmail/bin/qmail-smtpd
services start

The patch requires
| ~vpopmail/bin/vdelivermail '' bounce-no-mailbox
in ~vpopmail/domains/yourdomain.com/.tmda-default or it will bounce not block.

Test it (see next page for admin_emailer script)
./admin_emailer root@validdomain.com lala@alamak.com.sg
  ----- The following addresses had permanent fatal errors -----
lala@alamak.com.sg
    (reason: 550 sorry, no mailbox here by that name (#5.1.1 - chkusr))

   ----- Transcript of session follows -----
... while talking to alamak.com.sg.:
>>> RCPT To:
<<< 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
550 5.1.1 lala@alamak.com.sg... User unknown

If it says (#5.1.1 - chkusr) then it's working but if it says qmail-send program then it is bouncing not blocking.

Very cool because it doesn't use qmail-send. If the reply says invalid user from qmail-send program that means the whole email was accepted before attempting to bounce to an invalid reply addresses, not what you want! This patch just blocks and drops it while reading headers and after the handshake but before the body of the mail. It still returns an error to good guys who happened to type your email address wrongly and a little bop on the noze for spammers!

New Patch

The following code I added in November but is by no means finished. Primarily some spams are slipping through as being addressed from user@host to user@host. This generates a TMDA auth request to yourself so you still end up getting the spam anyway. Since I don't think it's normal for a person to send themself email I can take care of it with the following code in smtp_rcpt

Also some emails come in as from <netscape.net> or <@netscape.net>. These are not null sender bounces so we can refuse them and still comply with rfc821..
void smtp_rcpt(arg) char *arg; {
  FILE *fs = NULL;   
  int count;
  /*
     MYSQL mysql;
     MYSQL *db = mysql_init(NULL); */
     still playing around witb mysql logging so it's commented out
     both of these methods work depending how you want to do your pointers
     and I never remember what's what but if I remember correctly & means to dereference a pointer
  */

/* removed some stuff */

  if (!realrcpt_check()) {
    err_realrcpt();
    if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
      fprintf(fs, "IP=%s HOST=%s ERROR FROM=%s TO=%s\n", remoteip, remotehost, mailfrom.s, addr.s);
      fclose(fs);
    } 
    return;
  }
  /* ---
      watch out cron sends from-to same address or empty or sender or as anonymous@hostname
  --- */
  if (mailfrom.s != NULL && addr.s!=NULL && strcmp(remoteip, "127.0.0.1") != 0 && strcasecmp(mailfrom.s, addr.s) == 0) {
    if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
      fprintf(fs, "IP=%s HOST=%s ERROR FROM=%s TO=%s\n", remoteip, remotehost, mailfrom.s, addr.s);
      fclose(fs);
    } 
    out("553 Sorry, mail refused, sender address is invalid (#5.1.7)\r\n");
    return;
  }
  if (mailfrom.s != NULL && ( mailfrom.len = strlen(mailfrom.s) ) > 0) {
    count = byte_rchr(mailfrom.s,mailfrom.len,'@');
    /*
      0 means @ is first char
      count==len means there is NO @ char
    */
    if (count==0 || count==mailfrom.len) {
      if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
	fprintf(fs, "IP=%s HOST=%s ERROR FROM=%s TO=%s\n", remoteip, remotehost, mailfrom.s, addr.s, count, mailfrom.len);
	fclose(fs);
      } 
      out("553 Sorry, mail refused, sender address is invalid (#5.1.7)\r\n");
      return;
    }
  }


  /* ----------
  if (db) {
  mysql_options(db, MYSQL_READ_DEFAULT_GROUP, "qmail-smtpd");
  mysql_options(db,MYSQL_OPT_COMPRESS,0);
  if (!mysql_real_connect(db, NULL, "user", "password", "database_name", 0, NULL, 0)) {
    if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
      fprintf(fs, "Failed to connect to database: Error: %s\n",  mysql_error(db));
      fclose(fs);
    }
  } else {
    if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
      fprintf(fs, "Connected to database\n");
      fclose(fs);
    }
  }
  mysql_close(db);
  } else {
    if ( (fs = fopen("/tmp/qmail.log", "a")) != NULL ) {
      fprintf(fs, "Failed to connect to database\n");
      fclose(fs);
    }
  }
  ---------------- */

  if (!stralloc_cats(&rcptto,"T")) die_nomem();
  if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
  if (!stralloc_0(&rcptto)) die_nomem();

  if (tarpitcount && ++rcptcount >= tarpitcount) while (sleep(tarpitdelay)); 
chat
chat
chat