greylist.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 }