--- dns.c.old Tue Mar 14 19:31:52 2000 +++ dns.c Mon Mar 20 21:46:49 2000 @@ -5,6 +5,7 @@ #include #include #include +#include extern int res_query(); extern int res_search(); extern int errno; @@ -18,6 +19,8 @@ #include "dns.h" #include "case.h" +int blacklist_debug=0; + static unsigned short getshort(c) unsigned char *c; { unsigned short u; u = c[0]; return (u << 8) + c[1]; } @@ -301,29 +304,191 @@ return DNS_HARD; } +/* helper function by Mark Whitis */ +static void parse_ip(struct ip_address *result, int *length, const char *s) +{ + int unpacked_ip[4]; + int rc; + + *length=32; /* default */ + unpacked_ip[0]=unpacked_ip[1]=unpacked_ip[2]=unpacked_ip[3]=0; + sscanf(s,"%d.%d.%d.%d/%d", + &unpacked_ip[0],&unpacked_ip[1],&unpacked_ip[2],&unpacked_ip[3],length); + result->d[0]=unpacked_ip[0]; + result->d[1]=unpacked_ip[1]; + result->d[2]=unpacked_ip[2]; + result->d[3]=unpacked_ip[3]; + +} + +/* returns number of consequetive matching bits */ +/* i.e if (compare_ip(...)>=24) {... } will compare a /24 CIDR block */ + +/* helper function by Mark Whitis */ +static int compare_ip(struct ip_address *ip1, struct ip_address *ip2) +{ + unsigned long ip1l; + unsigned long ip2l; + unsigned long mask; + int i; + + + ip1l= (ip1->d[0]<<24) | (ip1->d[1]<<16) | (ip1->d[2]<<8) | ip1->d[3]; + ip2l= (ip2->d[0]<<24) | (ip2->d[1]<<16) | (ip2->d[2]<<8) | ip2->d[3]; + mask=0x80000000; + + + for(i=0; i<32; i++) { + if( (ip1l & mask)!=(ip2l & mask) ) break; + mask>>=1; + } + + if(blacklist_debug) fprintf(stderr,"Compare ip: [%d,%d,%d,%d] and [%d,%d,%d,%d] returns %d\n", + ip1->d[0], ip1->d[1], ip1->d[2], ip1->d[3], + ip2->d[0], ip2->d[1], ip2->d[2], ip2->d[3], + i); + return(i); +} + +/* this function was added by Russ Nelson and modified by Mark Whitis */ +/* It deviates from the coding style in the rest of the program */ +/* in that it uses fixed length strings and doesn't use certain djb */ +/* helper functions that I don't know well enough to use safely. */ + +/* This will cause the control/blacklists file to be read */ +/* each time the function is called which may not be the most */ +/* efficient but the overhead introduced is less than that of */ +/* the DNS lookups themselves. Also, if there is no file */ +/* then only the open() is attempted and the VNODES should */ +/* be in the cache so there is very little performance hit */ +/* if this feature is not used. In other words, I suspect */ +/* if you disable this feature by deleting control/blacklists */ +/* this code will probably not affect benchmark performance */ + +/* Return values: <0 == error. DNS_HARD and DNS_SOFT */ +/* actually will be interpreted as not being in the blacklist */ +/* Return value: 0 == blacklist */ +/* So, if we don't find the site blacklisted (or we find it */ +/* listed on an exception list, we want to return */ +/* DNS_HARD */ + + int dns_maps(sa,ip) stralloc *sa; struct ip_address *ip; { int r; + FILE *blacklist; + char buf[128]; + char *suffix; + int rc; + struct ip_address bl_ip; + int length; + int line; + int i; + int reverse_polarity; - if (!stralloc_ready(sa,iaafmt((char *) 0,ip,".rbl.maps.vix.com."))) return DNS_MEM; - sa->len = iaafmt(sa->s,ip,".rbl.maps.vix.com."); - switch(resolve(sa,T_TXT)) - { - case DNS_MEM: return DNS_MEM; - case DNS_SOFT: return DNS_SOFT; - case DNS_HARD: return DNS_HARD; - } - while ((r = findstring(T_TXT)) != 2) - { - if (r == DNS_SOFT) return DNS_SOFT; - if (r == 1) + if(blacklist_debug) fprintf(stderr,"dns_maps() entered\n"); + + line=0; + if(blacklist_debug) fprintf(stderr,"dns_maps(): CWD=%s\n",get_current_dir_name()); + + blacklist=fopen("/var/qmail/control/blacklists","r"); + /* any file opening error is treated as no file present/empty file */ + /* i.e., no blacklisting occurs */ + if(blacklist_debug) fprintf(stderr,"dns_maps(): blacklist=%d\n",blacklist); + + if(!blacklist) return(DNS_HARD); + + if(blacklist_debug) fprintf(stderr,"dns_maps() file opened\n"); + + while(1) { + line++; + reverse_polarity=0; + buf[0]=0; + /* the next line is the normal happy (no blacklist) return - when we */ + /* get to the end of file, we are happy */ + if(!fgets(buf, sizeof(buf), blacklist)) return( DNS_HARD ); + buf[sizeof(buf)-1]=0; + buf[strcspn(buf,"\n")]=0; /* kill newline at end */ + + if(blacklist_debug) fprintf(stderr,"dns_maps(): Line: %s\n",buf); + + if( buf[0]=='#' ) continue; + if( strspn(buf, " \t\n\r#")==strlen(buf) ) continue; + + if(blacklist_debug) fprintf(stderr,"dns_maps(): Rule: %s\n",buf); + suffix=&buf[0]; + /* if the concept of "+"= reverse polarity seems strange */ + /* remember that this code started out as purely exclusionary */ + /* and then evolved to a primative access control language */ + if( buf[0]=='+') { suffix=&buf[1]; reverse_polarity=1; }; + if( buf[0]=='-') { suffix=&buf[1]; reverse_polarity=0; }; + suffix+=strspn(suffix," \t"); /* skip white space */ + + if( *suffix=='@' ) { + /* Lines with an @ signify a suffix for a DNS style blacklist lookup */ + suffix++; + if (!stralloc_ready(sa,iaafmt((char *) 0,ip,suffix))) return DNS_MEM; + + sa->len = iaafmt(sa->s,ip,suffix); + /* this next part probably causes spurious returns */ + rc=resolve(sa,T_TXT); + /* now that we are looking up in multiple blacklists, we do */ + /* not want any errors in looking up one to prevent looking up */ + /* in the next */ + if(rc==DNS_MEM) return DNS_MEM; + if(rc<0) continue; + + while ((r = findstring(T_TXT)) != 2) { - if (!stralloc_copys(sa,name)) return DNS_MEM; - return 0; + if (r == DNS_SOFT) return DNS_SOFT; + if (r == 1) + { + if (!stralloc_copys(sa,name)) return DNS_MEM; + + /* They were found, returning zero will blacklist them */ + /* if this was an exception list, we will return the normal */ + /* error (meaning not found in blacklist) and effectively */ + /* stop looking up in other blacklists */ + if(reverse_polarity) { + return(DNS_HARD); + } else { + return (0); + } + } + } + } else if (*suffix=='[') { + /* Line of the form: */ + /* +[1.2.3.4/32]:complaint message */ + /* the "+" (or "-") and "/32" parts are optional */ + length=32; + parse_ip(&bl_ip,&length,suffix+1); + if(compare_ip(ip, &bl_ip)>=length) { + /* match */ + if(blacklist_debug) fprintf(stderr, "dns_maps(): comparison succeeded\n"); + if( reverse_polarity ) { + + return(DNS_HARD); + } else { + stralloc_copys(sa, "Mail from locally blacklisted site rejected"); + /* suffix+=strcspn(suffix,":"); */ + /* Supply reason for rejection from control/blacklists */ + if(!stralloc_copys( sa, suffix )) return DNS_MEM; + + return(0); } + + } else { + if(blacklist_debug) fprintf(stderr, "dns_maps(): comparison failed\n"); + /* fall through to processing the next line in file */ + } + + } else { + fprintf(stderr, "220-control/blacklists: syntax error\n"); + return DNS_HARD; } + } /* while (each line in config file) */ return DNS_HARD; } --- qmail-showctl.c.old Tue Mar 14 20:44:40 2000 +++ qmail-showctl.c Mon Mar 20 21:31:41 2000 @@ -215,6 +215,7 @@ } do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM."); + do_lst("blacklists","No blacklists are used",""," will be used as blacklist rule"); do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is "); do_str("bouncehost",1,"bouncehost","Bounce host name is "); do_int("concurrencylocal","10","Local concurrency is ",""); @@ -268,6 +269,7 @@ if (str_equal(d->d_name,"bouncefrom")) continue; if (str_equal(d->d_name,"bouncehost")) continue; if (str_equal(d->d_name,"badmailfrom")) continue; + if (str_equal(d->d_name,"blacklists")) continue; if (str_equal(d->d_name,"bouncefrom")) continue; if (str_equal(d->d_name,"bouncehost")) continue; if (str_equal(d->d_name,"concurrencylocal")) continue; --- INSTALL.old Tue Mar 14 20:42:17 2000 +++ INSTALL Mon Mar 20 22:04:01 2000 @@ -1,3 +1,7 @@ +NOTE: This version has Russ Nelson's patch to use the RBL applied +and Mark Whitis's extension to that patch to allow you to +specify blacklist lookup domains in control/qmail. + SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and information generally), the qmail system comes with NO WARRANTY. It's much more secure and reliable than sendmail, but that's not saying much. @@ -74,7 +78,57 @@ 18. Read TEST.receive. +19. Configure /var/qmail/control/blacklists if you want to + subscribe to various spam blacklists. Do not do this + unless you understand the implications, including some + legitimate mail being bounced. http://www.crynwr.com/spam/ + +[1.2.3.4/24]:one of our internal blocks + -[5.6.7.8/24]:ignores SPAM complaints + -@.rbl.maps.vix.com. + -@.relays.mail-abuse.org. + -@.dialups.mail-abuse.org. + -@.relays.orbs.org. + If blacklists is empty or non-existant, no blacklists will be used. + + You can also use the rblsmtpd program in the ucspi-tcp package. + +20. Block spam relaying so you are not used by spammers and so you + don't end up on the blacklist. To do this, create + /var/qmail/control/rcpthosts with a list of valid recipient + hostnames or domains (i.e. valid inbound mail). You will + also probably need to allow mail relaying from inside clients + if this is a primary mail server for a domain. To do that you need + to set the environment variable RELAYCLIENT when qmail is + invoked from a relay client. If you want to use tcpd to do this, + change the inetd.conf line to insert /usr/sbin/tcpd (long lines + will be continued with backslashes): + smtp stream tcp nowait qmaild /usr/sbin/tcpd \ + /usr/bin/qmail/tcp-env /usr/bin/qmail/qmail-smtpd + + Then add this to /etc/hosts.allow (substitute YOUR netblocks, of course): + # JUNK is to force frc931 lookup to occur, since "rfc931" doesn't + #tcp-env:setenv RELAYCLIENT:allow + tcp-env: 208.241.63.0/255.255.255.0, 63.64.19.1/255.255.255.0: \ + setenv RELAYCLIENT:setenv JUNK "%u@%h":banners /etc/banners/: \ + severity mail.info:rfc931:allow + tcp-env: ALL :setenv JUNK "%u@%h":banners /etc/banners/: \ + severity mail.info:rfc931:allow + + And create /etc/banners/tcp-env: + 220-Hello %u@%h + 220-No unsolicited bulk email. There will be a fee for delivery + 220-or relaying of unsolicited bulk email. By sending UBE + 220-you consent to our terms for such delivery. Write to + 220-us via paper mail for a copy of those terms. + 220- + 220-This system is for authorized users only. Unauthorized users or + 220-users exceeding their authority are subject to having their + 220-Keystrokes, packets, sessions, messages, and stored-data monitored, + 220-logged, and/or inspected. In the course of such monitoring, + 220-authorized users may inadvertantly be monitored. + 220- + You can use the tcpserver program instead of tcpd. That's it! To report success: % ( echo 'First M. Last'; cat `cat SYSDEPS` ) | mail djb-qst@cr.yp.to @@ -82,3 +136,6 @@ If you have questions about qmail, join the qmail mailing list; see http://pobox.com/~djb/qmail.html. + + + --- qmail-smtpd.8.old Tue Mar 14 21:57:46 2000 +++ qmail-smtpd.8 Mon Mar 20 22:05:42 2000 @@ -50,6 +50,36 @@ meaning every address at .IR host . .TP 5 +.I blacklists +The ip address of each connecting host will have its octets reversed +(as with dns lookups in the in-addr.arpa. domain) and looked up +in DNS with the suffix specified on each line. Mail will be rejected +if the host is listed in the blacklist. This will reject some +legitimate mail as well as SPAM. See: http://www.crynwr.com/spam/ +to find more information on the various blacklists. You must +read their websites before using the blacklists. +.EX + +[1.2.3.4/24]:one of our internal blocks + -[5.6.7.8/24]:ignores SPAM complaints + -@.rbl.maps.vix.com. + -@.relays.mail-abuse.org. + -@.dialups.mail-abuse.org. + -@.relays.orbs.org. +.EE +Comments and blank lines are allowed. The line may optionally begin +with either "+" or "-". Lines beginning with "-" (or anything other +than "+") are treated as normal blacklists. Lines beginning with +"+" are exception lists. If a match occurs, blacklist processing +stops and the site is considered ok. You probably want your +exception list(s) at the beginning of the file. Note that +the rlbdns program in Dan Bernstein's DNScache package is +availible for publishing blacklists and exception lists. +Lines with "@" will lookup the host in a reverse DNS style blacklist +using the supplied prefix. Lines of the form "[ip/len]:message" will +block or allow the specified CIDR block; if the packet is rejected, +the CIDR block and the supplied message will be used as a rejection +message. +.TP 5 .I databytes Maximum number of bytes allowed in a message, or 0 for no limit.