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