/* smtp-delay Copyright 2005 Jon Lewis 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. - For a copy of the GPL, see http://www.fsf.org/licensing/licenses/gpl.html This little number can be used to introduce smtp banner delays for qmail. When run between tcpserver and rblsmtpd, it'll do a reverse lookup of the connecting IP, compare that PTR to a regex, and then apply long banner delays if there was no PTR or if the PTR matches the "dialup" regex. The program depends on the fact that tcpserver will set TCPREMOTEIP, and will take advantage of TCPREMOTEHOST if it's set. If the client tries to pipeline (ram SMTP commands down our throat before we show them an SMTP banner), RBLSMTPD is set, notifying rblsmtpd to refuse their mail. 0.1: 20050614 0.2: 20050614 A bunch of minor code cleanups suggested by der Mouse 0.3: 20050615 made delays configurable via getopt 0.4: 20050615 won't blow away the RBLSMTPD env var if it already exists i.e. if it's set to "" to allow some broken host through, they'll still be allowed through even if they do pipeline. 0.5: 20050615 check and use TCPREMOTEHOST which tcpserver may have set. If it's there, great. If it's not, do our own reverse lookup. 0.6: 20050618 added -S switch for standalone mode...just print a 451 message and exit without calling rblsmtpd (or whatever is next) if they pipeline and RBLSMTPD was not already set. The message "logged" to stderr may not work properly (the time waited) on OS's other than Linux. Fixed a problem with the DYNPAT regex and added dhcp to it. Altered program flow such that pretty much everything is skipped if RBLSMTPD was already set. 0.7 20050707 differentiate between pipeliners and quitters. 0.8 20050707 If they pipeline, log up to 99 chars of whatever they sent. Minor code cleanups. 0.9 20050708 A few additions to the dynamic regex 0.10 20050715 Feature request: allow built-in and command line supplied delays to be overridden by environment variables that may be set by tcpserver via settings in its cdb. 0.11 20050722 DYNPAT regex update. Display DYNPAT as part of -v. 0.12 20050816 remove newline from end of pipeline if it's there...keeps from logging an blank line after each log entry for which a short amount of pipelined data was logged. 0.13 20051007 if RBLSMTPD is set to SMTPDELAY, smtp-delay will still do the smtp-delay logic, but will then set RBLSMTPD="". This only really makes sense with smtp-delay running in standalone mode, so it can issue a 4xx to pipeliners but treat the connection as if rblsmtpd would have been skipped. i.e. you want to apply smtp-delay to your own dialup users, but you wouldn't normally subject them to dnsbl blocking. 0.14 20051017 added -N option which allows smtp-delay to reject mail from hosts with no rDNS, but only do so with the -N supplied probability. This can be used to give remote hosts a sort of wakeup call on their lack of rDNS. With a small probability set, any mail blocked should eventually make it through...eventually. Added error checking to make sure numeric args really are. 0.15 20051018 minor code optimization. 0.16 20051219 I've realized that using the RBLSMTPD env var for smtp-delay (the feature added in 0.13) was a dangerous thing to do. Change the order of execution and forget to modify tcp.smtp, and you'll reject all mail. So this feature has been redone. Set SMTPDELAY="JUSTDELAY" now instead of RBLSMTPD="SMTPDELAY" if you want smtp-delay to skip rblsmtpd but still do banner-delay/anti-pipelining. 0.17 20051230 Minor tweaks to JUSTDELAY. 0.18 20060320 Added DYNAMICHELO env var setting for when the connecting IP looks dynamic. Intention is qmail-smtpd can compare DYNAMICHELO (if set) to what the host helo's as, and if they match, reject them. Minor tweak to DYNPAT to try to avoid some FPs. Set TCPREMOTEHOST if we look up the PTR and it wasn't previously set. Add a second "extra dynamic" looking regex. Put PTR in DYNAMICHELO if it matches this second regex. 0.19 20090902 Tweak to DYNIPPAT to try to reduce FPs triggered by oddly named mail servers and/or numbered servers in numeric domains. i.e. samx1w27m3.etrade.com or m15-29.126.com 0.20 20111230 Added the DYNFPPAT, a regex to try to avoid FPs on "poorly" named mail servers with otherwise dynamic looking PTRs. I'm sure this regex will be a work-in-progress */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define VERSION "0.20" #define SOMESSAGE "Rejected due to illegal pipelining" #define SORDNS "Rejected due to missing rDNS/PTR for IP: " #define DYNPAT "((ppp|slip|pool|dyn|dinamic|guest|user|dial|dhcp|modem|port|cable|catv|cabo|docsis|kabel|kctv|ktv|acceso|televiso|dorm|newres|resnet|rescomp|reshall|residence|mobile|wireless|wlan|wless|wls|wrless|students|unassigned|unlabeled|unknown|unprovisioned|unused).*\\.[^.]+\\.[^.]+)|[0-9]+[^0-9][0-9]+[^0-9][0-9]+[^0-9]|[0-9]{6}" #define DYNIPPAT "([^a-z]|^)(ppp|adsl|dhcp|modem|unassigned|unlabeled|unprovisioned)[^a-z].*\\.[^.]+\\.[^.]+|[0-9]+\\.[0-9]+\\.[0-9]+|[0-9]+-[0-9]+-[0-9]+" #define DYNFPPAT "[^a-z0-9]static[^a-z0-9]|^(e-?)?mail|mx|smtp" #define SDS 0 #define SDUS 250000 #define LDS 16 #define LDUS 0 #define PDS 26 #define PDUS 0 #define PIPELEN 100 #define SDENV "SMTPDELAYd" #define LDENV "SMTPDELAYD" #define PDENV "SMTPDELAYP" #define JUSTDELAY "JUSTDELAY" /**************************************************************************** * Print usage info and exit. * ****************************************************************************/ void usage(void) { printf("\nsmtp-delay version %s\n\n",VERSION); printf("Usage:\nsmtp-delay [-d N] [-D N] [-P N] [-N N] [-S] [-v]\n\n"); printf(" -d N - delay to use for hosts with non-dynamic looking rDNS\n"); printf(" -D N - delay to use for hosts with dynamic looking rDNS\n"); printf(" -P N - delay to use for hosts with no rDNS\n"); printf(" Any of these delays can be supplied as either an integer number of\n"); printf(" seconds or a decimal number of [seconds].[fractionalseconds].\n"); printf(" If values are not specified via command line arguments, defaults of:\n"); printf(" -d %f -D %f -P %f will be used.\n", SDS+SDUS/1000000.0, LDS+LDUS/1000000.0, PDS+PDUS/1000000.0); printf(" These delays can also be set via the environment variables %s,\n",SDENV); printf(" %s, and %s.\n", LDENV, PDENV); printf(" -N - probability for which IPs with no PTR should be turned away with a\n"); printf(" 421 message\n"); printf(" -S - standalone mode...will give the client a 451 message and terminate\n"); printf(" without execing whatever was to come next if they pipeline.\n"); printf(" -v - prints this message and exits\n\n"); printf(" \"Dynamic looking\" regex is currently:\n"); printf(" %s\n",DYNPAT); printf(" \"Extra Dynamic looking\" regex is currently:\n"); printf(" %s\n",DYNIPPAT); printf(" \"Anti-FP\" regex is currently:\n"); printf(" %s\n\n",DYNFPPAT); exit(EXIT_SUCCESS); } /**************************************************************************** * Get PTR of supplied IP. Get it from TCPREMOTEHOST if set by tcpserver... * * otherwise do it ourself. * ****************************************************************************/ char *getrev(struct in_addr ip) { char *host_env; const struct hostent *hent; host_env = getenv("TCPREMOTEHOST"); if (host_env) return(host_env); hent = gethostbyaddr((char *)&ip,sizeof(ip), AF_INET); if(hent) { /* may as well have this in the env for smtpd logging purposes For some reason, sometimes tcpserver's query will fail, but smtp-delay's will succeed. */ setenv("TCPREMOTEHOST",hent->h_name,1); return(hent->h_name); } else return(0); } /**************************************************************************** * Get override delays from environment variables which may have been set in * * tcp.smtp. * ****************************************************************************/ int get_ord(char *envvar, struct timeval *timeout) { float td; char *env_delay; env_delay = getenv(envvar); if (env_delay) { timeout->tv_sec = atoi(env_delay); td = atof(env_delay); timeout->tv_usec = 1000000 * (td - timeout->tv_sec); return(1); } return(0); } /*************************************************************************** * Return true or false based on whether or not the passed in number is >= * * a randomly generated number between 1 and 100. * ***************************************************************************/ int chance(int probability) { int j; if (probability == 0 || probability > 99) return(probability); srand(time(NULL)*getpid()); j = 1 + (int)(100.0*rand()/(RAND_MAX+1.0)); /* make a rand 1..100 */ return(probability >= j); } int main (int argc, char **argv) { struct in_addr ip; fd_set rfds, efds; struct timeval timeout, otimeout; regex_t dynreg, dynipreg, dynfpreg; char *ptrname, *ip_env, *rbl_env, pipeline[PIPELEN] = "", *errmsg; char *smtpdelay_env; int oc, lds, ldus, sds, sdus, pds, pdus, so, justdelay, noptrrej; float td; extern char *optarg; extern int optind, opterr, optopt; lds = ldus = sds = sdus = pds = pdus = -1; so = justdelay = noptrrej = 0; while (1) { oc = getopt(argc, argv, "+D:d:N:P:Sv"); if (oc == -1) break; switch(oc) { case 'd': if (optarg[0] < '0' || optarg[0] > '9') usage(); sds = atoi(optarg); td = atof(optarg); sdus = 1000000 * (td - sds); break; case 'D': if (optarg[0] < '0' || optarg[0] > '9') usage(); lds = atoi(optarg); td = atof(optarg); ldus = 1000000 * (td - lds); break; case 'N': if (optarg[0] < '0' || optarg[0] > '9') usage(); noptrrej = atoi(optarg); break; case 'P': if (optarg[0] < '0' || optarg[0] > '9') usage(); pds = atoi(optarg); td = atof(optarg); pdus = 1000000 * (td - pds); break; case 'S': so = 1; break; case '?': case 'v': usage(); break; } } if ((smtpdelay_env = getenv("SMTPDELAY")) != NULL) if (strncmp(smtpdelay_env,JUSTDELAY,strlen(JUSTDELAY)) == 0) justdelay = 1; if ((rbl_env = getenv("RBLSMTPD")) == NULL) { /* if RBLSMTPD is non NULL, we're blocking anyway, so don't bother with any of this. */ ip_env = getenv("TCPREMOTEIP"); if (ip_env) { inet_aton(ip_env,&ip); ptrname = getrev(ip); } else { ptrname = NULL; } if (ptrname) { /* ptr exists */ regcomp(&dynreg, DYNPAT, REG_EXTENDED|REG_ICASE|REG_NOSUB); regcomp(&dynfpreg, DYNFPPAT, REG_EXTENDED|REG_ICASE|REG_NOSUB); if (regexec(&dynreg, ptrname, 0, NULL, 0) || !regexec(&dynfpreg, ptrname, 0, NULL, 0)) { /* doesn't match dynamic regex, or matches the anti-fp regex*/ if (! get_ord(SDENV, &timeout)) { timeout.tv_sec = (sds < 0) ? SDS : sds; timeout.tv_usec = (sdus < 0) ? SDUS : sdus; } } else { /* ptr exists, but is dynamic looking */ if (!justdelay) { regcomp(&dynipreg, DYNIPPAT, REG_EXTENDED|REG_NOSUB); if (! regexec(&dynipreg, ptrname, 0, NULL, 0)) /* matches */ setenv("DYNAMICHELO",ptrname,1); } if (! get_ord(LDENV, &timeout)) { timeout.tv_sec = (lds < 0) ? LDS : lds; timeout.tv_usec = (ldus < 0) ? LDUS : ldus; } } } else { /* no ptr */ if (chance(noptrrej) && (!justdelay)) { if ((errmsg = malloc(strlen(SORDNS) + strlen(ip_env) + 1)) == NULL) { fprintf(stderr,"smtp-delay: %s pid %d: malloc failed\n", ip_env, getpid()); exit(EXIT_FAILURE); } sprintf(errmsg, "%s%s", SORDNS, ip_env); if (so) { printf("421 %s\r\n",errmsg); fprintf(stderr,"smtp-delay: %s pid %d: rejected for no rDNS\n", ip_env, getpid()); exit(EXIT_SUCCESS); } else { setenv("RBLSMTPD",errmsg,1); } } if (! get_ord(PDENV, &timeout)) { timeout.tv_sec = (pds < 0) ? PDS : pds; timeout.tv_usec = (pdus < 0) ? PDUS : pdus; } } FD_ZERO(&rfds); FD_ZERO(&efds); FD_SET(0, &rfds); FD_SET(0, &efds); FD_SET(1, &efds); memcpy(&otimeout, &timeout, sizeof(timeout)); if (select(2, &rfds, 0, &efds, &timeout) > 0) { if (so) { printf("451 %s\r\n",SOMESSAGE); if (fgets(pipeline,PIPELEN,stdin)) { if (pipeline[strlen(pipeline) - 1] == '\n') pipeline[strlen(pipeline) - 1] = 0; fprintf(stderr,"smtp-delay: %s pid %d: waited %0.2f %s\n", ip_env, getpid(), ((otimeout.tv_sec + otimeout.tv_usec / 1000000.0)-(timeout.tv_sec + timeout.tv_usec / 1000000.0)),pipeline); } else fprintf(stderr,"smtp-delay: %s pid %d: quit %0.2f\n", ip_env, getpid(), ((otimeout.tv_sec + otimeout.tv_usec / 1000000.0)-(timeout.tv_sec + timeout.tv_usec / 1000000.0))); exit(EXIT_SUCCESS); } if (!justdelay) setenv("RBLSMTPD",SOMESSAGE,1); } } if (justdelay) setenv("RBLSMTPD","",1); if (argv[optind]) execvp(argv[optind], argv + optind); return 0; }