Thanks to John Cummings.
This commit is contained in:
parent
cd37451963
commit
5cdb17983a
94 changed files with 26853 additions and 0 deletions
274
src/cmd/upas/smtp/greylist.c
Normal file
274
src/cmd/upas/smtp/greylist.c
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#include "common.h"
|
||||
#include "smtpd.h"
|
||||
#include "smtp.h"
|
||||
#include <ctype.h>
|
||||
#include <ip.h>
|
||||
#include <ndb.h>
|
||||
|
||||
typedef struct {
|
||||
int existed; /* these two are distinct to cope with errors */
|
||||
int created;
|
||||
int noperm;
|
||||
long mtime; /* mod time, iff it already existed */
|
||||
} Greysts;
|
||||
|
||||
/*
|
||||
* There's a bit of a problem with yahoo; they apparently have a vast
|
||||
* pool of machines that all run the same queue(s), so a 451 retry can
|
||||
* come from a different IP address for many, many retries, and it can
|
||||
* take ~5 hours for the same IP to call us back. Various other goofballs,
|
||||
* notably the IEEE, try to send mail just before 9 AM, then refuse to try
|
||||
* again until after 5 PM. Doh!
|
||||
*/
|
||||
enum {
|
||||
Nonspammax = 14*60*60, /* must call back within this time if real */
|
||||
};
|
||||
static char whitelist[] = "/mail/lib/whitelist";
|
||||
|
||||
/*
|
||||
* matches ip addresses or subnets in whitelist against nci->rsys.
|
||||
* ignores comments and blank lines in /mail/lib/whitelist.
|
||||
*/
|
||||
static int
|
||||
onwhitelist(void)
|
||||
{
|
||||
int lnlen;
|
||||
char *line, *parse;
|
||||
char input[128];
|
||||
uchar ip[IPaddrlen], ipmasked[IPaddrlen];
|
||||
uchar mask4[IPaddrlen], addr4[IPaddrlen];
|
||||
uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
|
||||
Biobuf *wl;
|
||||
static int beenhere;
|
||||
static allzero[IPaddrlen];
|
||||
|
||||
if (!beenhere) {
|
||||
beenhere = 1;
|
||||
fmtinstall('I', eipfmt);
|
||||
}
|
||||
|
||||
parseip(ip, nci->rsys);
|
||||
wl = Bopen(whitelist, OREAD);
|
||||
if (wl == nil)
|
||||
return 1;
|
||||
while ((line = Brdline(wl, '\n')) != nil) {
|
||||
if (line[0] == '#' || line[0] == '\n')
|
||||
continue;
|
||||
lnlen = Blinelen(wl);
|
||||
line[lnlen-1] = '\0'; /* clobber newline */
|
||||
|
||||
/* default mask is /32 (v4) or /128 (v6) for bare IP */
|
||||
parse = line;
|
||||
if (strchr(line, '/') == nil) {
|
||||
strncpy(input, line, sizeof input - 5);
|
||||
if (strchr(line, '.') != nil)
|
||||
strcat(input, "/32");
|
||||
else
|
||||
strcat(input, "/128");
|
||||
parse = input;
|
||||
}
|
||||
/* sorry, dave; where's parsecidr for v4 or v6? */
|
||||
v4parsecidr(addr4, mask4, parse);
|
||||
v4tov6(addr, addr4);
|
||||
v4tov6(mask, mask4);
|
||||
|
||||
maskip(addr, mask, addrmasked);
|
||||
maskip(ip, mask, ipmasked);
|
||||
if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
|
||||
break;
|
||||
}
|
||||
Bterm(wl);
|
||||
return line != nil;
|
||||
}
|
||||
|
||||
static int mkdirs(char *);
|
||||
|
||||
/*
|
||||
* if any directories leading up to path don't exist, create them.
|
||||
* modifies but restores path.
|
||||
*/
|
||||
static int
|
||||
mkpdirs(char *path)
|
||||
{
|
||||
int rv = 0;
|
||||
char *sl = strrchr(path, '/');
|
||||
|
||||
if (sl != nil) {
|
||||
*sl = '\0';
|
||||
rv = mkdirs(path);
|
||||
*sl = '/';
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* if path or any directories leading up to it don't exist, create them.
|
||||
* modifies but restores path.
|
||||
*/
|
||||
static int
|
||||
mkdirs(char *path)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (access(path, AEXIST) >= 0)
|
||||
return 0;
|
||||
|
||||
/* make presumed-missing intermediate directories */
|
||||
if (mkpdirs(path) < 0)
|
||||
return -1;
|
||||
|
||||
/* make final directory */
|
||||
fd = create(path, OREAD, 0777|DMDIR);
|
||||
if (fd < 0)
|
||||
/*
|
||||
* we may have lost a race; if the directory now exists,
|
||||
* it's okay.
|
||||
*/
|
||||
return access(path, AEXIST) < 0? -1: 0;
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long
|
||||
getmtime(char *file)
|
||||
{
|
||||
long mtime = -1;
|
||||
Dir *ds = dirstat(file);
|
||||
|
||||
if (ds != nil) {
|
||||
mtime = ds->mtime;
|
||||
free(ds);
|
||||
}
|
||||
return mtime;
|
||||
}
|
||||
|
||||
static void
|
||||
tryaddgrey(char *file, Greysts *gsp)
|
||||
{
|
||||
int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
|
||||
|
||||
gsp->created = (fd >= 0);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
gsp->existed = 0; /* just created; couldn't have existed */
|
||||
} else {
|
||||
/*
|
||||
* why couldn't we create file? it must have existed
|
||||
* (or we were denied perm on parent dir.).
|
||||
* if it existed, fill in gsp->mtime; otherwise
|
||||
* make presumed-missing intermediate directories.
|
||||
*/
|
||||
gsp->existed = access(file, AEXIST) >= 0;
|
||||
if (gsp->existed)
|
||||
gsp->mtime = getmtime(file);
|
||||
else if (mkpdirs(file) < 0)
|
||||
gsp->noperm = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
addgreylist(char *file, Greysts *gsp)
|
||||
{
|
||||
tryaddgrey(file, gsp);
|
||||
if (!gsp->created && !gsp->existed && !gsp->noperm)
|
||||
/* retry the greylist entry with parent dirs created */
|
||||
tryaddgrey(file, gsp);
|
||||
}
|
||||
|
||||
static int
|
||||
recentcall(Greysts *gsp)
|
||||
{
|
||||
long delay = time(0) - gsp->mtime;
|
||||
|
||||
if (!gsp->existed)
|
||||
return 0;
|
||||
/* reject immediate call-back; spammers are doing that now */
|
||||
return delay >= 30 && delay <= Nonspammax;
|
||||
}
|
||||
|
||||
/*
|
||||
* policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
|
||||
* reject this message as "451 temporary failure". if the caller is real,
|
||||
* he'll retry soon, otherwise he's a spammer.
|
||||
* at the first rejection, create a greylist entry for (my-ip, caller-ip,
|
||||
* rcpt, time), where time is the file's mtime. if they call back and there's
|
||||
* already a greylist entry, and it's within the allowed interval,
|
||||
* add their IP to the append-only whitelist.
|
||||
*
|
||||
* greylist files can be removed at will; at worst they'll cause a few
|
||||
* extra retries.
|
||||
*/
|
||||
|
||||
static int
|
||||
isrcptrecent(char *rcpt)
|
||||
{
|
||||
char *user;
|
||||
char file[256];
|
||||
Greysts gs;
|
||||
Greysts *gsp = &gs;
|
||||
|
||||
if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
|
||||
strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
|
||||
return 0;
|
||||
|
||||
/* shorten names to fit pre-fossil or pre-9p2000 file servers */
|
||||
user = strrchr(rcpt, '!');
|
||||
if (user == nil)
|
||||
user = rcpt;
|
||||
else
|
||||
user++;
|
||||
|
||||
/* check & try to update the grey list entry */
|
||||
snprint(file, sizeof file, "/mail/grey/%s/%s/%s",
|
||||
nci->lsys, nci->rsys, user);
|
||||
memset(gsp, 0, sizeof *gsp);
|
||||
addgreylist(file, gsp);
|
||||
|
||||
/* if on greylist already and prior call was recent, add to whitelist */
|
||||
if (gsp->existed && recentcall(gsp)) {
|
||||
syslog(0, "smtpd",
|
||||
"%s/%s was grey; adding IP to white", nci->rsys, rcpt);
|
||||
return 1;
|
||||
} else if (gsp->existed)
|
||||
syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
|
||||
nci->rsys, rcpt);
|
||||
else
|
||||
syslog(0, "smtpd", "no call registered for %s/%s; registering",
|
||||
nci->rsys, rcpt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
vfysenderhostok(void)
|
||||
{
|
||||
char *fqdn;
|
||||
int recent = 0;
|
||||
Link *l;
|
||||
|
||||
if (onwhitelist())
|
||||
return;
|
||||
|
||||
for (l = rcvers.first; l; l = l->next)
|
||||
if (isrcptrecent(s_to_c(l->p)))
|
||||
recent = 1;
|
||||
|
||||
/* if on greylist already and prior call was recent, add to whitelist */
|
||||
if (recent) {
|
||||
int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
|
||||
|
||||
if (fd >= 0) {
|
||||
seek(fd, 0, 2); /* paranoia */
|
||||
if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil)
|
||||
fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys);
|
||||
else
|
||||
fprint(fd, "# unknown\n%s\n\n", nci->rsys);
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
syslog(0, "smtpd",
|
||||
"no recent call from %s for a rcpt; rejecting with temporary failure",
|
||||
nci->rsys);
|
||||
reply("451 please try again soon from the same IP.\r\n");
|
||||
exits("no recent call for a rcpt");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue