greylist.c 7.5 KB

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