3 * $Id: qmail-checkspam.c,v 1.2 2004/04/08 01:36:26 mdw Exp $
5 * Filter messages for spam
7 * (c) 2003 Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 /*----- Header files ------------------------------------------------------*/
36 #include <sys/types.h>
37 #include <sys/unistd.h>
43 /*----- Main code ---------------------------------------------------------*/
45 static const char *strenv(const char *e
, const char *d
)
47 const char *p
= getenv(e
);
52 static double dblenv(const char *e
, double d
)
54 const char *p
= getenv(e
);
62 syslog(LOG_ERR
, "bad floating value `%s' for %s'; ignoring", p
, e
);
69 static int intenv(const char *e
, int d
)
71 const char *p
= getenv(e
);
78 if (errno
) return (d
);
80 if (l
< 0 || l
> INT_MAX
) {
81 syslog(LOG_ERR
, "bad integer value `%s' for `%s'; ignoring", p
, e
);
87 static int safewrite(int fd
, const void *p
, size_t sz
)
93 n
= write(fd
, pp
, sz
);
94 if (sz
<= 0 && errno
!= EINTR
&& errno
!= EAGAIN
)
102 static int shovel(int from
, int to
, const char *what
)
108 n
= read(from
, buf
, sizeof(buf
));
109 if (n
< 0 && errno
!= EINTR
&& errno
!= EAGAIN
) {
110 syslog(LOG_ERR
, "failed to read %s: %m", what
);
115 if (safewrite(to
, buf
, n
) < 0) {
116 syslog(LOG_ERR
, "failed to write %s: %m", what
);
127 char buf
[ENVBUF
+ 1];
130 const char *rcpt
[NRCPT
];
134 static int readenv(struct envelope
*e
)
139 /* Read the raw envelope data. */
142 while (e
->n
< ENVBUF
) {
143 n
= read(1, e
->buf
+ e
->n
, ENVBUF
- e
->n
);
144 if (n
< 0 && errno
!= EINTR
&& errno
!= EAGAIN
) {
145 syslog(LOG_ERR
, "failed to read envelope: %m");
153 /* Parse up the envelope data. */
154 p
= e
->buf
; l
= p
+ e
->n
;
157 e
->send
= "<invalid>";
158 syslog(LOG_ERR
, "corrupt envelope (no sender marker)");
163 if (p
>= l
) goto trunc
;
164 else if (!*p
++) break;
168 if (p
>= l
) goto trunc
;
170 else if (*p
++ != 'T') {
171 syslog(LOG_ERR
, "corrupt envelope (no recipient marker)");
174 if (p
>= l
) goto trunc
;
175 if (e
->nrcpt
< NRCPT
) e
->rcpt
[e
->nrcpt
++] = p
;
177 if (p
>= l
) goto trunc
;
178 else if (!*p
++) break;
183 /* Failed to reach the final terminator; maybe there's more. This isn't a
184 * very bad situation.
191 static void real(void)
194 strenv("QMAIL_CHECKSPAM_QUEUE", "/var/qmail/bin/qmail-queue");
195 execlp(qmq
, qmq
, (char *)0);
196 syslog(LOG_ERR
, "failed to exec %s: %m", qmq
);
200 static int split(int *fd_m
, int *fd_e
)
205 if (pipe(pm
) || pipe(pe
)) return (-1);
206 if ((kid
= fork()) < 0) return (-1);
208 dup2(pm
[0], 0); close(pm
[0]); close(pm
[1]);
209 dup2(pe
[0], 1); close(pe
[0]); close(pe
[1]);
212 close(pm
[0]); *fd_m
= pm
[1];
213 close(pe
[0]); *fd_e
= pe
[1];
218 static int writemsg(struct message
*m
, int fd_m
, int shovelp
)
220 if (message_write(fd_m
, m
) < 0) {
221 syslog(LOG_ERR
, "failed to write message body: %m");
224 if (shovelp
&& shovel(0, fd_m
, "message body"))
230 static int writeenv(struct envelope
*e
, int fd_e
)
232 if (safewrite(fd_e
, e
->buf
, e
->n
) < 0) {
233 syslog(LOG_ERR
, "failed to write envelope: %m");
236 if ((e
->f
& EF_TRUNC
) && shovel(1, fd_e
, "envelope"))
242 static void logenv(struct envelope
*e
)
246 syslog(LOG_NOTICE
, "sender = %s", e
->send
);
247 for (i
= 0; i
< e
->nrcpt
; i
++)
248 syslog(LOG_NOTICE
, "recipient %lu = %s", (unsigned long)i
, e
->rcpt
[i
]);
251 int main(int argc
, char *argv
[])
261 /* Set up logging output. */
262 if ((p
= strrchr(argv
[0], '/')) == 0) p
= argv
[0]; else p
++;
263 openlog(p
, LOG_PID
, LOG_MAIL
);
265 /* Read configuration from the environment. */
266 if (getenv("RELAYCLIENT")) real();
267 m
.max_len
= intenv("QMAIL_CHECKSPAM_MAXLEN", 2 * 1024 * 1024);
268 m
.timeout
= intenv("QMAIL_CHECKSPAM_TIMEOUT", 300);
270 /* Slurp an initial chunk of the message. */
271 rc
= message_read(0, 0, &m
);
276 /* We read the message body OK. Now try to slurp in the envelope and
277 * write a useful message.
279 if (readenv(&e
)) return (54);
280 syslog(LOG_NOTICE
, "scanning message");
283 /* Filter the message and see what happens. */
284 p
= strenv("QMAIL_CHECKSPAM_SPAMDHOST", "localhost");
285 port
= intenv("QMAIL_CHECKSPAM_SPAMDPORT", 783);
286 if (lookup_host(p
, port
, &sa
)) {
287 syslog(LOG_ERR
, "lookup failed for host `%s', port %d", p
, port
);
290 if (message_filter(&sa
, "spamd", 0, &m
)) {
291 syslog(LOG_ERR
, "filter failed unexpectedly");
295 /* Check the resulting score. */
296 thresh
= dblenv("QMAIL_CHECKSPAM_THRESH", m
.threshold
);
297 if (m
.score
>= thresh
) {
298 syslog(LOG_NOTICE
, "rejecting: score %g >= %g", m
.score
, thresh
);
301 syslog(LOG_NOTICE
, "accepting: score %g < %g", m
.score
, thresh
);
303 /* Send the remaining stuff on to the real server. */
304 if (split(&fd_m
, &fd_e
)) return (56);
305 if (writemsg(&m
, fd_m
, 0) || writeenv(&e
, fd_e
)) _exit(127);
309 /* Message was too big. We must pass it all on to the real server
310 * before picking up the envelope.
312 syslog(LOG_NOTICE
, "message too large for filtering");
313 if (split(&fd_m
, &fd_e
)) return (56);
314 if (writemsg(&m
, fd_m
, 1) || readenv(&e
)) _exit(127);
316 if (writeenv(&e
, fd_e
)) _exit(127);
320 syslog(LOG_ERR
, "failed to read message (rc = %d): %m", rc
);
327 /*----- That's all, folks -------------------------------------------------*/