=Digitalmind rcptcheck patch (2018-08-26) + added RCPTHOSTS variable if destination domain is in rcpthosts =Digitalmind rcptcheck patch (2017-08-23) * original patch from Jay Soffian (http://www.soffian.org/downloads/qmail/qmail-smtpd-doc.html) If RCPTCHECK environment variable is set, qmail-smtpd will exec $RCPTCHECK for each "rcpt to". These env variables are set before calling the rcptcheck program: SENDER, RECIPIENT These exit codes are supported: 100: recipient is rejected with "553 sorry, no mailbox here by that name. (#5.1.1)" 111: connection is dropped with a temporary error "421 unable to verify recipient (#4.3.0)" 120: connection is dropped with a temporary error "421 unable to execute recipient check (#4.3.0)" All others: recipient is accepted * modified by John M. Simpson https://qmail.jms1.net/patches/7.07/01.rcptcheck.patch https://qmail.jms1.net/patches/7.08/02.rcptcheck-custom-error.patch https://qmail.jms1.net/patches/7.09/01.rcptcheck-fix.patch New env variable HELO before running $RCPTCHECK, stderr is redirected to qmail-smtpd/log RCPTCHECK program can send a custom error message back to qmail-smtpd by writing to FD4 (must be used ONLY if USE_FD4==1 and MUST NOT include \r\n at the end). * additional changes by Luca Franceschini - if the variable RCPTCHECKRELAYCLIENT is defined, rcptcheck is called even if RELAYCLIENT is present - supports additional exit codes (only useful for specific qlog entries): 112: ignore/accept 113: overlimit For other exit codes, the behavior should be compatible with existing rcptcheck programs. - if the custom error string doesn't begin with a number >=400 and <=599, it is replaced with "451 temporary problem (#4.4.2)" - only if the custom error string begins with 421 the session will be forcibly closed - vpopmail/chkuser and validrcptto/morevalidrcptto.cdb are checked before calling $RCPTCHECK; if the recipient address was accepted by previous checks, the env variable RCPTFOUND will be available to the rcptcheck program - $RCPTCHECK will be called ONLY if the previous checks didn't reject the envelope (qregex,brtlimit,chkuser validrcptto, relayclient, rcpthosts, etc.) and BEFORE dns rbl checks. - since rcptcheck is called only if the recipient was previously considered valid, the exit code 112 is the same as accepting, but with the difference that qlog will display che correct reason for accepting the rcpt (ie. reason=chkuser, reason=validrcptto, etc.) instead of reason=rcptcheck. - exit code 113 does the same as rejecting the recipient with a "421" message and closing the connection, but the specific qlog "reason=rcptcheck detail=overlimit" will be used instead of the generic "reason=rcptcheck detail=custom". --- a/qmail-smtpd.c +++ b/qmail-smtpd.c @@ -172,11 +172,9 @@ void err_vrt() { qlogenvelope("rejected" void die_brtlimit() { qlogenvelope("rejected","brtlimit","","421"); out("421 too many invalid addresses, goodbye (#4.3.0)\r\n"); flush(); _exit(1); } void err_rcpt() { qlogenvelope("rejected","nomailbox","","550"); out("550 sorry, no mailbox here by that name (#5.1.1)\r\n"); } /* rcptcheck: start */ -/* -void err_badrcpt() { out("553 sorry, no mailbox here by that name. (#5.1.1)\r\n"); } -void die_fork() { out("421 unable to fork (#4.3.0)\r\n"); flush(); _exit(1); } -void die_rcpt() { out("421 unable to verify recipient (#4.3.0)\r\n"); flush(); _exit(1); } -void die_rcpt2() { out("421 unable to execute recipient check (#4.3.0)\r\n"); flush(); _exit(1); } +void die_fork() { qlogenvelope("rejected","rcptcheck","cannotfork","421"); out("421 unable to fork (#4.3.0)\r\n"); flush(); _exit(1); } +void die_rcpt() { qlogenvelope("rejected","rcptcheck","cannotverify","421"); out("421 unable to verify recipient (#4.3.0)\r\n"); flush(); _exit(1); } +void die_rcpt2() { qlogenvelope("rejected","rcptcheck","cannotexecute","421"); out("421 unable to execute recipient check (#4.3.0)\r\n"); flush(); _exit(1); } /* rcptcheck: end */ /* qregex: start */ /* @@ -187,7 +185,7 @@ void err_bmt() { out("553 sorry, your en void err_bhelo() { out("553 sorry, your HELO host name has been denied (#5.7.1)\r\n"); } /* qregex: end */ /* rejectnullsenders: start */ -void die_nullsender() { qlogenvelope("rejected","nullsenderdenied","","421"); out("421 null senders temporary denied (#4.3.0)\r\n"); flush(); _exit(1); } +void die_nullsender() { qlogenvelope("rejected","nullsenderdenied","","421"); out("421 null senders temporarily denied (#4.3.0)\r\n"); flush(); _exit(1); } /* rejectnullsenders: end */ /* rejectrelaytest: start */ void err_relay() { qlogenvelope("rejected","dontrelay","","553"); out("553 we don't relay (#5.7.1)\r\n"); } @@ -343,7 +341,9 @@ static char strnumqp[FMT_ULONG]; /* realbadrcpt: end */ /* rcptcheck: start */ -/*static char *rcptcheck[2] = { 0, 0 };*/ +static char *rcptcheck[2] = { 0, 0 }; +char rcptcheck_err[1024]; +int rcptcheckrelayclient = 0; /* rcptcheck: end */ void setup() @@ -415,7 +415,10 @@ void setup() /* realbadrcpt: end */ /* rcptcheck: start */ -/* rcptcheck[0] = env_get("RCPTCHECK");*/ + rcptcheck[0] = env_get("RCPTCHECK"); + + x = env_get("RCPTCHECKRELAYCLIENT"); + if (x) { scan_ulong(x,&u); rcptcheckrelayclient = u; }; /* rcptcheck: end */ /* rejectrelaytest: start */ @@ -1000,35 +1003,70 @@ int brtcount = 0; /* for brtlimit count /* realbadrcpt: end */ /* rcptcheck: start */ -/* int addrvalid() { int pid; int wstat; + int pierr[2] ; + substdio ss; + char ssbuf[sizeof(rcptcheck_err)]; + int len = 0 ; + char ch; if (!rcptcheck[0]) return 1; + if (pipe(pierr) == -1) die_rcpt2(); switch(pid = fork()) { - case -1: die_fork(); + case -1: + close(pierr[0]); + close(pierr[1]); + die_fork(); case 0: if (!env_put2("SENDER",mailfrom.s)) die_nomem(); if (!env_put2("RECIPIENT",addr.s)) die_nomem(); + if (!env_put2("HELO",helohost.s)) die_nomem(); + if (!env_put2("USE_FD4","1")) die_nomem(); + close(1); + dup2(2,1); + close(pierr[0]); + if (fd_move(4,pierr[1]) == -1) die_rcpt2(); execv(*rcptcheck,rcptcheck); _exit(120); } + + close(pierr[1]); if (wait_pid(&wstat,pid) == -1) die_rcpt2(); if (wait_crashed(wstat)) die_rcpt2(); + + substdio_fdbuf(&ss,read,pierr[0],ssbuf,sizeof(ssbuf)); + while ( substdio_bget(&ss,&ch,1) && len < (sizeof(ssbuf)-3) ) + rcptcheck_err[len++] = ch; + close(pierr[0]); + + while (len&&((rcptcheck_err[len-1]=='\n')||(rcptcheck_err[len-1]=='\r'))) + len -- ; + if (len) { + rcptcheck_err[len] = '\0'; + strerr_warn3(title.s,"RCPTCHECK error: ",rcptcheck_err,0); + rcptcheck_err[len++] = '\r'; + rcptcheck_err[len++] = '\n'; + } + rcptcheck_err[len] = '\0'; + switch(wait_exitcode(wstat)) { - case 1: return 0; - case 0: return 1; - case 255: return -1; - case 100: return 0; - case 111: die_rcpt(); - case 120: die_rcpt2(); + case 100: + return 0; + case 111: + die_rcpt(); + case 112: + return 2; // ignore + case 113: + return 3; // overlimit + case 120: + die_rcpt2(); } return 1; } -*/ /* rcptcheck: end */ int checkrcptcount() { @@ -1334,7 +1372,7 @@ int flagdnsbl = 0; stralloc dnsblhost = {0}; void smtp_rcpt(arg) char *arg; { - int flagrcptmatch = 0; /* 0 undefined, 1 validrcptto, 2 chkuser, 3 chkuserrelay, */ + int flagrcptmatch = 0; /* 0 undefined, 1 validrcptto, 2 chkuser, 3 chkuserrelay, 4 rcptcheck */ /* added by empf patch */ int ret = 0; /* end of empf pacth */ @@ -1416,7 +1454,6 @@ void smtp_rcpt(arg) char *arg; { /* realbadrcpt: start */ if (!relayclient) { /* if relayclient is defined, skip valid recipient checking */ - /* validrcptto */ flagvrt = 0; int vrtres = 0; @@ -1436,22 +1473,6 @@ void smtp_rcpt(arg) char *arg; { } } - /* rcptcheck */ -/* - if (!flagvrt) { - vrtres = addrvalid(); - if (vrtres == 1) { - flagvrt = 1; - strerr_warn5(title.s,"rcptcheck: accepted address <",addr.s,"> at ",remoteip,0); - } - else if (vrtres == -1) { - strerr_warn5(title.s,"rcptcheck: drop address <",addr.s,"> at ",remoteip,0); - err_badrcpt(); - return; - } - else strerr_warn5(title.s,"rcptcheck: ignore address <",addr.s,"> at ",remoteip,0); - } -*/ if (!flagvrt) { switch (chkuser_realrcpt (&mailfrom, &addr)) { case CHKUSER_OK: @@ -1523,9 +1544,82 @@ void smtp_rcpt(arg) char *arg; { break; } } - } + } // if (!relayclient) + + /* rcptcheck */ + if ( (rcptcheck[0]) && (!relayclient || rcptcheckrelayclient) ) { // if RCPTCHECK is not defined, addrvalid returns 1 (rcpt ok),check before calling + strerr_warn5(title.s,"rcptcheck: checking <",addr.s,"> at ",remoteip,0); + if (flagrcptmatch) { + if (!env_put2("RCPTFOUND","1")) die_nomem(); + } + else { + if (!env_unset("RCPTFOUND")) die_nomem(); + } + if (addrinrcpthosts) { + if (!env_put2("RCPTHOSTS","1")) die_nomem(); + } + else { + if (!env_unset("RCPTHOSTS")) die_nomem(); + } + + int rcres = 0; + rcres = addrvalid(); + + char smtperrcode[4]; + char *smtperrstrptr; + long smtperrcodenum = 0; + int len = 0; + int closesession = 0; + + if ((rcptcheck_err[0]) && (sizeof(rcptcheck_err) > 3)) { + strncpy(smtperrcode,rcptcheck_err,3); + smtperrcode[3] = '\0'; + smtperrcodenum = strtoul(smtperrcode, &smtperrstrptr, 10); + if ((smtperrcodenum >= 400) && (smtperrcodenum <=599)) { + if (smtperrcodenum == 421) closesession = 1; + } + else { + len = str_copy(rcptcheck_err,"451 temporary problem (#4.4.2)\r\n"); + rcptcheck_err[len] = '\0' ; + } + qlogenvelope("rejected","rcptcheck","custom",smtperrcode); + } + else { + switch (rcres) { + case 0: + strerr_warn5(title.s,"rcptcheck: drop address <",addr.s,"> at ",remoteip,0); + qlogenvelope("rejected","rcptcheck","nomailbox","550"); + len = str_copy(rcptcheck_err,"550 sorry, no mailbox here by that name. (#5.1.1)\r\n"); + rcptcheck_err[len] = '\0'; + break; + case 1: + strerr_warn5(title.s,"rcptcheck: accepted address <",addr.s,"> at ",remoteip,0); + flagrcptmatch = 4; + break; + case 2: + strerr_warn5(title.s,"rcptcheck: ignore address <",addr.s,"> at ",remoteip,0); + break; + case 3: + strerr_warn5(title.s,"rcptcheck: overlimit sender <",addr.s,"> at ",remoteip,0); + qlogenvelope("rejected","rcptcheck","overlimit","421"); + len = str_copy(rcptcheck_err,"421 you have exceeded your messaging limits (#4.3.0)\r\n"); + rcptcheck_err[len] = '\0'; + closesession = 1; + break; + } + } + + if ( (rcres == 0) || (rcres == 3) ) { + out(rcptcheck_err); flush(); + if (closesession) { + _exit(1); + } + return; + } + } // if rcptcheck[0] /* realbadrcpt: end */ + /* end chkuser code */ /* rbl: start */ if ((rblok) && !(relayclient || seenauth || dnsblskip || flagrbldns)) { @@ -1564,7 +1658,11 @@ void smtp_rcpt(arg) char *arg; { if (flagrcptmatch == 1) { qlogenvelope("accepted","rcptto","validrcptto","250"); } else if (flagrcptmatch == 2) { qlogenvelope("accepted","rcptto","chkuser","250"); } else if (flagrcptmatch == 3) { qlogenvelope("accepted","rcptto","chkuserrelay","250"); } - else { qlogenvelope("accepted","rcptto","","250"); } + else if (flagrcptmatch == 4) { qlogenvelope("accepted","rcptto","rcptcheck","250"); } + else { + if (relayclient) { qlogenvelope("accepted","relayclient","","250"); } + else { qlogenvelope("accepted","rcpthosts","","250"); } + } out("250 ok\r\n"); }