123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- #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/grey/whitelist";
- /*
- * matches ip addresses or subnets in whitelist against nci->rsys.
- * ignores comments and blank lines in /mail/grey/whitelist.
- */
- static int
- onwhitelist(void)
- {
- int lnlen;
- char *line, *parse, *p;
- 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) {
- lnlen = Blinelen(wl);
- line[lnlen-1] = '\0'; /* clobber newline */
- p = strpbrk(line, " \t");
- if (p)
- *p = 0;
- if (line[0] == '#' || line[0] == 0)
- continue;
-
- /* default mask is /32 (v4) or /128 (v6) for bare IP */
- parse = line;
- if (strchr(line, '/') == nil) {
- strecpy(input, input+sizeof input-5, line);
- 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/tmp/%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 */
- fqdn = csgetvalue(nci->root, "ip", nci->rsys, "dom",
- nil);
- if (fqdn != nil)
- fprint(fd, "%s %s\n", nci->rsys, fqdn);
- else
- fprint(fd, "%s\n", nci->rsys);
- free(fqdn);
- 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");
- }
- }
|