Blob


1 #include "common.h"
2 #include "smtpd.h"
3 #include "smtp.h"
4 #include <ctype.h>
5 #include <ip.h>
6 #include <ndb.h>
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;
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";
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;
44 if (!beenhere) {
45 beenhere = 1;
46 fmtinstall('I', eipfmt);
47 whitelist = unsharp(whitelist);
48 }
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 */
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);
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 }
84 static int mkdirs(char *);
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, '/');
96 if (sl != nil) {
97 *sl = '\0';
98 rv = mkdirs(path);
99 *sl = '/';
101 return rv;
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)
111 int fd;
113 if (access(path, AEXIST) >= 0)
114 return 0;
116 /* make presumed-missing intermediate directories */
117 if (mkpdirs(path) < 0)
118 return -1;
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;
132 static long
133 getmtime(char *file)
135 long mtime = -1;
136 Dir *ds = dirstat(file);
138 if (ds != nil) {
139 mtime = ds->mtime;
140 free(ds);
142 return mtime;
145 static void
146 tryaddgrey(char *file, Greysts *gsp)
148 int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
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;
169 static void
170 addgreylist(char *file, Greysts *gsp)
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);
178 static int
179 recentcall(Greysts *gsp)
181 long delay = time(0) - gsp->mtime;
183 if (!gsp->existed)
184 return 0;
185 /* reject immediate call-back; spammers are doing that now */
186 return delay >= 30 && delay <= Nonspammax;
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.
198 * greylist files can be removed at will; at worst they'll cause a few
199 * extra retries.
200 */
202 static int
203 isrcptrecent(char *rcpt)
205 char *user;
206 char file[256];
207 Greysts gs;
208 Greysts *gsp = &gs;
210 if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
211 strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
212 return 0;
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++;
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);
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;
241 void
242 vfysenderhostok(void)
244 int recent = 0;
245 Link *l;
247 if (onwhitelist())
248 return;
250 for (l = rcvers.first; l; l = l->next)
251 if (isrcptrecent(s_to_c(l->p)))
252 recent = 1;
254 /* if on greylist already and prior call was recent, add to whitelist */
255 if (recent) {
256 int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
258 if (fd >= 0) {
259 seek(fd, 0, 2); /* paranoia */
260 fprint(fd, "# unknown\n%s\n\n", nci->rsys);
261 close(fd);
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");