/* * Copyright (C) 2007 Sebastian Werner * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #define BASEDIR "/var/qmail/greylist" #define ENV_BASEDIR "GL_BASEDIR" #define ENV_MIN_REJECT "GL_MIN_REJECT" #define ENV_MAX_WAIT "GL_MAX_WAIT" #define ENV_ACCEPT_GOOD "GL_ACCEPT_GOOD" // Initial waiting time 5min #define DEFAULT_MIN_REJECT 300 // Wait one day after first seen, before forgetting #define DEFAULT_MAX_WAIT 86400 // Entries stay valid for 32 days #define DEFAULT_ACCEPT_GOOD 2764800 int ppid; time_t now; int min_reject, max_wait, accept_good; /* * Basic ideas of this pretty "flat" implementation: * - Create a file per remote IP address when host is seen for first time * - Issue a 451 error as long as minimum waiting time is not met * - When host comes back after minimum waiting time, update the files atime * to the current time and accept the mail * - If after first seen the host did not come back until max waiting time * is hit, delete the entry * - After a lifetime of "accept_good" delete the entry of the file as well * * So per host we get _one_ file. * The mtime of this file is the time, this host has been seen the first time. * Its atime is corresponding time, when this host has been seen the last time. * * I choosed the one-file-per-host model, because it is easy to maintain. * For high-volume mail servers, it is advisable to put the basedir on a * ramdisk with little size but many inodes :) * * Actually this concept is also IPv6 safe. * * Please send patches & suggestions to blackwing@blackwing.de */ /***********************************************************************************/ /* remove the entry of a hostname by unlinking the corresponding file */ int remove_ip (char* host) { if(unlink(host) < 0) { fprintf (stderr, "greylist: pid %d - removing stale entry %s failed: %s\n", ppid,host,strerror(errno)); return(-1); } return(1); } /* check wether in basedir a valid entry for the corresponding host is to be found */ int check_ip (char* host) { struct stat hostfile; int retval; // file does not exist - so never seen before if ( stat ( host, &hostfile ) < 0 ) { return(-1); } // user is back too soon if (now - hostfile.st_mtime < min_reject) { return(0); } // host was not back soon enough after first try. unlink the entry. if ((now - hostfile.st_atime > max_wait) && (hostfile.st_atime == hostfile.st_mtime)) { retval = remove_ip(host); return(-2); } // max lifetime of the entry is reach. unlink the entry. if (now - hostfile.st_atime > accept_good) { retval = remove_ip(host); return(-3); } // host has met requirements. return(1); } /* create a file based on the hostname */ int add_ip (char* host) { int file = open(host, O_CREAT | O_RDWR | O_NOFOLLOW, S_IRUSR | S_IWUSR); if (file < 0) { fprintf (stderr, "greylist: pid %d - creating %s failed: %s\n", ppid,host,strerror(errno)); return(-1); } close(file); return(1); } /* update the atime of the file for the specified hostname */ int update_ip (char* host) { struct utimbuf newutime; struct stat hostfile; if ( stat ( host, &hostfile ) < 0 ) { /* This should never happen! */ return(-1); } // assign new last-seen time newutime.actime = now; newutime.modtime = hostfile.st_mtime; if ( utime ( host, &newutime ) < 0 ) { fprintf (stderr, "greylist: pid %d - updating %s failed: %s\n", ppid,host,strerror(errno)); return(-1); } return(1); } /* parse an numeric environment variable */ int get_numeric_option (char *name, int dflt) { char *value = getenv(name); if (value) { return atoi(value); } return dflt; } int main (int argc, char *argv[]) { char *addr = getenv ("TCPREMOTEIP"); ppid = getppid (); now = time(NULL); /* Are we running in an tcpserver environment? */ if (!addr) { printf ("D\n"); fprintf (stderr, "greylist: pid %d - no TCPREMOTEIP\n", ppid); exit (0); } /* User disabled plugin via tcp.smtp - so exit */ if (getenv ("SKIP_GREY")) { fprintf (stderr, "greylist: pid %d - greylisting skipped.\n", ppid); exit (0); } /* Change to basedir, where all the files are */ char* basedir = getenv(ENV_BASEDIR); if (!basedir) basedir = BASEDIR; if (chdir(basedir) < 0) { fprintf (stderr, "greylist: pid %d - open basedir %s failed: %s\n", ppid,basedir,strerror(errno) ); exit (0); } /* Parse user supplied timing values */ min_reject = get_numeric_option(ENV_MIN_REJECT, DEFAULT_MIN_REJECT); max_wait = get_numeric_option(ENV_MAX_WAIT, DEFAULT_MAX_WAIT); accept_good = get_numeric_option(ENV_ACCEPT_GOOD, DEFAULT_ACCEPT_GOOD); int result = check_ip(addr); if (result < 0) { /* No match found -> reject and add entry */ if( add_ip(addr) ) { printf("E451 Greylisting in progress. Please try again later.\n"); fprintf (stderr, "greylist: pid %d - %s new - temp error\n", ppid, addr); } // if creating fails, we accept the mail anyway to be failsafe. } else if (result == 0) { /* Match found, but min_reject is not expired -> reject again */ printf("E451 Greylisting in progress. Please try again later.\n"); fprintf (stderr, "greylist: pid %d - %s back too soon - temp error\n", ppid, addr); } else { /* Match found and min_reject expired and max_wait or accept_good not * expired */ if ( update_ip(addr) ) { fprintf (stderr, "greylist: pid %d - Accepted %s\n", ppid, addr); } // if updating fails, we accept the mail anyway to be failsafe. } exit (0); }