Build system: detect/set CC and CFLAGS, _buh -> devel, plus..
[s-mailx.git] / spam.c
blobed61c1982b23e22032279f434e702bff76848c4e
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Spam related facilities.
4 * Copyright (c) 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include "config.h"
21 #ifndef HAVE_SPAM
22 typedef int avoid_empty_file_compiler_warning;
23 #else
24 #include "nail.h"
27 * TODO - We cannot use the spamc library because of our jumping behaviour.
28 * TODO We could nonetheless if we'd start a fork(2)ed child which would
29 * TODO use the spamc library.
30 * TODO -- In fact using a child process that is immune from the terrible
31 * TODO signal and jumping mess, and that controls further childs, and
32 * TODO gains file descriptors via sendmsg(2), and is started once it is
33 * TODO needed first, i have in mind for quite some time, for the transition
34 * TODO to a select(2) based implementation: we could slowly convert SMTP,
35 * TODO etc., finally IMAP, at which case we could rejoin back into a single
36 * TODO process program (unless we want to isolate the UI at that time, to
37 * TODO allow for some xUI protocol).
38 * TODO :: That is to say -- it's a horrible signal and jump mess ::
39 * TODO - We do not yet handle direct communication with spamd(1).
40 * TODO I.e., this could be a lean alternative to the first item;
41 * TODO the protocol is easy and we could support ALL operations easily.
42 * TODO - We do not yet update mails in place, i.e., replace the original
43 * TODO message with the updated one; we could easily do it as if `edit' has
44 * TODO been used, but the nail codebase doesn't truly support that for IMAP.
45 * TODO (And it seems a bit grazy to download a message, update it and upload
46 * TODO it again.)
49 enum spam_action {
50 _SPAM_RATE,
51 _SPAM_HAM,
52 _SPAM_SPAM,
53 _SPAM_FORGET
56 struct spam_vc {
57 struct message * mp;
58 size_t mno;
59 int action;
60 int __dummy;
61 char * comm_s;
62 char * buffer;
63 /* TODO This codebase jumps around and uses "stacks" of signal handling;
64 * TODO until some later time we have to play the same game */
65 sighandler_type otstp;
66 sighandler_type ottin;
67 sighandler_type ottou;
68 sighandler_type ohup;
69 sighandler_type opipe;
70 sighandler_type oint;
71 char const * comm_a[16];
74 /* Indices according to enum spam_action */
75 static char const _spam_comms[][16] = {
76 "spamrate", "spamham", "spamspam", "spamforget"
79 /* Shared action setup */
80 static bool_t _spam_action(enum spam_action sa, int *ip);
82 /* Convert a 2[.x]/whatever spam rate into message.m_spamscore */
83 static void _spam_rate2score(struct spam_vc *vc);
85 /* Interact with spamc(1) */
86 static bool_t _spam_interact(struct spam_vc *vc);
88 static bool_t
89 _spam_action(enum spam_action sa, int *ip)
91 struct spam_vc vc;
92 struct str str;
93 size_t maxsize;
94 char const *cp, **args;
95 bool_t ok = FAL0;
97 vc.action = sa;
99 if ((cp = voption("spam-command")) == NULL) {
100 #ifdef SPAMC_PATH
101 cp = SPAMC_PATH;
102 #else
103 fprintf(stderr, tr(514, "`%s': *spam-command* is not set\n"),
104 _spam_comms[sa]);
105 goto jleave;
106 #endif
109 /* Prepare the spamc(1) command line */
110 args = vc.comm_a;
111 *args++ = cp;
113 switch (sa) {
114 case _SPAM_RATE:
115 *args = "-c";
116 break;
117 case _SPAM_HAM:
118 args[1] = "ham";
119 goto jlearn;
120 case _SPAM_SPAM:
121 args[1] = "spam";
122 goto jlearn;
123 case _SPAM_FORGET:
124 args[1] = "forget";
125 jlearn:
126 *args = "-L";
127 ++args;
128 break;
130 ++args;
132 if ((cp = voption("spam-socket")) != NULL) {
133 *args++ = "-U";
134 *args++ = cp;
135 } else {
136 if ((cp = voption("spam-host")) != NULL) {
137 *args++ = "-d";
138 *args++ = cp;
140 if ((cp = voption("spam-port")) != NULL) {
141 *args++ = "-p";
142 *args++ = cp;
146 *args++ = "-l"; /* --log-to-stderr */
148 if ((cp = voption("spam-user")) != NULL) {
149 *args++ = "-u";
150 *args++ = cp;
153 *args = NULL;
154 vc.comm_s = str_concat_cpa(&str, vc.comm_a, " ")->s;
155 if (options & OPT_DEBUG)
156 fprintf(stderr, "spamc(1) via <%s>\n", vc.comm_s);
158 /* *spam-maxsize* we do handle ourselfs instead */
159 maxsize = 0;
160 if ((cp = voption("spam-maxsize")) != NULL)
161 maxsize = (size_t)strtol(cp, NULL, 10);
162 if (maxsize <= 0)
163 maxsize = SPAM_MAXSIZE;
165 /* Finally get an I/O buffer */
166 vc.buffer = salloc(BUFFER_SIZE);
168 for (ok = TRU1; *ip != 0; ++ip) {
169 vc.mno = (size_t)*ip - 1;
170 vc.mp = message + vc.mno;
171 if (sa == _SPAM_RATE)
172 vc.mp->m_spamscore = 0;
173 if (vc.mp->m_size > maxsize) {
174 if (options & OPT_VERBOSE)
175 fprintf(stderr, tr(515,
176 "`%s': message %lu exceeds maxsize (%lu > %lu), skip\n"),
177 _spam_comms[sa], (ul_it)vc.mno + 1,
178 (ul_it)vc.mp->m_size, (ul_it)maxsize);
179 continue;
181 if ((ok = _spam_interact(&vc)) == FAL0)
182 break;
184 #ifndef SPAMC_PATH
185 jleave:
186 #endif
187 return ! ok;
190 static void
191 _spam_rate2score(struct spam_vc *vc)
193 char *cp;
194 size_t size;
195 ui_it m, s;
197 cp = strchr(vc->buffer, '/');
198 if (cp == NULL)
199 goto jleave;
200 size = (size_t)(cp - vc->buffer);
201 vc->buffer[size] = '\0';
203 m = (ui_it)strtol(vc->buffer, &cp, 10);
204 if (cp == vc->buffer)
205 goto jleave;
207 s = (*cp == '\0') ? 0 : (ui_it)strtol(++cp, NULL, 10);
209 vc->mp->m_spamscore = (m << 8) | (s & 0xFF);
210 jleave:
214 static sigjmp_buf __spam_actjmp; /* TODO someday, we won't need it no more */
215 static int __spam_sig; /* TODO someday, we won't need it no more */
216 static void
217 __spam_onsig(int sig) /* TODO someday, we won't need it no more */
219 __spam_sig = sig;
220 siglongjmp(__spam_actjmp, 1);
223 static bool_t
224 _spam_interact(struct spam_vc *vc)
226 int p2c[2], c2p[2];
227 sigset_t cset;
228 size_t size;
229 pid_t pid;
230 FILE *ibuf;
231 enum {
232 _NONE = 0,
233 _SIGHOLD = 1<<0,
234 _P2C_0 = 1<<1,
235 _P2C_1 = 1<<2,
236 _P2C = _P2C_0 | _P2C_1,
237 _C2P_0 = 1<<3,
238 _C2P_1 = 1<<4,
239 _C2P = _C2P_0 | _C2P_1,
240 _JUMPED = 1<<5,
241 _RUNNING = 1<<6,
242 _GOODRUN = 1<<7,
243 _ERRORS = 1<<8
244 } state = _NONE;
246 setdot(vc->mp);
247 if ((ibuf = setinput(&mb, vc->mp, NEED_BODY)) == NULL) {
248 perror("setinput"); /* XXX tr() */
249 goto j_leave;
252 /* TODO Avoid that we jump away; yet necessary signal mess */
253 vc->otstp = safe_signal(SIGTSTP, SIG_DFL);
254 vc->ottin = safe_signal(SIGTTIN, SIG_DFL);
255 vc->ottou = safe_signal(SIGTTOU, SIG_DFL);
256 vc->opipe = safe_signal(SIGPIPE, SIG_IGN);
257 holdsigs();
258 state |= _SIGHOLD;
259 vc->ohup = safe_signal(SIGHUP, &__spam_onsig);
260 vc->oint = safe_signal(SIGINT, &__spam_onsig);
261 /* Keep sigs blocked */
262 pid = 0; /* cc uninit */
264 if (! pipe_cloexec(p2c)) {
265 perror("pipe"); /* XXX tr() */
266 goto jleave;
268 state |= _P2C;
270 if (! pipe_cloexec(c2p)) {
271 perror("pipe"); /* XXX tr() */
272 goto jleave;
274 state |= _C2P;
276 if (sigsetjmp(__spam_actjmp, 1)) {
277 state |= _JUMPED;
278 goto jleave;
280 relsesigs();
281 state &= ~_SIGHOLD;
283 sigemptyset(&cset);
284 pid = start_command(vc->comm_s, &cset, p2c[0], c2p[1], NULL, NULL, NULL);
285 state |= _RUNNING;
286 close(p2c[0]);
287 state &= ~_P2C_0;
289 /* Yes, we could sendmp(SEND_MBOX), but simply passing through the MBOX
290 * content does the same in effect, but is much more efficient.
291 * NOTE: this may mean we pass a message without From_ line! */
292 for (size = vc->mp->m_size; size > 0;) {
293 size_t i = fread(vc->buffer, 1, MIN(size, BUFFER_SIZE), ibuf);
294 if (i == 0) {
295 if (ferror(ibuf))
296 state |= _ERRORS;
297 break;
299 size -= i;
300 if (i != (size_t)write(p2c[1], vc->buffer, i)) {
301 state |= _ERRORS;
302 break;
306 jleave:
307 /* In what follows you see a lot of races; these can't be helped without
308 * atomic compare-and-swap; it only matters if we */
309 if (state & _SIGHOLD) {
310 state &= ~_SIGHOLD;
311 relsesigs();
314 if (state & _P2C_0) {
315 state &= ~_P2C_0;
316 close(p2c[0]);
318 if (state & _C2P_1) {
319 state &= ~_C2P_1;
320 close(c2p[1]);
322 /* Close the write end, so that spamc(1) goes */
323 if (state & _P2C_1) {
324 state &= ~_P2C_1;
325 close(p2c[1]);
328 if (state & _RUNNING) {
329 state &= ~_RUNNING;
330 if (wait_child(pid) == 0)
331 state |= _GOODRUN;
334 /* XXX This only works because spamc(1) follows the clear protocol (1) read
335 * XXX everything until EOF on input, then (2) work, then (3) output
336 * XXX a single result line; otherwise we could deadlock here, but since
337 * TODO this is rather intermediate, go with it */
338 if (vc->action == _SPAM_RATE && ! (state & (_JUMPED | _ERRORS))) {
339 ssize_t i = read(c2p[0], vc->buffer, BUFFER_SIZE - 1);
340 if (i > 0) {
341 vc->buffer[i] = '\0';
342 _spam_rate2score(vc);
343 } else if (i != 0)
344 state |= _ERRORS;
347 if (state & _C2P_0) {
348 state &= ~_C2P_0;
349 close(c2p[0]);
352 if (vc->action == _SPAM_RATE) {
353 switch (state & (_JUMPED | _GOODRUN | _ERRORS)) {
354 case _GOODRUN:
355 vc->mp->m_flag &= ~MSPAM;
356 break;
357 case 0:
358 vc->mp->m_flag |= MSPAM;
359 default:
360 break;
362 } else {
363 if (state & (_JUMPED | _ERRORS))
364 /* xxx print message? */;
365 else if (vc->action == _SPAM_SPAM)
366 vc->mp->m_flag |= MSPAM;
367 else if (vc->action == _SPAM_HAM)
368 vc->mp->m_flag &= ~MSPAM;
371 safe_signal(SIGINT, vc->oint);
372 safe_signal(SIGHUP, vc->ohup);
373 safe_signal(SIGPIPE, vc->opipe);
374 safe_signal(SIGTSTP, vc->otstp);
375 safe_signal(SIGTTIN, vc->ottin);
376 safe_signal(SIGTTOU, vc->ottou);
378 /* Bounce jumps to the lex.c trampolines
379 * (i'd have never believed i'd ever say or even do something like this) */
380 if (state & _JUMPED) {
381 sigemptyset(&cset);
382 sigaddset(&cset, __spam_sig);
383 sigprocmask(SIG_UNBLOCK, &cset, NULL);
384 kill(0, __spam_sig);
386 j_leave:
387 return ! (state & _ERRORS);
391 cspam_clear(void *v)
393 int *ip;
395 for (ip = v; *ip != 0; ++ip)
396 message[(size_t)*ip - 1].m_flag &= ~MSPAM;
397 return 0;
401 cspam_set(void *v)
403 int *ip;
405 for (ip = v; *ip != 0; ++ip)
406 message[(size_t)*ip - 1].m_flag |= MSPAM;
407 return 0;
411 cspam_forget(void *v)
413 return _spam_action(_SPAM_FORGET, (int*)v) ? OKAY : STOP;
417 cspam_ham(void *v)
419 return _spam_action(_SPAM_HAM, (int*)v) ? OKAY : STOP;
423 cspam_rate(void *v)
425 return _spam_action(_SPAM_RATE, (int*)v) ? OKAY : STOP;
429 cspam_spam(void *v)
431 return _spam_action(_SPAM_SPAM, (int*)v) ? OKAY : STOP;
433 #endif /* HAVE_SPAM */
435 /* vim:set fenc=utf-8:s-it-mode */