diff --git Makefile Makefile index 0f0e31a..4a05de9 100644 --- Makefile +++ Makefile @@ -136,6 +136,11 @@ auto_usera.o: \ compile auto_usera.c ./compile auto_usera.c +badrcptto.o: \ +compile badrcptto.c byte.h constmap.h control.h env.h fmt.h str.h \ +stralloc.h strerr.h + ./compile badrcptto.c + binm1: \ binm1.sh conf-qmail cat binm1.sh \ @@ -237,6 +242,10 @@ case_lowers.o: \ compile case_lowers.c case.h ./compile case_lowers.c +case_startb2.o: \ +compile case_startb2.c case.h + ./compile case_startb2.c + case_starts.o: \ compile case_starts.c case.h ./compile case_starts.c @@ -1331,6 +1340,54 @@ fmt.h str.h scan.h open.h error.h getln.h auto_break.h auto_qmail.h \ auto_usera.h ./compile qmail-pw2u.c +qmail-qfilter-ofmipd-queue: \ +load qmail-qfilter-ofmipd-queue.o env.a substdio.a error.a str.a \ +fs.a alloc.a + ./load qmail-qfilter-ofmipd-queue env.a substdio.a error.a str.a \ + fs.a alloc.a + +qmail-qfilter-ofmipd-queue.c: \ +qmail-qfilter-smtpd-queue.c + cat qmail-qfilter-smtpd-queue.c \ + | sed s}control/smtpfilters}control/ofmipfilters}g \ + > qmail-qfilter-ofmipd-queue.c + +qmail-qfilter-ofmipd-queue.o: \ +compile qmail-qfilter-ofmipd-queue.c env.h substdio.h + ./compile qmail-qfilter-ofmipd-queue.c + +qmail-qfilter-queue: \ +load qmail-qfilter-queue.o control.o env.a error.a fs.a getln.a \ +open.a stralloc.a substdio.a str.a alloc.a wait.a + ./load qmail-qfilter-queue control.o env.a error.a fs.a getln.a \ + open.a stralloc.a substdio.a str.a alloc.a wait.a + +qmail-qfilter-queue.o: \ +compile qmail-qfilter-queue.c control.h stralloc.h wait.h + ./compile qmail-qfilter-queue.c + +qmail-qfilter-smtpd-queue: \ +load qmail-qfilter-smtpd-queue.o env.a substdio.a error.a str.a \ +fs.a alloc.a + ./load qmail-qfilter-smtpd-queue env.a substdio.a error.a str.a \ + fs.a alloc.a + +qmail-qfilter-smtpd-queue.o: \ +compile qmail-qfilter-smtpd-queue.c env.h substdio.h + ./compile qmail-qfilter-smtpd-queue.c + +qmail-qfilter-viruscan: \ +load qmail-qfilter-viruscan.o viruscan.o case_startb2.o control.o \ +env.a error.a case.a fs.a getln.a open.a stralloc.a substdio.a \ +str.a alloc.a + ./load qmail-qfilter-viruscan viruscan.o case_startb2.o control.o \ + env.a error.a case.a fs.a getln.a open.a stralloc.a substdio.a \ + str.a alloc.a + +qmail-qfilter-viruscan.o: \ +compile qmail-qfilter-viruscan.c viruscan.h + ./compile qmail-qfilter-viruscan.c + qmail-qmqpc: \ load qmail-qmqpc.o slurpclose.o timeoutread.o timeoutwrite.o \ timeoutconn.o ip.o control.o auto_qmail.o sig.a ndelay.a open.a \ @@ -1437,6 +1494,54 @@ alloc.h substdio.h datetime.h now.h datetime.h triggerpull.h extra.h \ auto_qmail.h auto_uids.h date822fmt.h fmtqfn.h ./compile qmail-queue.c +qmail-rcptcheck: \ +load qmail-rcptcheck.o control.o env.a error.a fs.a getln.a open.a \ +stralloc.a substdio.a str.a alloc.a wait.a + ./load qmail-rcptcheck control.o env.a error.a fs.a getln.a open.a \ + stralloc.a substdio.a str.a alloc.a wait.a + +qmail-rcptcheck-badrcptto: \ +load qmail-rcptcheck-badrcptto.o badrcptto.o control.o constmap.o \ +case.a env.a fs.a getln.a open.a stralloc.a strerr.a substdio.a \ +error.a str.a alloc.a + ./load qmail-rcptcheck-badrcptto badrcptto.o control.o constmap.o \ + case.a env.a fs.a getln.a open.a stralloc.a strerr.a substdio.a \ + error.a str.a alloc.a + +qmail-rcptcheck-badrcptto.o: \ +compile qmail-rcptcheck-badrcptto.c badrcptto.h env.h + ./compile qmail-rcptcheck-badrcptto.c + +qmail-rcptcheck-qregex: \ +load qmail-rcptcheck-qregex.o qregexrcptto.o control.o env.a \ +qregex.o stralloc.a strerr.a error.a getln.a open.a fs.a \ +substdio.a str.a alloc.a + ./load qmail-rcptcheck-qregex qregexrcptto.o control.o env.a \ + qregex.o stralloc.a strerr.a error.a getln.a open.a fs.a \ + substdio.a str.a alloc.a + +qmail-rcptcheck-qregex.o: \ +compile qmail-rcptcheck-qregex.c env.h qregexrcptto.h + ./compile qmail-rcptcheck-qregex.c + +qmail-rcptcheck-realrcptto: \ +load qmail-rcptcheck-realrcptto.o realrcptto.o auto_break.o \ +auto_usera.o control.o constmap.o timeoutwrite.o \ +case.a cdb.a env.a error.a fs.a getln.a open.a str.a stralloc.a \ +alloc.a substdio.a + ./load qmail-rcptcheck-realrcptto realrcptto.o auto_break.o \ + auto_usera.o control.o constmap.o timeoutwrite.o \ + case.a cdb.a env.a error.a fs.a getln.a open.a str.a stralloc.a \ + alloc.a substdio.a + +qmail-rcptcheck-realrcptto.o: \ +compile qmail-rcptcheck-realrcptto.c env.h realrcptto.h + ./compile qmail-rcptcheck-realrcptto.c + +qmail-rcptcheck.o: \ +compile qmail-rcptcheck.c control.h env.h stralloc.h substdio.h wait.h + ./compile qmail-rcptcheck.c + qmail-remote: \ load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \ timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \ @@ -1655,6 +1760,14 @@ gen_alloc.h error.h gen_alloc.h gen_allocdefs.h headerbody.h exit.h \ open.h quote.h qmail.h substdio.h ./compile qreceipt.c +qregex.o: \ +compile qregex.c qregex.h + ./compile qregex.c + +qregexrcptto.o: \ +compile qregexrcptto.c control.h env.h qregex.h stralloc.h strerr.h + ./compile qregexrcptto.c + qsmhook: \ load qsmhook.o sig.a case.a fd.a wait.a getopt.a env.a stralloc.a \ alloc.a substdio.a error.a str.a @@ -1686,6 +1799,11 @@ compile readsubdir.c readsubdir.h direntry.h fmt.h scan.h str.h \ auto_split.h ./compile readsubdir.c +realrcptto.o: \ +compile realrcptto.c auto_break.h auto_usera.h byte.h case.h cdb.h \ +constmap.h error.h fmt.h open.h str.h stralloc.h uint32.h + ./compile realrcptto.c + received.o: \ compile received.c fmt.h qmail.h substdio.h now.h datetime.h \ datetime.h date822fmt.h received.h @@ -1696,6 +1814,13 @@ compile remoteinfo.c byte.h substdio.h ip.h fmt.h timeoutconn.h \ timeoutread.h timeoutwrite.h remoteinfo.h ./compile remoteinfo.c +rejectutils: \ +qmail-qfilter-queue \ +qmail-qfilter-ofmipd-queue qmail-qfilter-smtpd-queue \ +qmail-qfilter-viruscan \ +qmail-rcptcheck \ +qmail-rcptcheck-badrcptto qmail-rcptcheck-qregex qmail-rcptcheck-realrcptto + scan_8long.o: \ compile scan_8long.c scan.h ./compile scan_8long.c @@ -2128,6 +2253,10 @@ tryulong32.c compile load uint32.h1 uint32.h2 && cat uint32.h2 || cat uint32.h1 ) > uint32.h rm -f tryulong32.o tryulong32 +viruscan.o: \ +compile viruscan.c byte.h case.h control.h env.h fmt.h getln.h str.h stralloc.h substdio.h + ./compile viruscan.c + wait.a: \ makelib wait_pid.o wait_nohang.o ./makelib wait.a wait_pid.o wait_nohang.o diff --git TARGETS TARGETS index facdad7..e97040f 100644 --- TARGETS +++ TARGETS @@ -385,3 +385,26 @@ forgeries.0 man setup check +badrcptto.o +case_startb2.o +qmail-qfilter-queue +qmail-qfilter-queue.o +qmail-qfilter-ofmipd-queue +qmail-qfilter-ofmipd-queue.c +qmail-qfilter-ofmipd-queue.o +qmail-qfilter-smtpd-queue +qmail-qfilter-smtpd-queue.o +qmail-qfilter-viruscan +qmail-qfilter-viruscan.o +qmail-rcptcheck +qmail-rcptcheck.o +qmail-rcptcheck-badrcptto +qmail-rcptcheck-badrcptto.o +qmail-rcptcheck-qregex +qmail-rcptcheck-qregex.o +qmail-rcptcheck-realrcptto +qmail-rcptcheck-realrcptto.o +qregex.o +qregexrcptto.o +realrcptto.o +viruscan.o diff --git badrcptto.c badrcptto.c new file mode 100644 index 0000000..40a06f9 --- /dev/null +++ badrcptto.c @@ -0,0 +1,67 @@ +#include +#include "byte.h" +#include "constmap.h" +#include "control.h" +#include "env.h" +#include "fmt.h" +#include "str.h" +#include "stralloc.h" +#include "strerr.h" + +extern void die_control(); +extern void die_nomem(); + +static void _badrcptto_log_rejection(char *recipient) +{ + char smtpdpid[32]; + char *remoteip = env_get("TCPREMOTEIP"); + if (!remoteip) remoteip = "unknown"; + str_copy(smtpdpid + fmt_ulong(smtpdpid,getppid())," "); + strerr_warn5("rcptcheck: badrcptto ",smtpdpid,remoteip," ",recipient,0); +} + +static int _badrcptto_reject_exact_address(struct constmap map, stralloc address) +{ + return (1 && constmap(&map,address.s,address.len - 1)); +} + +static int _badrcptto_reject_whole_domain(struct constmap map, stralloc address) +{ + /* why not just comment out the domain in control/rcpthosts? */ + int j = byte_rchr(address.s,address.len,'@'); + return ((j < address.len) && (constmap(&map,address.s + j,address.len - j - 1))); +} + +static int _badrcptto_reject_string(char *string) +{ + stralloc addr = {0}; + stralloc brt = {0}; + struct constmap mapbrt; + int brtok = control_readfile(&brt,"control/badrcptto",0); + if (brtok == -1) die_control(); + if (!brtok) return 0; + if (!constmap_init(&mapbrt,brt.s,brt.len,0)) die_nomem(); + + if (!stralloc_copys(&addr,string)) die_nomem(); + if (!stralloc_0(&addr)) die_nomem(); + + if (_badrcptto_reject_exact_address(mapbrt,addr)) { + _badrcptto_log_rejection(addr.s); + return 1; + } + + if (_badrcptto_reject_whole_domain(mapbrt,addr)) { + _badrcptto_log_rejection(addr.s); + return 1; + } + + return 0; +} + +int badrcptto_reject_recipient(char *recipient) +{ + if (env_get("RELAYCLIENT")) + return 0; + + return _badrcptto_reject_string(recipient); +} diff --git badrcptto.h badrcptto.h new file mode 100644 index 0000000..d4ced30 --- /dev/null +++ badrcptto.h @@ -0,0 +1,6 @@ +#ifndef BADRCPTTO_H +#define BADRCPTTO_H + +int badrcptto_reject_recipient(char *); + +#endif diff --git case_startb2.c case_startb2.c new file mode 100644 index 0000000..ee88efe --- /dev/null +++ case_startb2.c @@ -0,0 +1,21 @@ +#include "case.h" + +int case_startb(s,len,t) +register char *s; +unsigned int len; +register char *t; +{ + register unsigned char x; + register unsigned char y; + + for (;;) { + y = *t++ - 'A'; + if (y <= 'Z' - 'A') y += 'a'; else y += 'A'; + if (!y) return 1; + if (!len) return 0; + --len; + x = *s++ - 'A'; + if (x <= 'Z' - 'A') x += 'a'; else x += 'A'; + if (x != y) return 0; + } +} diff --git qmail-qfilter-queue.c qmail-qfilter-queue.c new file mode 100755 index 0000000..c67364f --- /dev/null +++ qmail-qfilter-queue.c @@ -0,0 +1,61 @@ +#include +#include "alloc.h" +#include "control.h" +#include "env.h" +#include "str.h" +#include "stralloc.h" +#include "wait.h" + +static void unable_to_allocate() { _exit(51); } +static void unable_to_execute() { _exit(71); } +static void unable_to_verify() { _exit(55); } + +static int num_lines(stralloc *lines) { + int num = 0; + int i; + for (i = 0; i < lines->len; i++) if (lines->s[i] == '\0') num++; + return num; +} + +static void run_qmail_qfilter(stralloc *filters) { + int num_args; + char **args; + int arg; + int linestart; + int i; + + num_args = 2 * num_lines(filters); + if (num_args == 0) num_args = 1; + if (!(args = (char **) alloc(sizeof(char *) * num_args))) + unable_to_allocate(); + + args[0] = "bin/qmail-qfilter"; + + arg = 0; + linestart = 0; + for (i = 0; i < filters->len; i++) { + if (filters->s[i] == '\0') { + stralloc filter = {0}; + stralloc_copys(&filter, filters->s + linestart); + stralloc_0(&filter); + args[++arg] = filter.s; + args[++arg] = "--"; + linestart = i + 1; + } + } + args[num_args] = 0; + + execv(*args, args); + unable_to_execute(); +} + +int main(int argc, char **argv) { + stralloc filters = {0}; + char *filterfile; + + if ((filterfile = env_get("QMAILQUEUEFILTERS"))) + if (control_readfile(&filters,filterfile,0) != 1) + unable_to_verify(); + + run_qmail_qfilter(&filters); +} diff --git qmail-qfilter-smtpd-queue.c qmail-qfilter-smtpd-queue.c new file mode 100755 index 0000000..b6a30ba --- /dev/null +++ qmail-qfilter-smtpd-queue.c @@ -0,0 +1,25 @@ +#include +#include "env.h" +#include "substdio.h" + +static void unable_to_execute() { _exit(71); } +static void unable_to_verify() { _exit(55); } + +static char errbuf[SUBSTDIO_OUTSIZE]; +static substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof(errbuf)); + +int main(int argc, char **argv) { + char *qqqargs[] = { "bin/qmail-qfilter-queue", 0 }; + + substdio_puts(&sserr,argv[0]); + substdio_puts(&sserr," is deprecated. Please set\n"); + substdio_puts(&sserr," QMAILQUEUE=\"qmail-qfilter-queue\"\n"); + substdio_puts(&sserr," QMAILQUEUEFILTERS=\"control/smtpfilters\"\n"); + substdio_flush(&sserr); + + if (!env_put("QMAILQUEUEFILTERS=control/smtpfilters")) + unable_to_verify(); + + execv(*qqqargs, qqqargs); + unable_to_execute(); +} diff --git qmail-qfilter-viruscan.c qmail-qfilter-viruscan.c new file mode 100644 index 0000000..47da307 --- /dev/null +++ qmail-qfilter-viruscan.c @@ -0,0 +1,16 @@ +#include +#include "viruscan.h" + +static void accept_message() { _exit( 0); } +static void reject_message() { _exit(31); } + +void die_control() { _exit(55); } +void die_nomem() { _exit(51); } + +int main(void) +{ + if (viruscan_reject_attachment()) + reject_message(); + + accept_message(); +} diff --git qmail-rcptcheck-badrcptto.c qmail-rcptcheck-badrcptto.c new file mode 100644 index 0000000..dbdd202 --- /dev/null +++ qmail-rcptcheck-badrcptto.c @@ -0,0 +1,23 @@ +#include +#include "badrcptto.h" +#include "env.h" + +void accept_recipient() { _exit( 0); } +void reject_recipient() { _exit(100); } +void unable_to_verify() { _exit(111); } + +void die_control() { unable_to_verify(); } +void die_nomem() { unable_to_verify(); } + +int main(void) +{ + char *recipient = env_get("RECIPIENT"); + + if (!recipient) + unable_to_verify(); + + if (badrcptto_reject_recipient(recipient)) + reject_recipient(); + + accept_recipient(); +} diff --git qmail-rcptcheck-qregex.c qmail-rcptcheck-qregex.c new file mode 100644 index 0000000..55dd653 --- /dev/null +++ qmail-rcptcheck-qregex.c @@ -0,0 +1,29 @@ +#include +#include "env.h" +#include "qregexrcptto.h" + +void accept_recipient() { _exit( 0); } +void reject_recipient() { _exit(100); } +void unable_to_verify() { _exit(111); } + +void die_control() { unable_to_verify(); } +void die_nomem() { unable_to_verify(); } + +int main(void) +{ + char *helohost = env_get("HELOHOST"); + char *sender = env_get("SENDER"); + char *recipient = env_get("RECIPIENT"); + + if (!sender || !recipient) + unable_to_verify(); + + if (helohost && qregex_reject_helohost(helohost)) + reject_recipient(); + if (qregex_reject_sender(sender)) + reject_recipient(); + if (qregex_reject_recipient(recipient)) + reject_recipient(); + + accept_recipient(); +} diff --git qmail-rcptcheck-realrcptto.c qmail-rcptcheck-realrcptto.c new file mode 100644 index 0000000..54810ae --- /dev/null +++ qmail-rcptcheck-realrcptto.c @@ -0,0 +1,27 @@ +#include +#include "env.h" +#include "realrcptto.h" + +void accept_recipient() { _exit( 0); } +void reject_recipient() { _exit(100); } +void unable_to_verify() { _exit(111); } + +void die_cdb() { unable_to_verify(); } +void die_control() { unable_to_verify(); } +void die_nomem() { unable_to_verify(); } +void die_sys() { unable_to_verify(); } + +int main(void) +{ + char *recipient = env_get("RECIPIENT"); + + if (!recipient) + unable_to_verify(); + + realrcptto_init(); + realrcptto_start(); + if (!realrcptto(recipient)) + reject_recipient(); + + accept_recipient(); +} diff --git qmail-rcptcheck.c qmail-rcptcheck.c new file mode 100755 index 0000000..e88c894 --- /dev/null +++ qmail-rcptcheck.c @@ -0,0 +1,167 @@ +#include +#include "control.h" +#include "env.h" +#include "stralloc.h" +#include "substdio.h" +#include "wait.h" + +static char outbuf[128]; +static struct substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof outbuf); +static char errbuf[128]; +static struct substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof errbuf); + +struct rcptcheck_api { + char *(*get_sender)(); + char *(*get_recipient)(); + void (*accept_recipient)(); + void (*reject_recipient)(); + void (*unable_to_verify)(); + void (*unable_to_execute)(); +} rcptcheck; + +static char *rcptcheck_get_sender() { + return env_get("SENDER"); +} + +static char *rcptcheck_get_recipient() { + return env_get("RECIPIENT"); +} + +static void rcptcheck_accept_recipient() { + _exit(0); +} + +static void rcptcheck_reject_recipient() { + _exit(100); +} + +static void rcptcheck_unable_to_verify() { + _exit(111); +} + +static void rcptcheck_unable_to_execute() { + _exit(120); +} + +static void run_rcptchecks_under_rcptcheck() { + rcptcheck.get_sender = rcptcheck_get_sender; + rcptcheck.get_recipient = rcptcheck_get_recipient; + rcptcheck.accept_recipient = rcptcheck_accept_recipient; + rcptcheck.reject_recipient = rcptcheck_reject_recipient; + rcptcheck.unable_to_verify = rcptcheck_unable_to_verify; + rcptcheck.unable_to_execute = rcptcheck_unable_to_execute; +} + +static void putsdie(substdio *ss,char *string) { + substdio_puts(ss,string); + substdio_flush(ss); + _exit(0); +} + +static char *spp_get_sender() { + return env_get("SMTPMAILFROM"); +} + +static char *spp_get_recipient() { + return env_get("SMTPRCPTTO"); +} + +static void spp_accept_recipient() { + putsdie(&ssout,""); +} + +static void spp_reject_recipient() { + putsdie(&ssout,"E553 sorry, no mailbox here by that name. (#5.1.1)\n"); +} + +static void spp_unable_to_verify() { + putsdie(&ssout,"R421 unable to verify recipient (#4.3.0)\n"); +} + +static void spp_unable_to_execute() { + putsdie(&ssout,"R421 unable to execute recipient check (#4.3.0)\n"); +} + +static void run_rcptchecks_under_spp() { + rcptcheck.get_sender = spp_get_sender; + rcptcheck.get_recipient = spp_get_recipient; + rcptcheck.accept_recipient = spp_accept_recipient; + rcptcheck.reject_recipient = spp_reject_recipient; + rcptcheck.unable_to_verify = spp_unable_to_verify; + rcptcheck.unable_to_execute = spp_unable_to_execute; +} + +static void run_rcptcheck(char *program) +{ + char *rcptcheck_program[2] = { program, 0 }; + int pid; + int wstat; + + switch(pid = fork()) { + case -1: + rcptcheck.unable_to_execute(); + case 0: + execv(*rcptcheck_program,rcptcheck_program); + rcptcheck.unable_to_execute(); + } + + if (wait_pid(&wstat,pid) == -1) + rcptcheck.unable_to_execute(); + if (wait_crashed(wstat)) + rcptcheck.unable_to_execute(); + + switch(wait_exitcode(wstat)) { + case 100: rcptcheck.reject_recipient(); + case 111: rcptcheck.unable_to_verify(); + case 120: rcptcheck.unable_to_execute(); + default: return; + } +} + +static int looks_like_spp() { + char *x = env_get("SMTPRCPTTO"); + return (x != 0); +} + +static void set_environment(char *sender, char *recipient) { + substdio_puts(&sserr,"qmail-rcptcheck: from "); + + if (sender) { + substdio_puts(&sserr,sender); + if (!env_put2("SENDER", sender)) rcptcheck.unable_to_verify(); + } + + substdio_puts(&sserr," to "); + + if (recipient) { + substdio_puts(&sserr,recipient); + if (!env_put2("RECIPIENT", recipient)) rcptcheck.unable_to_verify(); + } + + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); +} + +int main(void) +{ + stralloc rcptchecks = {0}; + int linestart; + int pos; + + if (looks_like_spp()) run_rcptchecks_under_spp(); + else run_rcptchecks_under_rcptcheck(); + + set_environment(rcptcheck.get_sender(), rcptcheck.get_recipient()); + + if (control_readfile(&rcptchecks,"control/rcptchecks",0) == -1) + rcptcheck.unable_to_verify(); + + for (linestart = 0, pos = 0; pos < rcptchecks.len; pos++) { + if (rcptchecks.s[pos] == '\0') { + run_rcptcheck(rcptchecks.s + linestart); + linestart = pos + 1; + } + } + + rcptcheck.accept_recipient(); +} diff --git qregex.c qregex.c new file mode 100644 index 0000000..2a758c7 --- /dev/null +++ qregex.c @@ -0,0 +1,57 @@ +/* + * qregex (v2) + * $Id: qregex.c,v 2.1 2001/12/28 07:05:21 evan Exp $ + * + * Author : Evan Borgstrom (evan at unixpimps dot org) + * Created : 2001/12/14 23:08:16 + * Modified: $Date: 2001/12/28 07:05:21 $ + * Revision: $Revision: 2.1 $ + * + * Do POSIX regex matching on addresses for anti-relay / spam control. + * It logs to the maillog + * See the qregex-readme file included with this tarball. + * If you didn't get this file in a tarball please see the following URL: + * http://www.unixpimps.org/software/qregex + * + * qregex.c is released under a BSD style copyright. + * See http://www.unixpimps.org/software/qregex/copyright.html + * + * Note: this revision follows the coding guidelines set forth by the rest of + * the qmail code and that described at the following URL. + * http://cr.yp.to/qmail/guarantee.html + * + */ + +#include +#include +#include "qregex.h" + +#define REGCOMP(X,Y) regcomp(&X, Y, REG_EXTENDED|REG_ICASE) +#define REGEXEC(X,Y) regexec(&X, Y, (size_t)0, (regmatch_t *)0, (int)0) + +int matchregex(char *text, char *regex) { + regex_t qreg; + int retval = 0; + + + /* build the regex */ + if ((retval = REGCOMP(qreg, regex)) != 0) { + regfree(&qreg); + return(-retval); + } + + /* execute the regex */ + if ((retval = REGEXEC(qreg, text)) != 0) { + /* did we just not match anything? */ + if (retval == REG_NOMATCH) { + regfree(&qreg); + return(0); + } + regfree(&qreg); + return(-retval); + } + + /* signal the match */ + regfree(&qreg); + return(1); +} diff --git qregex.h qregex.h new file mode 100644 index 0000000..e333a2d --- /dev/null +++ qregex.h @@ -0,0 +1,5 @@ +/* simple header file for the matchregex prototype */ +#ifndef _QREGEX_H_ +#define _QREGEX_H_ +int matchregex(char *text, char *regex); +#endif diff --git qregexrcptto.c qregexrcptto.c new file mode 100644 index 0000000..7af7a57 --- /dev/null +++ qregexrcptto.c @@ -0,0 +1,126 @@ +#include +#include "control.h" +#include "env.h" +#include "fmt.h" +#include "qregex.h" +#include "str.h" +#include "stralloc.h" +#include "strerr.h" + +extern void die_control(); +extern void die_nomem(); + +static stralloc matchedregex = {0}; + +static int _qregex_at_least_one_match(stralloc haystack, char *needle) +{ + int i = 0; + int j = 0; + int x = 0; + int negate = 0; + stralloc bmb = {0}; + stralloc curregex = {0}; + + if (!stralloc_copy(&bmb,&haystack)) die_nomem(); + + while (j < bmb.len) { + i = j; + while ((i < bmb.len) && (bmb.s[i] != '\0')) i++; + if (bmb.s[j] == '!') { + negate = 1; + j++; + } + if (!stralloc_copyb(&curregex,bmb.s + j,(i - j))) die_nomem(); + if (!stralloc_0(&curregex)) die_nomem(); + x = matchregex(needle, curregex.s); + if ((negate) && (x == 0)) { + if (!stralloc_copyb(&matchedregex,bmb.s + j - 1,(i - j + 1))) die_nomem(); + if (!stralloc_0(&matchedregex)) die_nomem(); + return 1; + } + if (!(negate) && (x > 0)) { + if (!stralloc_copyb(&matchedregex,bmb.s + j,(i - j))) die_nomem(); + if (!stralloc_0(&matchedregex)) die_nomem(); + return 1; + } + j = i + 1; + negate = 0; + } + return 0; +} + +static void _qregex_log_rejection(char *type, char *addr) +{ + char smtpdpid[32]; + char *remoteip = env_get("TCPREMOTEIP"); + stralloc message = {0}; + + str_copy(smtpdpid + fmt_ulong(smtpdpid,getppid())," "); + if (!remoteip) remoteip = "unknown"; + + stralloc_copys(&message,"rcptcheck: qregex "); + stralloc_cats(&message,smtpdpid); + stralloc_cats(&message,remoteip); + stralloc_cats(&message," "); + stralloc_cats(&message,addr); + stralloc_cats(&message," ("); + stralloc_cats(&message,type); + if (env_get("LOGREGEX")) { + stralloc_cats(&message," pattern: "); + stralloc_cats(&message,matchedregex.s); + } + stralloc_cats(&message,")"); + stralloc_0(&message); + + strerr_warn1(message.s,0); +} + +static int _qregex_reject_string(char *type, char *control, char *string) +{ + stralloc regexes = {0}; + int have_some_regexes = control_readfile(®exes,control,0); + if (have_some_regexes == -1) die_control(); + + if (have_some_regexes && _qregex_at_least_one_match(regexes,string)) { + _qregex_log_rejection(type,string); + return 1; + } + + return 0; +} + +int qregex_reject_helohost(char *host) +{ + if (!env_get("NOBADHELO") + && _qregex_reject_string("badhelo","control/badhelo",host)) + return 1; + + return 0; +} + +int qregex_reject_recipient(char *to) +{ + if (_qregex_reject_string("badmailto","control/badmailto",to)) + return 1; + + if (!env_get("RELAYCLIENT") + && _qregex_reject_string("badmailto","control/badmailtonorelay",to)) + return 1; + + return 0; +} + +int qregex_reject_sender(char *from) +{ + if ('\0' == from[0]) + return 0; + + if (_qregex_reject_string("badmailfrom","control/badmailfrom",from)) + return 1; + + if (!env_get("RELAYCLIENT") + && _qregex_reject_string("badmailfrom","control/badmailfromnorelay",from)) + return 1; + + return 0; +} diff --git qregexrcptto.h qregexrcptto.h new file mode 100644 index 0000000..cbdaeda --- /dev/null +++ qregexrcptto.h @@ -0,0 +1,8 @@ +#ifndef QREGEXRCPTTO_H +#define QREGEXRCPTTO_H + +int qregex_reject_helohost(char *); +int qregex_reject_recipient(char *); +int qregex_reject_sender(char *); + +#endif diff --git realrcptto.c realrcptto.c new file mode 100644 index 0000000..ee2fb12 --- /dev/null +++ realrcptto.c @@ -0,0 +1,336 @@ +#include +#include +#include +#include +#include "auto_break.h" +#include "auto_usera.h" +#include "byte.h" +#include "case.h" +#include "cdb.h" +#include "constmap.h" +#include "error.h" +#include "fmt.h" +#include "open.h" +#include "str.h" +#include "stralloc.h" +#include "uint32.h" +#include "substdio.h" +#include "env.h" + +extern void die_nomem(); +extern void die_control(); +extern void die_cdb(); +extern void die_sys(); + +static stralloc envnoathost = {0}; +static stralloc percenthack = {0}; +static stralloc locals = {0}; +static stralloc vdoms = {0}; +static struct constmap mappercenthack; +static struct constmap maplocals; +static struct constmap mapvdoms; + +static char *dash; +static char *extension; +static char *local; +static struct passwd *pw; + +static char errbuf[128]; +static struct substdio sserr = SUBSTDIO_FDBUF(write,2,errbuf,sizeof errbuf); + +static char pidbuf[64]; +static char remoteipbuf[64]=" "; + +static int flagdenyall; +static int flagdenyany; + +void realrcptto_init() +{ + char *x; + + if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) + die_control(); + + if (control_readfile(&locals,"control/locals",1) != 1) die_control(); + if (!constmap_init(&maplocals,locals.s,locals.len,0)) die_nomem(); + switch(control_readfile(&percenthack,"control/percenthack",0)) { + case -1: die_control(); + case 0: if (!constmap_init(&mappercenthack,"",0,0)) die_nomem(); + case 1: + if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) + die_nomem(); + } + switch(control_readfile(&vdoms,"control/virtualdomains",0)) { + case -1: die_control(); + case 0: if (!constmap_init(&mapvdoms,"",0,1)) die_nomem(); + case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) die_nomem(); + } + + str_copy(pidbuf + fmt_ulong(pidbuf,getppid())," "); + x=env_get("PROTO"); + if (x) { + static char const remoteip[]="REMOTEIP"; + unsigned int len = str_len(x); + if (len <= sizeof remoteipbuf - sizeof remoteip) { + byte_copy(remoteipbuf,len,x); + byte_copy(remoteipbuf + len,sizeof remoteip,remoteip); + x = env_get(remoteipbuf); + len = str_len(x); + if (len + 1 < sizeof remoteipbuf) { + byte_copy(remoteipbuf,len,x); + remoteipbuf[len]=' '; + remoteipbuf[len + 1]='\0'; + } + } + } + + x = env_get("QMAILRRTDENYALL"); + flagdenyall = (x && x[0]=='1' && x[1]=='\0'); +} + +void realrcptto_start() +{ + flagdenyany = 0; +} + +static int denyaddr(addr) +char *addr; +{ + substdio_puts(&sserr,"rcptcheck: realrcptto "); + substdio_puts(&sserr,pidbuf); + substdio_puts(&sserr,remoteipbuf); + substdio_puts(&sserr,addr); + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); + flagdenyany = 1; + return flagdenyall; +} + +static void stat_error(path,error) +char* path; +int error; +{ + substdio_puts(&sserr,"unable to stat "); + substdio_puts(&sserr,path); + substdio_puts(&sserr,": "); + substdio_puts(&sserr,error_str(error)); + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); +} + +#define GETPW_USERLEN 32 + +static int userext() +{ + char username[GETPW_USERLEN]; + struct stat st; + + extension = local + str_len(local); + for (;;) { + if (extension - local < sizeof(username)) + if (!*extension || (*extension == *auto_break)) { + byte_copy(username,extension - local,local); + username[extension - local] = 0; + case_lowers(username); + errno = 0; + pw = getpwnam(username); + if (errno == error_txtbsy) die_sys(); + if (pw) + if (pw->pw_uid) + if (stat(pw->pw_dir,&st) == 0) { + if (st.st_uid == pw->pw_uid) { + dash = ""; + if (*extension) { ++extension; dash = "-"; } + return 1; + } + } + else + if (error_temp(errno)) die_sys(); + } + if (extension == local) return 0; + --extension; + } +} + +int realrcptto(addr) +char *addr; +{ + char *homedir; + static stralloc localpart = {0}; + static stralloc lower = {0}; + static stralloc nughde = {0}; + static stralloc wildchars = {0}; + static stralloc safeext = {0}; + static stralloc qme = {0}; + unsigned int i,at; + + /* Short circuit, or full logging? Short circuit. */ + if (flagdenyall && flagdenyany) return 1; + + /* qmail-send:rewrite */ + if (!stralloc_copys(&localpart,addr)) die_nomem(); + i = byte_rchr(localpart.s,localpart.len,'@'); + if (i == localpart.len) { + if (!stralloc_cats(&localpart,"@")) die_nomem(); + if (!stralloc_cat(&localpart,&envnoathost)) die_nomem(); + } + while (constmap(&mappercenthack,localpart.s + i + 1,localpart.len - i - 1)) { + unsigned int j = byte_rchr(localpart.s,i,'%'); + if (j == i) break; + localpart.len = i; + i = j; + localpart.s[i] = '@'; + } + at = byte_rchr(localpart.s,localpart.len,'@'); + if (constmap(&maplocals,localpart.s + at + 1,localpart.len - at - 1)) { + localpart.len = at; + localpart.s[at] = '\0'; + } else { + unsigned int xlen,newlen; + char *x; + for (i = 0;;++i) { + if (i > localpart.len) return 1; + if (!i || (i == at + 1) || (i == localpart.len) || + ((i > at) && (localpart.s[i] == '.'))) { + x = constmap(&mapvdoms,localpart.s + i,localpart.len - i); + if (x) break; + } + } + if (!*x) return 1; + xlen = str_len(x) + 1; /* +1 for '-' */ + newlen = xlen + at + 1; /* +1 for \0 */ + if (xlen < 1 || newlen - 1 < xlen || newlen < 1 || + !stralloc_ready(&localpart,newlen)) + die_nomem(); + localpart.s[newlen - 1] = '\0'; + byte_copyr(localpart.s + xlen,at,localpart.s); + localpart.s[xlen - 1] = '-'; + byte_copy(localpart.s,xlen - 1,x); + localpart.len = newlen; + } + + /* qmail-lspawn:nughde_get */ + { + int r,fd,flagwild; + if (!stralloc_copys(&lower,"!")) die_nomem(); + if (!stralloc_cats(&lower,localpart.s)) die_nomem(); + if (!stralloc_0(&lower)) die_nomem(); + case_lowerb(lower.s,lower.len); + if (!stralloc_copys(&nughde,"")) die_nomem(); + fd = open_read("users/cdb"); + if (fd == -1) { + if (errno != error_noent) die_cdb(); + } else { + uint32 dlen; + r = cdb_seek(fd,"",0,&dlen); + if (r != 1) die_cdb(); + if (!stralloc_ready(&wildchars,(unsigned int) dlen)) die_nomem(); + wildchars.len = dlen; + if (cdb_bread(fd,wildchars.s,wildchars.len) == -1) die_cdb(); + i = lower.len; + flagwild = 0; + do { /* i > 0 */ + if (!flagwild || (i == 1) || + (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1]) + < wildchars.len)) { + r = cdb_seek(fd,lower.s,i,&dlen); + if (r == -1) die_cdb(); + if (r == 1) { + char *x; + if (!stralloc_ready(&nughde,(unsigned int) dlen)) die_nomem(); + nughde.len = dlen; + if (cdb_bread(fd,nughde.s,nughde.len) == -1) die_cdb(); + if (flagwild) + if (!stralloc_cats(&nughde,localpart.s + i - 1)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + close(fd); + x=nughde.s; + /* skip username */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip uid */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip gid */ + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip homedir */ + homedir=x; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + /* skip dash */ + dash=x; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return 1; + ++x; + extension=x; + goto got_nughde; + } + } + --i; + flagwild = 1; + } while (i); + close(fd); + } + } + + /* qmail-getpw */ + local = localpart.s; + if (!userext()) { + extension = local; + dash = "-"; + pw = getpwnam(auto_usera); + } + if (!pw) return denyaddr(addr); + if (!stralloc_copys(&nughde,pw->pw_dir)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + homedir=nughde.s; + + got_nughde: + + /* qmail-local:qmesearch */ + if (!*dash) return 1; + if (!stralloc_copys(&safeext,extension)) die_nomem(); + case_lowerb(safeext.s,safeext.len); + for (i = 0;i < safeext.len;++i) + if (safeext.s[i] == '.') + safeext.s[i] = ':'; + { + struct stat st; + int i; + if (!stralloc_copys(&qme,homedir)) die_nomem(); + if (!stralloc_cats(&qme,"/.qmail")) die_nomem(); + if (!stralloc_cats(&qme,dash)) die_nomem(); + if (!stralloc_cat(&qme,&safeext)) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + if (stat(qme.s,&st) == 0) return 1; + if (errno != error_noent) { + stat_error(qme.s,errno); + return 1; + } + for (i = safeext.len;i >= 0;--i) + if (!i || (safeext.s[i - 1] == '-')) { + if (!stralloc_copys(&qme,homedir)) die_nomem(); + if (!stralloc_cats(&qme,"/.qmail")) die_nomem(); + if (!stralloc_cats(&qme,dash)) die_nomem(); + if (!stralloc_catb(&qme,safeext.s,i)) die_nomem(); + if (!stralloc_cats(&qme,"default")) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + if (stat(qme.s,&st) == 0) return 1; + if (errno != error_noent) { + stat_error(qme.s,errno); + return 1; + } + } + return denyaddr(addr); + } +} + +int realrcptto_deny() +{ + return flagdenyall && flagdenyany; +} diff --git realrcptto.h realrcptto.h new file mode 100644 index 0000000..9a50d14 --- /dev/null +++ realrcptto.h @@ -0,0 +1,9 @@ +#ifndef REALRCPTTO_H +#define REALRCPTTO_H + +void realrcptto_init(); +void realrcptto_start(); +int realrcptto(char *); +int realrcptto_deny(); + +#endif diff --git viruscan.c viruscan.c new file mode 100644 index 0000000..0252cd7 --- /dev/null +++ viruscan.c @@ -0,0 +1,194 @@ +#include +#include "byte.h" +#include "case.h" +#include "control.h" +#include "env.h" +#include "fmt.h" +#include "getln.h" +#include "str.h" +#include "stralloc.h" +#include "substdio.h" + +extern void die_control(); +extern void die_nomem(); + +static void viruscan_log_rejection(char *signature) +{ + char errbuf[SUBSTDIO_OUTSIZE]; + substdio sserr; + char *remoteip; + char *smtpdpid; + char qfilterpid[FMT_ULONG]; + + substdio_fdbuf(&sserr,write,2,errbuf,sizeof(errbuf)); + + remoteip = env_get("TCPREMOTEIP"); + if (!remoteip) remoteip = "unknown"; + smtpdpid = env_get("QMAILPPID"); + str_copy(qfilterpid + fmt_ulong(qfilterpid,getppid()),""); + + substdio_puts(&sserr,"qfilter: viruscan "); + if (smtpdpid) substdio_puts(&sserr,smtpdpid); + else substdio_puts(&sserr,qfilterpid); + substdio_puts(&sserr," "); + substdio_puts(&sserr,remoteip); + substdio_puts(&sserr," "); + substdio_puts(&sserr,signature); + substdio_puts(&sserr,"\n"); + substdio_flush(&sserr); +} + +static int viruscan_reject_line(stralloc *line) +{ + int sigsok, i, j; + stralloc sigs = {0}; + sigsok = control_readfile(&sigs,"control/signatures",0); + if (sigsok == -1) die_control(); + + j = 0; + for (i = 0; i < sigs.len; i++) if (!sigs.s[i]) { + if (i-j < line->len) + if (!str_diffn(line->s,sigs.s+j,i-j)) { + viruscan_log_rejection(sigs.s); + return 1; + } + j = i+1; + } + + return 0; +} + +static int linespastheader; +static char linetype; +static int flagexecutable; +static int flagqsbmf; +static stralloc line = {0}; +static stralloc content = {0}; +static stralloc boundary = {0}; +static int boundary_start; + +static void put(ch) +char *ch; +{ + char *cp, *cpstart, *cpafter; + unsigned int len; + + if (line.len < 1024) + if (!stralloc_catb(&line,ch,1)) die_nomem(); + + if (*ch == '\n') { + if (linespastheader == 0) { + if (line.len == 1) { + linespastheader = 1; + if (flagqsbmf) { + flagqsbmf = 0; + linespastheader = 0; + } + if (content.len) { /* MIME header */ + cp = content.s; + len = content.len; + while (len && (*cp == ' ' || *cp == '\t')) { ++cp; --len; } + cpstart = cp; + if (len && *cp == '"') { /* might be commented */ + ++cp; --len; cpstart = cp; + while (len && *cp != '"') { ++cp; --len; } + } else { + while (len && *cp != ' ' && *cp != '\t' && *cp != ';') { + ++cp; --len; + } + } + if (!case_diffb(cpstart,cp-cpstart,"message/rfc822")) + linespastheader = 0; + + cpafter = content.s+content.len; + while((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) { + ++cp; + while (cp < cpafter && (*cp == ' ' || *cp == '\t')) ++cp; + if (case_startb(cp,cpafter - cp,"boundary=")) { + cp += 9; /* after boundary= */ + if (cp < cpafter && *cp == '"') { + ++cp; + cpstart = cp; + while (cp < cpafter && *cp != '"') ++cp; + } else { + cpstart = cp; + while (cp < cpafter && + *cp != ';' && *cp != ' ' && *cp != '\t') ++cp; + } + /* push the current boundary. Append a null and remember start. */ + if (!stralloc_0(&boundary)) die_nomem(); + boundary_start = boundary.len; + if (!stralloc_cats(&boundary,"--")) die_nomem(); + if (!stralloc_catb(&boundary,cpstart,cp-cpstart)) + die_nomem(); + break; + } + } + } + } else { /* non-blank header line */ + if ((*line.s == ' ' || *line.s == '\t')) { + switch(linetype) { + case 'C': if (!stralloc_catb(&content,line.s,line.len-1)) die_nomem(); break; + default: break; + } + } else { + if (case_startb(line.s,line.len,"content-type:")) { + if (!stralloc_copyb(&content,line.s+13,line.len-14)) die_nomem(); + linetype = 'C'; + } else { + linetype = ' '; + } + } + } + } else { /* non-header line */ + if (boundary.len-boundary_start && *line.s == '-' && line.len > (boundary.len-boundary_start) && + !str_diffn(line.s,boundary.s+boundary_start,boundary.len-boundary_start)) { /* matches a boundary */ + if (line.len > boundary.len-boundary_start + 2 && + line.s[boundary.len-boundary_start+0] == '-' && + line.s[boundary.len-boundary_start+1] == '-') { + /* XXXX - pop the boundary here */ + if (boundary_start) boundary.len = boundary_start - 1; + boundary_start = boundary.len; + while(boundary_start--) if (!boundary.s[boundary_start]) break; + boundary_start++; + linespastheader = 2; + } else { + linespastheader = 0; + } + } else if (linespastheader == 1) { /* first line -- match a signature? */ + if (/*mailfrom.s[0] == '\0' && */ + str_start(line.s,"Hi. This is the ")) + flagqsbmf = 1; + else if (/*mailfrom.s[0] == '\0' && */ + str_start(line.s,"This message was created automatically by mail delivery software")) + flagqsbmf = 1; + else if (viruscan_reject_line(&line)) { + flagexecutable = 1; + } + linespastheader = 2; + } + if (flagqsbmf && str_start(line.s,"---")) { + linespastheader = 0; + } + } + line.len = 0; + } +} + +int viruscan_reject_attachment() +{ + char inbuf[SUBSTDIO_INSIZE]; + substdio ssin; + char ch; + + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + + for (;;) { + if (1 != substdio_get(&ssin,&ch,1)) break; + put(&ch); + if (flagexecutable) + return 1; + } + + return 0; +} diff --git viruscan.h viruscan.h new file mode 100644 index 0000000..aed8ced --- /dev/null +++ viruscan.h @@ -0,0 +1,6 @@ +#ifndef VIRUSCAN_H +#define VIRUSCAN_H + +int viruscan_reject_attachment(void); + +#endif