1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Spam related facilities.
4 * Copyright (c) 2013 - 2014 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 #ifndef HAVE_AMALGAMATION
26 * TODO - We cannot use the spamc library because of our jumping behaviour.
27 * TODO We could nonetheless if we'd start a fork(2)ed child which would
28 * TODO use the spamc library.
29 * TODO -- In fact using a child process that is immune from the terrible
30 * TODO signal and jumping mess, and that controls further childs, and
31 * TODO gains file descriptors via sendmsg(2), and is started once it is
32 * TODO needed first, i have in mind for quite some time, for the transition
33 * TODO to a select(2) based implementation: we could slowly convert SMTP,
34 * TODO etc., finally IMAP, at which case we could rejoin back into a single
35 * TODO process program (unless we want to isolate the UI at that time, to
36 * TODO allow for some xUI protocol).
37 * TODO :: That is to say -- it's a horrible signal and jump mess ::
38 * TODO - We do not yet handle direct communication with spamd(1).
39 * TODO I.e., this could be a lean alternative to the first item;
40 * TODO the protocol is easy and we could support ALL operations easily.
41 * TODO - We do not yet update mails in place, i.e., replace the original
42 * TODO message with the updated one; we could easily do it as if `edit' has
43 * TODO been used, but the nail codebase doesn't truly support that for IMAP.
44 * TODO (And it seems a bit grazy to download a message, update it and upload
62 /* TODO This codebase jumps around and uses "stacks" of signal handling;
63 * TODO until some later time we have to play the same game */
64 sighandler_type otstp
;
65 sighandler_type ottin
;
66 sighandler_type ottou
;
68 sighandler_type opipe
;
70 char const *comm_a
[16];
73 /* Indices according to enum spam_action */
74 static char const _spam_comms
[][16] = {
75 "spamrate", "spamham", "spamspam", "spamforget"
78 /* Shared action setup */
79 static bool_t
_spam_action(enum spam_action sa
, int *ip
);
81 /* Convert a 2[.x]/whatever spam rate into message.m_spamscore */
82 static void _spam_rate2score(struct spam_vc
*vc
);
84 /* Interact with spamc(1) */
85 static bool_t
_spam_interact(struct spam_vc
*vc
);
88 _spam_action(enum spam_action sa
, int *ip
)
93 char const *cp
, **args
;
99 if ((cp
= ok_vlook(spam_command
)) == NULL
) {
103 fprintf(stderr
, tr(514, "`%s': *spam-command* is not set\n"),
109 /* Prepare the spamc(1) command line */
132 if ((cp
= ok_vlook(spam_socket
)) != NULL
) {
136 if ((cp
= ok_vlook(spam_host
)) != NULL
) {
140 if ((cp
= ok_vlook(spam_port
)) != NULL
) {
146 *args
++ = "-l"; /* --log-to-stderr */
148 if ((cp
= ok_vlook(spam_user
)) != 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 */
160 if ((cp
= ok_vlook(spam_maxsize
)) != NULL
)
161 maxsize
= (size_t)strtol(cp
, NULL
, 10);
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
);
181 if ((ok
= _spam_interact(&vc
)) == FAL0
)
192 _spam_rate2score(struct spam_vc
*vc
)
199 cp
= strchr(vc
->buffer
, '/');
202 size
= PTR2SIZE(cp
- vc
->buffer
);
203 vc
->buffer
[size
] = '\0';
205 m
= (ui32_t
)strtol(vc
->buffer
, &cp
, 10);
206 if (cp
== vc
->buffer
)
209 s
= (*cp
== '\0') ? 0 : (ui32_t
)strtol(++cp
, NULL
, 10);
211 vc
->mp
->m_spamscore
= (m
<< 8) | (s
& 0xFF);
216 static sigjmp_buf __spam_actjmp
; /* TODO someday, we won't need it no more */
217 static int __spam_sig
; /* TODO someday, we won't need it no more */
219 __spam_onsig(int sig
) /* TODO someday, we won't need it no more */
221 NYD_X
; /* Signal handler */
223 siglongjmp(__spam_actjmp
, 1);
227 _spam_interact(struct spam_vc
*vc
)
239 _P2C
= _P2C_0
| _P2C_1
,
242 _C2P
= _C2P_0
| _C2P_1
,
251 if ((ibuf
= setinput(&mb
, vc
->mp
, NEED_BODY
)) == NULL
) {
252 perror("setinput"); /* XXX tr() */
256 /* TODO Avoid that we jump away; yet necessary signal mess */
257 vc
->otstp
= safe_signal(SIGTSTP
, SIG_DFL
);
258 vc
->ottin
= safe_signal(SIGTTIN
, SIG_DFL
);
259 vc
->ottou
= safe_signal(SIGTTOU
, SIG_DFL
);
260 vc
->opipe
= safe_signal(SIGPIPE
, SIG_IGN
);
263 vc
->ohup
= safe_signal(SIGHUP
, &__spam_onsig
);
264 vc
->oint
= safe_signal(SIGINT
, &__spam_onsig
);
265 /* Keep sigs blocked */
266 pid
= 0; /* cc uninit */
268 if (!pipe_cloexec(p2c
)) {
269 perror("pipe"); /* XXX tr() */
274 if (!pipe_cloexec(c2p
)) {
275 perror("pipe"); /* XXX tr() */
280 if (sigsetjmp(__spam_actjmp
, 1)) {
288 pid
= start_command(vc
->comm_s
, &cset
, p2c
[0], c2p
[1], NULL
, NULL
, NULL
);
293 /* Yes, we could sendmp(SEND_MBOX), but simply passing through the MBOX
294 * content does the same in effect, but is much more efficient.
295 * NOTE: this may mean we pass a message without From_ line! */
296 for (size
= vc
->mp
->m_size
; size
> 0;) {
297 size_t i
= fread(vc
->buffer
, 1, MIN(size
, BUFFER_SIZE
), ibuf
);
304 if (i
!= (size_t)write(p2c
[1], vc
->buffer
, i
)) {
311 /* TODO In what follows you see a lot of races; these can't be helped without
312 * TODO atomic compare-and-swap -- WE COULD ALSO BLOCK ANYTHING FOR A WHILE*/
313 if (state
& _SIGHOLD
) {
318 if (state
& _P2C_0
) {
322 if (state
& _C2P_1
) {
326 /* Close the write end, so that spamc(1) goes */
327 if (state
& _P2C_1
) {
332 if (state
& _RUNNING
) {
334 if (wait_child(pid
, NULL
))
338 /* XXX This only works because spamc(1) follows the clear protocol (1) read
339 * XXX everything until EOF on input, then (2) work, then (3) output
340 * XXX a single result line; otherwise we could deadlock here, but since
341 * TODO this is rather intermediate, go with it */
342 if (vc
->action
== _SPAM_RATE
&& !(state
& (_JUMPED
| _ERRORS
))) {
343 ssize_t i
= read(c2p
[0], vc
->buffer
, BUFFER_SIZE
- 1);
345 vc
->buffer
[i
] = '\0';
346 _spam_rate2score(vc
);
351 if (state
& _C2P_0
) {
356 if (vc
->action
== _SPAM_RATE
) {
357 switch (state
& (_JUMPED
| _GOODRUN
| _ERRORS
)) {
359 vc
->mp
->m_flag
&= ~MSPAM
;
362 vc
->mp
->m_flag
|= MSPAM
;
367 if (state
& (_JUMPED
| _ERRORS
))
368 /* xxx print message? */;
369 else if (vc
->action
== _SPAM_SPAM
)
370 vc
->mp
->m_flag
|= MSPAM
;
371 else if (vc
->action
== _SPAM_HAM
)
372 vc
->mp
->m_flag
&= ~MSPAM
;
375 safe_signal(SIGINT
, vc
->oint
);
376 safe_signal(SIGHUP
, vc
->ohup
);
377 safe_signal(SIGPIPE
, vc
->opipe
);
378 safe_signal(SIGTSTP
, vc
->otstp
);
379 safe_signal(SIGTTIN
, vc
->ottin
);
380 safe_signal(SIGTTOU
, vc
->ottou
);
382 /* Bounce jumps to the lex.c trampolines
383 * (i'd have never believed i'd ever say or even do something like this) */
384 if (state
& _JUMPED
) {
386 sigaddset(&cset
, __spam_sig
);
387 sigprocmask(SIG_UNBLOCK
, &cset
, NULL
);
393 return !(state
& _ERRORS
);
397 c_spam_clear(void *v
)
402 for (ip
= v
; *ip
!= 0; ++ip
)
403 message
[(size_t)*ip
- 1].m_flag
&= ~MSPAM
;
414 for (ip
= v
; *ip
!= 0; ++ip
)
415 message
[(size_t)*ip
- 1].m_flag
|= MSPAM
;
421 c_spam_forget(void *v
)
426 rv
= _spam_action(_SPAM_FORGET
, (int*)v
) ? OKAY
: STOP
;
437 rv
= _spam_action(_SPAM_HAM
, (int*)v
) ? OKAY
: STOP
;
448 rv
= _spam_action(_SPAM_RATE
, (int*)v
) ? OKAY
: STOP
;
459 rv
= _spam_action(_SPAM_SPAM
, (int*)v
) ? OKAY
: STOP
;
463 #endif /* HAVE_SPAM */
465 /* vim:set fenc=utf-8:s-it-mode */