greylist.c 6.4 KB

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