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