Mercurial > dnsbl
annotate src/spamass.cpp.in @ 260:7c05043a220e
add recipient rate limits by email from address or domain
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Sat, 21 Jul 2012 08:39:30 -0700 |
parents | 536d59c3a7b8 |
children | 78eedbbce636 |
rev | line source |
---|---|
163 | 1 /* |
2 | |
3 Copyright (c) 2007 Carl Byington - 510 Software Group, released under | |
4 the GPL version 3 or any later version at your choice available at | |
5 http://www.gnu.org/licenses/gpl-3.0.txt | |
6 | |
7 Based on spamass-milter by Georg C. F. Greve <greve@gnu.org> | |
8 | |
9 */ | |
10 | |
177 | 11 #include "includes.h" |
163 | 12 #include <errno.h> |
13 #include <fcntl.h> | |
14 #include <poll.h> | |
15 #include <signal.h> | |
16 #include <string> | |
17 #include <sys/types.h> | |
18 #include <sys/wait.h> | |
19 #include <unistd.h> | |
20 | |
21 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
22 const char *spamc = "@SPAMC@"; |
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
23 const char *spamc_empty = ""; |
177 | 24 static bool warnedmacro = false; // have we logged that we couldn't fetch a macro? |
25 const int maxlen = 1000; // used for snprintf buffers | |
163 | 26 |
27 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
28 SpamAssassin::SpamAssassin(mlfiPriv *priv_, int ip, const char *helo_, const char *from, const char *qid) |
163 | 29 { |
30 error = false; | |
31 running = false; | |
32 first_recipient = true; | |
167
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
33 priv = priv_; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
34 ip4 = ip; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
35 helo = helo_; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
36 envfrom = from; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
37 queueid = qid; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
38 pid = 0; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
39 pipe_io[0][0] = -1; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
40 pipe_io[0][1] = -1; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
41 pipe_io[1][0] = -1; |
9b129ed78d7d
actually use spamassassin result, allow build without spam assassin, only call it if some recipient needs it.
carl
parents:
164
diff
changeset
|
42 pipe_io[1][1] = -1; |
163 | 43 } |
44 | |
45 | |
46 SpamAssassin::~SpamAssassin() | |
47 { | |
48 // close all pipes that are still open | |
49 if (pipe_io[0][0] > -1) close(pipe_io[0][0]); | |
50 if (pipe_io[0][1] > -1) close(pipe_io[0][1]); | |
51 if (pipe_io[1][0] > -1) close(pipe_io[1][0]); | |
52 if (pipe_io[1][1] > -1) close(pipe_io[1][1]); | |
53 // child still running? | |
54 if (running) { | |
55 // make sure the pid is valid | |
56 if (pid > 0) { | |
57 // slaughter child | |
58 kill(pid, SIGKILL); | |
59 // wait for child to terminate | |
60 int status; | |
61 waitpid(pid, &status, 0); | |
62 } | |
63 } | |
64 } | |
65 | |
66 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
67 void SpamAssassin::mlfi_envrcpt(SMFICTX *ctx, const char *envrcpt) |
163 | 68 { |
69 if (first_recipient) { | |
177 | 70 first_recipient = false; |
163 | 71 /* Send the envelope headers as X-Envelope-From: and |
72 X-Envelope-To: so that SpamAssassin can use them in its | |
73 whitelist checks. Also forge as complete a dummy | |
74 Received: header as possible because SA gets a lot of | |
75 info from it. | |
76 | |
77 HReceived: $?sfrom $s $.$?_($?s$|from $.$_) | |
78 $.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.) | |
79 $.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version} | |
80 (version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u | |
81 for $u; $|; | |
82 $.$b$?g | |
83 (envelope-from $g)$. | |
84 | |
85 */ | |
86 const char *macro_b, *macro_i, *macro_j, *macro_r, | |
87 *macro_s, *macro_v, *macro_Z, *macro__; | |
88 char date[32]; | |
89 time_t tval; | |
90 time(&tval); | |
91 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&tval)); | |
92 macro_b = date; | |
93 | |
168 | 94 // queue ID |
163 | 95 macro_i = queueid; |
96 | |
168 | 97 // FQDN of this site |
98 macro_j = getorwarnmacro(ctx, "j", "localhost", "ENVRCPT"); | |
163 | 99 |
170 | 100 // Protocol used to receive the message, not really needed by spam assassin |
101 macro_r = "SMTP"; | |
163 | 102 |
170 | 103 // helo value we already have |
163 | 104 macro_s = helo; |
105 | |
170 | 106 // Sendmail binary version, not really needed by spam assassin |
107 macro_v = "8.13.0"; | |
163 | 108 |
170 | 109 // Sendmail .cf version, not really needed by spam assassin |
110 macro_Z = "8.13.0"; | |
163 | 111 |
168 | 112 // Validated sending site's address |
170 | 113 macro__ = getorwarnmacro(ctx, "_", "unknown", "ENVRCPT"); |
163 | 114 |
115 output(string("Received: from ") + macro_s + " (" + macro__+ ")\r\n\t" + | |
116 "by " + macro_j + " (" + macro_v + "/" + macro_Z + ") with " + macro_r + " id " + macro_i + "\r\n\t" + | |
117 "for " + envfrom + ";\r\n\t" + | |
118 macro_b + "\r\n"); | |
119 | |
120 output(string("X-Envelope-From: ") + envfrom + "\r\n"); | |
121 } | |
122 output(string("X-Envelope-To: ") + envrcpt + "\r\n"); | |
123 } | |
124 | |
125 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
126 void SpamAssassin::mlfi_header(const char* headerf, const char* headerv) |
163 | 127 { |
128 if (!running) Connect(); | |
177 | 129 if (running) { |
130 output(spamc_input); | |
227
3fee608becbc
Fixes to compile on old systems without memrchr or string::clear().
Carl Byington <carl@five-ten-sg.com>
parents:
214
diff
changeset
|
131 spamc_input = ""; |
177 | 132 } |
133 | |
163 | 134 output(headerf); |
135 output(": "); | |
136 output(headerv); | |
137 output("\r\n"); | |
138 } | |
139 | |
140 | |
141 void SpamAssassin::mlfi_eoh() | |
142 { | |
143 output("\r\n"); | |
144 } | |
145 | |
146 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
147 void SpamAssassin::mlfi_body(const u_char *bodyp, size_t bodylen) |
163 | 148 { |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
149 output((const char *)bodyp, bodylen); |
163 | 150 } |
151 | |
152 | |
153 int SpamAssassin::mlfi_eom() | |
154 { | |
155 close_output(); // signal EOF to SpamAssassin | |
156 input(); // read what the Assassin is telling us | |
157 my_syslog(priv, "spamc returned " + spamc_output); | |
158 return atoi(spamc_output.c_str()); | |
159 } | |
160 | |
161 | |
162 void SpamAssassin::Connect() | |
163 { | |
164 if (error) return; | |
165 // set up pipes for in- and output | |
166 error |= (pipe(pipe_io[0])); | |
167 error |= (pipe(pipe_io[1])); | |
168 if (error) return; | |
169 | |
170 // now execute SpamAssassin client for contact with SpamAssassin spamd | |
171 // start child process | |
172 pid = fork(); | |
173 switch (pid) { | |
174 case -1: | |
175 // forking trouble. | |
176 my_syslog(priv, "unable to fork for spamc"); | |
177 error = true; | |
178 close(pipe_io[0][0]); | |
179 close(pipe_io[0][1]); | |
180 close(pipe_io[1][0]); | |
181 close(pipe_io[1][1]); | |
182 pipe_io[0][0] = -1; | |
183 pipe_io[0][1] = -1; | |
184 pipe_io[1][0] = -1; | |
185 pipe_io[1][1] = -1; | |
186 return; | |
187 case 0: | |
188 // +++ CHILD +++ | |
189 | |
190 // close unused pipes | |
191 close(pipe_io[1][0]); | |
192 close(pipe_io[0][1]); | |
193 | |
194 // redirect stdin(0), stdout(1) and stderr(2) | |
195 dup2(pipe_io[0][0],0); | |
196 dup2(pipe_io[1][1],1); | |
197 dup2(pipe_io[1][1],2); | |
198 | |
199 closeall(3); | |
200 | |
201 // execute spamc | |
202 char* argv[3]; | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
203 argv[0] = (char*)spamc; |
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
204 argv[1] = (char*)"-c"; |
163 | 205 argv[2] = NULL; |
206 execvp(argv[0] , argv); // does not return! | |
207 _exit(1); // exec failed | |
208 break; | |
209 } | |
210 | |
211 // +++ PARENT +++ | |
212 | |
213 // close unused pipes | |
214 close(pipe_io[0][0]); | |
215 close(pipe_io[1][1]); | |
216 pipe_io[0][0] = -1; | |
217 pipe_io[1][1] = -1; | |
218 | |
219 // mark the pipes non-blocking | |
220 if (fcntl(pipe_io[0][1], F_SETFL, O_NONBLOCK) == -1) | |
221 error = true; | |
222 #if 0 /* don't really need to make the sink pipe nonblocking */ | |
223 if (fcntl(pipe_io[1][0], F_SETFL, O_NONBLOCK) == -1) | |
224 error = true; | |
225 #endif | |
226 | |
227 // we have to assume the client is running now. | |
228 running = true; | |
229 } | |
230 | |
231 | |
232 void SpamAssassin::output(const char* buffer, size_t size) | |
233 { | |
234 // if there are problems, fail. | |
235 if (error) return; | |
236 | |
237 if (!running) { | |
238 // buffer it | |
239 spamc_input.append(buffer, size); | |
240 return; | |
241 } | |
242 | |
243 // send to SpamAssassin | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
244 size_t total = 0; |
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
245 size_t wsize = 0; |
163 | 246 string reason; |
247 int status; | |
248 do { | |
249 struct pollfd fds[2]; | |
250 int nfds = 2, nready; | |
251 fds[0].fd = pipe_io[0][1]; | |
252 fds[0].events = POLLOUT; | |
253 fds[0].revents = 0; | |
254 fds[1].fd = pipe_io[1][0]; | |
255 fds[1].events = POLLIN; | |
256 fds[1].revents = 0; | |
257 | |
258 nready = poll(fds, nfds, 1000); | |
259 if (nready == -1) { | |
260 my_syslog(priv, "poll failed"); | |
261 error = true; | |
262 return; | |
263 } | |
264 | |
240
536d59c3a7b8
pollnval might be triggered on the last pollin data chunk
Carl Byington <carl@five-ten-sg.com>
parents:
227
diff
changeset
|
265 if (fds[1].revents & (POLLERR|POLLNVAL)) { |
163 | 266 my_syslog(priv, "poll says my read pipe is busted"); |
267 error = true; | |
268 return; | |
269 } | |
270 | |
271 if (fds[0].revents & (POLLERR|POLLNVAL|POLLHUP)) { | |
272 my_syslog(priv, "poll says my write pipe is busted"); | |
273 error = true; | |
274 return; | |
275 } | |
276 | |
277 if (fds[1].revents & POLLIN) { | |
278 read_pipe(); | |
279 } | |
280 | |
281 if (fds[0].revents & POLLOUT) { | |
282 switch(wsize = write(pipe_io[0][1], (char *)buffer + total, size - total)) { | |
283 case -1: | |
284 if (errno == EAGAIN) continue; | |
285 reason = string(strerror(errno)); | |
286 // close the pipes | |
287 close(pipe_io[0][1]); | |
288 close(pipe_io[1][0]); | |
289 pipe_io[0][1] = -1; | |
290 pipe_io[1][0] = -1; | |
291 // Slaughter child | |
292 kill(pid, SIGKILL); | |
293 // wait for child to terminate | |
294 waitpid(pid, &status, 0); | |
295 my_syslog(priv, "write error: " + reason); | |
296 error = true; | |
297 running = false; | |
298 return; | |
299 default: | |
300 total += wsize; | |
301 break; | |
302 } | |
303 } | |
304 } while ( total < size ); | |
305 } | |
306 | |
307 | |
308 void SpamAssassin::output(const char* buffer) | |
309 { | |
310 output(buffer, strlen(buffer)); | |
311 } | |
312 | |
313 | |
314 void SpamAssassin::output(string buffer) | |
315 { | |
316 output(buffer.c_str(), buffer.size()); | |
317 } | |
318 | |
319 | |
320 void SpamAssassin::close_output() | |
321 { | |
322 if (close(pipe_io[0][1])) | |
323 my_syslog(priv, "close error: " + string(strerror(errno))); | |
324 pipe_io[0][1] = -1; | |
325 } | |
326 | |
327 | |
328 void SpamAssassin::input() | |
329 { | |
330 if (!running || error) return; | |
331 empty_and_close_pipe(); | |
332 if (running) { | |
333 // wait until child is dead | |
334 int status; | |
335 if (waitpid(pid, &status, 0) < 0) { | |
336 error = true; | |
337 }; | |
338 } | |
339 running = false; | |
340 } | |
341 | |
342 | |
343 int SpamAssassin::read_pipe() | |
344 { | |
345 long size; | |
346 int status; | |
347 char iobuff[1024]; | |
348 string reason; | |
349 | |
350 if (pipe_io[1][0] == -1) return 0; | |
351 | |
352 size = read(pipe_io[1][0], iobuff, 1024); | |
353 | |
354 if (size < 0) { | |
355 reason = string(strerror(errno)); | |
356 // Close remaining pipe. | |
357 close(pipe_io[1][0]); | |
358 pipe_io[1][0] = -1; | |
359 // Slaughter child | |
360 kill(pid, SIGKILL); | |
361 // wait for child to terminate | |
362 waitpid(pid, &status, 0); | |
363 my_syslog(priv, "read error: " + reason); | |
364 size = 0; | |
365 error = true; | |
366 running = false; | |
367 } else if (size == 0) { | |
368 // EOF. Close the pipe | |
369 if (close(pipe_io[1][0])) { | |
370 error = true; | |
371 my_syslog(priv, "close error: " + string(strerror(errno))); | |
372 } | |
373 pipe_io[1][0] = -1; | |
374 } else { | |
375 // append to mail buffer | |
376 spamc_output.append(iobuff, size); | |
377 } | |
378 return size; | |
379 } | |
380 | |
381 | |
382 void SpamAssassin::empty_and_close_pipe() | |
383 { | |
384 while (read_pipe()) | |
385 ; | |
386 } | |
387 | |
388 | |
389 void SpamAssassin::closeall(int fd) | |
390 { | |
391 int fdlimit = sysconf(_SC_OPEN_MAX); | |
392 while (fd < fdlimit) | |
393 close(fd++); | |
394 } | |
395 | |
396 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
397 const char *SpamAssassin::getorwarnmacro(SMFICTX *ctx, const char *macro, const char *def, const char *scope) |
168 | 398 { |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
399 const char *rc = smfi_getsymval(ctx, (char*)macro); |
168 | 400 if (!rc) { |
401 rc = def; | |
402 warnmacro(macro, scope); | |
403 } | |
404 return rc; | |
405 } | |
406 | |
407 | |
214
82886d4dd71f
Fixes to compile on Fedora 9 and for const correctness.
Carl Byington <carl@five-ten-sg.com>
parents:
203
diff
changeset
|
408 void SpamAssassin::warnmacro(const char *macro, const char *scope) |
163 | 409 { |
410 if (warnedmacro) return; | |
411 char buf[maxlen]; | |
412 snprintf(buf, sizeof(buf), "Could not retrieve sendmail macro %s. Add it to confMILTER_MACROS_%s for better results.", macro, scope); | |
413 my_syslog(priv, buf); | |
414 warnedmacro = true; | |
415 } | |
416 |