URI: 
       tgreylist.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tgreylist.c (6351B)
       ---
            1 #include "common.h"
            2 #include "smtpd.h"
            3 #include "smtp.h"
            4 #include <ctype.h>
            5 #include <ip.h>
            6 #include <ndb.h>
            7 
            8 typedef struct {
            9         int        existed;        /* these two are distinct to cope with errors */
           10         int        created;
           11         int        noperm;
           12         long        mtime;                /* mod time, iff it already existed */
           13 } Greysts;
           14 
           15 /*
           16  * There's a bit of a problem with yahoo; they apparently have a vast
           17  * pool of machines that all run the same queue(s), so a 451 retry can
           18  * come from a different IP address for many, many retries, and it can
           19  * take ~5 hours for the same IP to call us back.  Various other goofballs,
           20  * notably the IEEE, try to send mail just before 9 AM, then refuse to try
           21  * again until after 5 PM.  Doh!
           22  */
           23 enum {
           24         Nonspammax = 14*60*60,  /* must call back within this time if real */
           25 };
           26 static char *whitelist = "#9/mail/lib/whitelist";
           27 
           28 /*
           29  * matches ip addresses or subnets in whitelist against nci->rsys.
           30  * ignores comments and blank lines in /mail/lib/whitelist.
           31  */
           32 static int
           33 onwhitelist(void)
           34 {
           35         int lnlen;
           36         char *line, *parse;
           37         char input[128];
           38         uchar ip[IPaddrlen], ipmasked[IPaddrlen];
           39         uchar mask4[IPaddrlen], addr4[IPaddrlen];
           40         uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
           41         Biobuf *wl;
           42         static int beenhere;
           43 
           44         if (!beenhere) {
           45                 beenhere = 1;
           46                 fmtinstall('I', eipfmt);
           47                 whitelist = unsharp(whitelist);
           48         }
           49 
           50         parseip(ip, nci->rsys);
           51         wl = Bopen(whitelist, OREAD);
           52         if (wl == nil)
           53                 return 1;
           54         while ((line = Brdline(wl, '\n')) != nil) {
           55                 if (line[0] == '#' || line[0] == '\n')
           56                         continue;
           57                 lnlen = Blinelen(wl);
           58                 line[lnlen-1] = '\0';                /* clobber newline */
           59 
           60                 /* default mask is /32 (v4) or /128 (v6) for bare IP */
           61                 parse = line;
           62                 if (strchr(line, '/') == nil) {
           63                         strncpy(input, line, sizeof input - 5);
           64                         if (strchr(line, '.') != nil)
           65                                 strcat(input, "/32");
           66                         else
           67                                 strcat(input, "/128");
           68                         parse = input;
           69                 }
           70                 /* sorry, dave; where's parsecidr for v4 or v6? */
           71                 v4parsecidr(addr4, mask4, parse);
           72                 v4tov6(addr, addr4);
           73                 v4tov6(mask, mask4);
           74 
           75                 maskip(addr, mask, addrmasked);
           76                 maskip(ip, mask, ipmasked);
           77                 if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
           78                         break;
           79         }
           80         Bterm(wl);
           81         return line != nil;
           82 }
           83 
           84 static int mkdirs(char *);
           85 
           86 /*
           87  * if any directories leading up to path don't exist, create them.
           88  * modifies but restores path.
           89  */
           90 static int
           91 mkpdirs(char *path)
           92 {
           93         int rv = 0;
           94         char *sl = strrchr(path, '/');
           95 
           96         if (sl != nil) {
           97                 *sl = '\0';
           98                 rv = mkdirs(path);
           99                 *sl = '/';
          100         }
          101         return rv;
          102 }
          103 
          104 /*
          105  * if path or any directories leading up to it don't exist, create them.
          106  * modifies but restores path.
          107  */
          108 static int
          109 mkdirs(char *path)
          110 {
          111         int fd;
          112 
          113         if (access(path, AEXIST) >= 0)
          114                 return 0;
          115 
          116         /* make presumed-missing intermediate directories */
          117         if (mkpdirs(path) < 0)
          118                 return -1;
          119 
          120         /* make final directory */
          121         fd = create(path, OREAD, 0777|DMDIR);
          122         if (fd < 0)
          123                 /*
          124                  * we may have lost a race; if the directory now exists,
          125                  * it's okay.
          126                  */
          127                 return access(path, AEXIST) < 0? -1: 0;
          128         close(fd);
          129         return 0;
          130 }
          131 
          132 static long
          133 getmtime(char *file)
          134 {
          135         long mtime = -1;
          136         Dir *ds = dirstat(file);
          137 
          138         if (ds != nil) {
          139                 mtime = ds->mtime;
          140                 free(ds);
          141         }
          142         return mtime;
          143 }
          144 
          145 static void
          146 tryaddgrey(char *file, Greysts *gsp)
          147 {
          148         int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
          149 
          150         gsp->created = (fd >= 0);
          151         if (fd >= 0) {
          152                 close(fd);
          153                 gsp->existed = 0;  /* just created; couldn't have existed */
          154         } else {
          155                 /*
          156                  * why couldn't we create file? it must have existed
          157                  * (or we were denied perm on parent dir.).
          158                  * if it existed, fill in gsp->mtime; otherwise
          159                  * make presumed-missing intermediate directories.
          160                  */
          161                 gsp->existed = access(file, AEXIST) >= 0;
          162                 if (gsp->existed)
          163                         gsp->mtime = getmtime(file);
          164                 else if (mkpdirs(file) < 0)
          165                         gsp->noperm = 1;
          166         }
          167 }
          168 
          169 static void
          170 addgreylist(char *file, Greysts *gsp)
          171 {
          172         tryaddgrey(file, gsp);
          173         if (!gsp->created && !gsp->existed && !gsp->noperm)
          174                 /* retry the greylist entry with parent dirs created */
          175                 tryaddgrey(file, gsp);
          176 }
          177 
          178 static int
          179 recentcall(Greysts *gsp)
          180 {
          181         long delay = time(0) - gsp->mtime;
          182 
          183         if (!gsp->existed)
          184                 return 0;
          185         /* reject immediate call-back; spammers are doing that now */
          186         return delay >= 30 && delay <= Nonspammax;
          187 }
          188 
          189 /*
          190  * policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
          191  * reject this message as "451 temporary failure".  if the caller is real,
          192  * he'll retry soon, otherwise he's a spammer.
          193  * at the first rejection, create a greylist entry for (my-ip, caller-ip,
          194  * rcpt, time), where time is the file's mtime.  if they call back and there's
          195  * already a greylist entry, and it's within the allowed interval,
          196  * add their IP to the append-only whitelist.
          197  *
          198  * greylist files can be removed at will; at worst they'll cause a few
          199  * extra retries.
          200  */
          201 
          202 static int
          203 isrcptrecent(char *rcpt)
          204 {
          205         char *user;
          206         char file[256];
          207         Greysts gs;
          208         Greysts *gsp = &gs;
          209 
          210         if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
          211             strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
          212                 return 0;
          213 
          214         /* shorten names to fit pre-fossil or pre-9p2000 file servers */
          215         user = strrchr(rcpt, '!');
          216         if (user == nil)
          217                 user = rcpt;
          218         else
          219                 user++;
          220 
          221         /* check & try to update the grey list entry */
          222         snprint(file, sizeof file, "%s/mail/grey/%s/%s/%s",
          223                 get9root(), nci->lsys, nci->rsys, user);
          224         memset(gsp, 0, sizeof *gsp);
          225         addgreylist(file, gsp);
          226 
          227         /* if on greylist already and prior call was recent, add to whitelist */
          228         if (gsp->existed && recentcall(gsp)) {
          229                 syslog(0, "smtpd",
          230                         "%s/%s was grey; adding IP to white", nci->rsys, rcpt);
          231                 return 1;
          232         } else if (gsp->existed)
          233                 syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
          234                         nci->rsys, rcpt);
          235         else
          236                 syslog(0, "smtpd", "no call registered for %s/%s; registering",
          237                         nci->rsys, rcpt);
          238         return 0;
          239 }
          240 
          241 void
          242 vfysenderhostok(void)
          243 {
          244         int recent = 0;
          245         Link *l;
          246 
          247         if (onwhitelist())
          248                 return;
          249 
          250         for (l = rcvers.first; l; l = l->next)
          251                 if (isrcptrecent(s_to_c(l->p)))
          252                         recent = 1;
          253 
          254         /* if on greylist already and prior call was recent, add to whitelist */
          255         if (recent) {
          256                 int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
          257 
          258                 if (fd >= 0) {
          259                         seek(fd, 0, 2);                        /* paranoia */
          260                         fprint(fd, "# unknown\n%s\n\n", nci->rsys);
          261                         close(fd);
          262                 }
          263         } else {
          264                 syslog(0, "smtpd",
          265         "no recent call from %s for a rcpt; rejecting with temporary failure",
          266                         nci->rsys);
          267                 reply("451 please try again soon from the same IP.\r\n");
          268                 exits("no recent call for a rcpt");
          269         }
          270 }