1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Spam related facilities.
4 * Copyright (c) 2013 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
5 * SPDX-License-Identifier: ISC
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #ifndef HAVE_AMALGAMATION
29 #ifdef HAVE_SPAM_SPAMD
30 # include <sys/socket.h>
34 /* This is chosen rather arbitrarily.
35 * It must be able to swallow the first line of a rate response,
36 * and an entire CHECK/TELL spamd(1) response */
37 #if BUFFER_SIZE < 1024
38 # error *spam-interface* BUFFER_SIZE constraints are not matched
41 #ifdef HAVE_SPAM_SPAMD
42 # define SPAMD_IDENT "SPAMC/1.5"
44 # define SUN_LEN(SUP) \
45 (sizeof(*(SUP)) - sizeof((SUP)->sun_path) + strlen((SUP)->sun_path))
49 #ifdef HAVE_SPAM_FILTER
50 /* n_NELEM() of regmatch_t groups */
51 # define SPAM_FILTER_MATCHES 32u
61 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
64 char *cf_result
; /* _SPAM_RATE: first response line */
68 /* .cf_cmd may be adjusted for each call (`spamforget')... */
71 char const *cf_env
[4];
72 sighandler_type cf_otstp
;
73 sighandler_type cf_ottin
;
74 sighandler_type cf_ottou
;
75 sighandler_type cf_ohup
;
76 sighandler_type cf_opipe
;
77 sighandler_type cf_oint
;
78 sighandler_type cf_oquit
;
82 #ifdef HAVE_SPAM_SPAMC
84 struct spam_cf c_super
;
85 char const *c_cmd_arr
[9];
89 #ifdef HAVE_SPAM_SPAMD
92 sighandler_type d_otstp
;
93 sighandler_type d_ottin
;
94 sighandler_type d_ottou
;
95 sighandler_type d_ohup
;
96 sighandler_type d_opipe
;
97 sighandler_type d_oint
;
98 sighandler_type d_oquit
;
99 struct sockaddr_un d_sun
;
103 #ifdef HAVE_SPAM_FILTER
105 struct spam_cf f_super
;
106 char const *f_cmd_nospam
; /* Working relative to current message.. */
107 char const *f_cmd_noham
;
110 ui32_t f_score_grpno
; /* 0 for not set */
111 regex_t f_score_regex
;
117 enum spam_action vc_action
;
118 bool_t vc_verbose
; /* Verbose output */
119 bool_t vc_progress
; /* "Progress meter" (mutual verbose) */
121 bool_t (*vc_act
)(struct spam_vc
*);
122 void (*vc_dtor
)(struct spam_vc
*);
123 char *vc_buffer
; /* I/O buffer, BUFFER_SIZE bytes */
124 size_t vc_mno
; /* Current message number */
125 struct message
*vc_mp
; /* Current message */
126 FILE *vc_ifp
; /* Input stream on .vc_mp */
128 #ifdef HAVE_SPAM_SPAMC
129 struct spam_spamc spamc
;
131 #ifdef HAVE_SPAM_SPAMD
132 struct spam_spamd spamd
;
134 #ifdef HAVE_SPAM_FILTER
135 struct spam_filter filter
;
137 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
141 char const *vc_esep
; /* Error separator for progress mode */
144 /* Indices according to enum spam_action */
145 static char const _spam_cmds
[][16] = {
146 "spamrate", "spamham", "spamspam", "spamforget"
149 /* Shared action setup */
150 static bool_t
_spam_action(enum spam_action sa
, int *ip
);
152 /* *spam-interface*=spamc: initialize, communicate */
153 #ifdef HAVE_SPAM_SPAMC
154 static bool_t
_spamc_setup(struct spam_vc
*vcp
);
155 static bool_t
_spamc_interact(struct spam_vc
*vcp
);
156 static void _spamc_dtor(struct spam_vc
*vcp
);
159 /* *spam-interface*=spamd: initialize, communicate */
160 #ifdef HAVE_SPAM_SPAMD
161 static bool_t
_spamd_setup(struct spam_vc
*vcp
);
162 static bool_t
_spamd_interact(struct spam_vc
*vcp
);
165 /* *spam-interface*=filter: initialize, communicate */
166 #ifdef HAVE_SPAM_FILTER
167 static bool_t
_spamfilter_setup(struct spam_vc
*vcp
);
168 static bool_t
_spamfilter_interact(struct spam_vc
*vcp
);
169 static void _spamfilter_dtor(struct spam_vc
*vcp
);
172 /* *spam-interface*=(spamc|filter): create child + communication */
173 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
174 static void _spam_cf_setup(struct spam_vc
*vcp
, bool_t useshell
);
175 static bool_t
_spam_cf_interact(struct spam_vc
*vcp
);
178 /* Convert a floating-point spam rate into message.m_spamscore */
179 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_SPAMD ||\
180 (defined HAVE_SPAM_FILTER && defined HAVE_REGEX)
181 static void _spam_rate2score(struct spam_vc
*vcp
, char *buf
);
185 _spam_action(enum spam_action sa
, int *ip
)
188 size_t maxsize
, skipped
, cnt
, curr
;
193 memset(&vc
, 0, sizeof vc
);
195 vc
.vc_verbose
= ((n_poption
& n_PO_VERB
) != 0);
196 vc
.vc_progress
= (!vc
.vc_verbose
&& ((n_psonce
& n_PSO_INTERACTIVE
) != 0));
197 vc
.vc_esep
= vc
.vc_progress
? "\n" : n_empty
;
199 /* Check and setup the desired spam interface */
200 if ((cp
= ok_vlook(spam_interface
)) == NULL
) {
201 n_err(_("`%s': no *spam-interface* set\n"), _spam_cmds
[sa
]);
203 #ifdef HAVE_SPAM_SPAMC
204 } else if (!asccasecmp(cp
, "spamc")) {
205 if (!_spamc_setup(&vc
))
208 #ifdef HAVE_SPAM_SPAMD
209 } else if (!asccasecmp(cp
, "spamd")) { /* TODO v15: remove */
210 n_OBSOLETE(_("*spam-interface*=spamd is obsolete, please use =spamc"));
211 if (!_spamd_setup(&vc
))
214 #ifdef HAVE_SPAM_FILTER
215 } else if (!asccasecmp(cp
, "filter")) {
216 if (!_spamfilter_setup(&vc
))
220 n_err(_("`%s': unknown / unsupported *spam-interface*: %s\n"),
225 /* *spam-maxsize* we do handle ourselfs instead */
226 if ((cp
= ok_vlook(spam_maxsize
)) == NULL
||
227 (n_idec_ui32_cp(&maxsize
, cp
, 0, NULL
), maxsize
) == 0)
228 maxsize
= SPAM_MAXSIZE
;
230 /* Finally get an I/O buffer */
231 vc
.vc_buffer
= n_autorec_alloc(BUFFER_SIZE
);
234 if (vc
.vc_progress
) {
238 for (curr
= 0, ok
= TRU1
; *ip
!= 0; --cnt
, ++curr
, ++ip
) {
239 vc
.vc_mno
= (size_t)*ip
;
240 vc
.vc_mp
= message
+ vc
.vc_mno
- 1;
241 if (sa
== _SPAM_RATE
)
242 vc
.vc_mp
->m_spamscore
= 0;
244 if (vc
.vc_mp
->m_size
> maxsize
) {
246 n_err(_("`%s': message %lu exceeds maxsize (%lu > %lu), skip\n"),
247 _spam_cmds
[sa
], (ul_i
)vc
.vc_mno
, (ul_i
)(size_t)vc
.vc_mp
->m_size
,
249 else if (vc
.vc_progress
) {
250 fprintf(n_stdout
, "\r%s: !%-6" PRIuZ
" %6" PRIuZ
"/%-6" PRIuZ
,
251 _spam_cmds
[sa
], vc
.vc_mno
, cnt
, curr
);
257 n_err(_("`%s': message %lu\n"), _spam_cmds
[sa
], (ul_i
)vc
.vc_mno
);
258 else if (vc
.vc_progress
) {
259 fprintf(n_stdout
, "\r%s: .%-6" PRIuZ
" %6" PRIuZ
"/%-6" PRIuZ
,
260 _spam_cmds
[sa
], vc
.vc_mno
, cnt
, curr
);
265 if ((vc
.vc_ifp
= setinput(&mb
, vc
.vc_mp
, NEED_BODY
)) == NULL
) {
266 n_err(_("%s`%s': cannot load message %lu: %s\n"),
267 vc
.vc_esep
, _spam_cmds
[sa
], (ul_i
)vc
.vc_mno
,
268 n_err_to_doc(n_err_no
));
273 if (!(ok
= (*vc
.vc_act
)(&vc
)))
277 if (vc
.vc_progress
) {
279 fprintf(n_stdout
, _(" %s (%" PRIuZ
"/%" PRIuZ
" all/skipped)\n"),
280 (ok
? _("done") : V_(n_error
)), curr
, skipped
);
284 if (vc
.vc_dtor
!= NULL
)
291 #ifdef HAVE_SPAM_SPAMC
293 _spamc_setup(struct spam_vc
*vcp
)
295 struct spam_spamc
*sscp
;
297 char const **args
, *cp
;
301 sscp
= &vcp
->vc_t
.spamc
;
302 args
= sscp
->c_cmd_arr
;
304 if ((cp
= ok_vlook(spamc_command
)) == NULL
) {
305 # ifdef SPAM_SPAMC_PATH
306 cp
= SPAM_SPAMC_PATH
;
308 n_err(_("`%s': *spamc-command* is not set\n"),
309 _spam_cmds
[vcp
->vc_action
]);
315 switch (vcp
->vc_action
) {
334 *args
++ = "-l"; /* --log-to-stderr */
335 *args
++ = "-x"; /* No "safe callback", we need to react on errors! */
337 if ((cp
= ok_vlook(spamc_arguments
)) != NULL
)
340 if ((cp
= ok_vlook(spamc_user
)) != NULL
) {
342 cp
= ok_vlook(LOGNAME
);
346 assert(PTR2SIZE(args
- sscp
->c_cmd_arr
) <= n_NELEM(sscp
->c_cmd_arr
));
349 sscp
->c_super
.cf_cmd
= str_concat_cpa(&str
, sscp
->c_cmd_arr
, " ")->s
;
351 n_err(_("spamc(1) via %s\n"),
352 n_shexp_quote_cp(sscp
->c_super
.cf_cmd
, FAL0
));
354 _spam_cf_setup(vcp
, FAL0
);
356 vcp
->vc_act
= &_spamc_interact
;
357 vcp
->vc_dtor
= &_spamc_dtor
;
359 # ifndef SPAM_SPAMC_PATH
367 _spamc_interact(struct spam_vc
*vcp
)
372 if (!(rv
= _spam_cf_interact(vcp
)))
375 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
376 if (vcp
->vc_action
!= _SPAM_RATE
) {
377 if (vcp
->vc_action
== _SPAM_SPAM
)
378 vcp
->vc_mp
->m_flag
|= MSPAM
;
382 switch (WEXITSTATUS(vcp
->vc_t
.spamc
.c_super
.cf_waitstat
)) {
384 vcp
->vc_mp
->m_flag
|= MSPAM
;
393 if ((cp
= strchr(buf
= vcp
->vc_t
.spamc
.c_super
.cf_result
, '/')) != NULL
)
394 buf
[PTR2SIZE(cp
- buf
)] = '\0';
395 _spam_rate2score(vcp
, buf
);
403 _spamc_dtor(struct spam_vc
*vcp
)
406 if (vcp
->vc_t
.spamc
.c_super
.cf_result
!= NULL
)
407 n_free(vcp
->vc_t
.spamc
.c_super
.cf_result
);
410 #endif /* HAVE_SPAM_SPAMC */
412 #ifdef HAVE_SPAM_SPAMD
414 _spamd_setup(struct spam_vc
*vcp
)
416 struct spam_spamd
*ssdp
;
422 ssdp
= &vcp
->vc_t
.spamd
;
424 if ((cp
= ok_vlook(spamd_user
)) != NULL
) {
426 cp
= ok_vlook(LOGNAME
);
427 ssdp
->d_user
.l
= strlen(ssdp
->d_user
.s
= n_UNCONST(cp
));
430 if ((cp
= ok_vlook(spamd_socket
)) == NULL
) {
431 n_err(_("`%s': required *spamd-socket* is not set\n"),
432 _spam_cmds
[vcp
->vc_action
]);
435 if ((l
= strlen(cp
) +1) >= sizeof(ssdp
->d_sun
.sun_path
)) {
436 n_err(_("`%s': *spamd-socket* too long: %s\n"),
437 _spam_cmds
[vcp
->vc_action
], n_shexp_quote_cp(cp
, FAL0
));
440 ssdp
->d_sun
.sun_family
= AF_UNIX
;
441 memcpy(ssdp
->d_sun
.sun_path
, cp
, l
);
443 vcp
->vc_act
= &_spamd_interact
;
450 static sigjmp_buf __spamd_actjmp
; /* TODO oneday, we won't need it no more */
451 static int volatile __spamd_sig
; /* TODO oneday, we won't need it no more */
453 __spamd_onsig(int sig
) /* TODO someday, we won't need it no more */
455 NYD_X
; /* Signal handler */
457 siglongjmp(__spamd_actjmp
, 1);
461 _spamd_interact(struct spam_vc
*vcp
)
463 struct spam_spamd
*ssdp
;
465 char *lp
, *cp
, * volatile headbuf
= NULL
;
466 int volatile dsfd
= -1;
467 bool_t
volatile rv
= FAL0
;
470 ssdp
= &vcp
->vc_t
.spamd
;
474 ssdp
->d_otstp
= safe_signal(SIGTSTP
, SIG_DFL
);
475 ssdp
->d_ottin
= safe_signal(SIGTTIN
, SIG_DFL
);
476 ssdp
->d_ottou
= safe_signal(SIGTTOU
, SIG_DFL
);
477 ssdp
->d_opipe
= safe_signal(SIGPIPE
, SIG_IGN
);
478 ssdp
->d_ohup
= safe_signal(SIGHUP
, &__spamd_onsig
);
479 ssdp
->d_oint
= safe_signal(SIGINT
, &__spamd_onsig
);
480 ssdp
->d_oquit
= safe_signal(SIGQUIT
, &__spamd_onsig
);
481 if (sigsetjmp(__spamd_actjmp
, 1)) {
482 if (*vcp
->vc_esep
!= '\0')
488 if ((dsfd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) == -1) {
489 n_err(_("%s`%s': can't create unix(4) socket: %s\n"),
490 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], n_err_to_doc(n_err_no
));
494 if (connect(dsfd
, (struct sockaddr
*)&ssdp
->d_sun
, SUN_LEN(&ssdp
->d_sun
)) ==
496 n_err(_("%s`%s': can't connect to *spam-socket*: %s\n"),
497 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], n_err_to_doc(n_err_no
));
503 /* The command header, finalized with an empty line.
504 * This needs to be written in a single write(2)! */
506 # define _X(X) do {memcpy(lp, X, sizeof(X) -1); lp += sizeof(X) -1;} while (0)
508 i
= ((cp
= ssdp
->d_user
.s
) != NULL
) ? ssdp
->d_user
.l
: 0;
509 size
= sizeof(NETLINE("A_VERY_LONG_COMMAND " SPAMD_IDENT
)) +
510 sizeof(NETLINE("Content-length: 9223372036854775807")) +
511 ((cp
!= NULL
) ? sizeof("User: ") + i
+ sizeof(NETNL
) : 0) +
512 sizeof(NETLINE("Message-class: spam")) +
513 sizeof(NETLINE("Set: local")) +
514 sizeof(NETLINE("Remove: local")) +
515 sizeof(NETNL
) /*+1*/;
516 lp
= headbuf
= n_lofi_alloc(size
);
518 switch (vcp
->vc_action
) {
520 _X(NETLINE("CHECK " SPAMD_IDENT
));
525 _X(NETLINE("TELL " SPAMD_IDENT
));
529 lp
+= snprintf(lp
, size
, NETLINE("Content-length: %" PRIuZ
),
530 (size_t)vcp
->vc_mp
->m_size
);
539 switch (vcp
->vc_action
) {
544 _X(NETLINE("Message-class: ham")
545 NETLINE("Set: local")
549 _X(NETLINE("Message-class: spam")
550 NETLINE("Set: local")
554 if (vcp
->vc_mp
->m_flag
& MSPAM
)
555 _X(NETLINE("Message-class: spam"));
557 _X(NETLINE("Message-class: ham"));
558 _X(NETLINE("Remove: local")
564 i
= PTR2SIZE(lp
- headbuf
);
565 if (n_poption
& n_PO_VERBVERB
)
566 n_err(">>> %.*s <<<\n", (int)i
, headbuf
);
567 if (i
!= (size_t)write(dsfd
, headbuf
, i
))
570 /* Then simply pass through the message "as-is" */
571 for (size
= vcp
->vc_mp
->m_size
; size
> 0;) {
572 i
= fread(vcp
->vc_buffer
, sizeof *vcp
->vc_buffer
,
573 n_MIN(size
, BUFFER_SIZE
), vcp
->vc_ifp
);
575 if (ferror(vcp
->vc_ifp
))
581 if (i
!= (size_t)write(dsfd
, vcp
->vc_buffer
, i
)) {
583 n_err(_("%s`%s': I/O on *spamd-socket* failed: %s\n"),
584 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], n_err_to_doc(n_err_no
));
589 /* We are finished, say so */
590 shutdown(dsfd
, SHUT_WR
);
592 /* Be aware on goto: i will be a line counter after this loop! */
593 for (size
= 0, i
= BUFFER_SIZE
-1;;) {
594 ssize_t j
= read(dsfd
, vcp
->vc_buffer
+ size
, i
);
600 /* For the current way of doing things a single read will suffice.
601 * Note we'll be "penaltized" when awaiting EOF on the socket, at least
602 * in blocking mode, so do avoid that and break off */
606 vcp
->vc_buffer
[size
] = '\0';
608 if (size
== 0 || size
== BUFFER_SIZE
) {
610 n_err(_("%s`%s': bogus spamd(1) I/O interaction (%lu)\n"),
611 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], (ul_i
)i
);
613 if (n_poption
& n_PO_VERBVERB
)
614 n_err(">>> BUFFER: %s <<<\n", vcp
->vc_buffer
);
619 /* From the response, read those lines that interest us */
620 for (lp
= vcp
->vc_buffer
; size
> 0; ++i
) {
622 lp
= strchr(lp
, NETNL
[0]);
626 if (lp
[1] != NETNL
[1])
629 size
-= PTR2SIZE(lp
- cp
);
632 if (!strncmp(cp
, "SPAMD/1.1 0 EX_OK", sizeof("SPAMD/1.1 0 EX_OK") -1))
634 if (vcp
->vc_action
!= _SPAM_RATE
||
635 strstr(cp
, "Service Unavailable") == NULL
)
638 /* Unfortunately a missing --allow-tell drops connection.. */
639 n_err(_("%s`%s': service not available in spamd(1) instance\n"),
640 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
]);
644 switch (vcp
->vc_action
) {
646 if (strncmp(cp
, "Spam: ", sizeof("Spam: ") -1))
648 cp
+= sizeof("Spam: ") -1;
650 if (!strncmp(cp
, "False", sizeof("False") -1)) {
651 cp
+= sizeof("False") -1;
652 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
653 } else if (!strncmp(cp
, "True", sizeof("True") -1)) {
654 cp
+= sizeof("True") -1;
655 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
656 vcp
->vc_mp
->m_flag
|= MSPAM
;
660 while (blankspacechar(*cp
))
666 char *xcp
= strchr(cp
, '/');
668 size
= PTR2SIZE(xcp
- cp
);
671 _spam_rate2score(vcp
, cp
);
677 /* Empty response means ok but "did nothing" */
679 strncmp(cp
, "DidSet: local", sizeof("DidSet: local") -1))
681 if (*cp
== '\0' && vcp
->vc_verbose
)
682 n_err(_("\tBut spamd(1) \"did nothing\" for message\n"));
683 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
684 if (vcp
->vc_action
== _SPAM_SPAM
)
685 vcp
->vc_mp
->m_flag
|= MSPAM
;
690 strncmp(cp
, "DidRemove: local", sizeof("DidSet: local") -1))
692 if (*cp
== '\0' && vcp
->vc_verbose
)
693 n_err(_("\tBut spamd(1) \"did nothing\" for message\n"));
694 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
704 n_lofi_free(headbuf
);
708 safe_signal(SIGQUIT
, ssdp
->d_oquit
);
709 safe_signal(SIGINT
, ssdp
->d_oint
);
710 safe_signal(SIGHUP
, ssdp
->d_ohup
);
711 safe_signal(SIGPIPE
, ssdp
->d_opipe
);
712 safe_signal(SIGTSTP
, ssdp
->d_otstp
);
713 safe_signal(SIGTTIN
, ssdp
->d_ottin
);
714 safe_signal(SIGTTOU
, ssdp
->d_ottou
);
717 if (__spamd_sig
!= 0) {
720 sigaddset(&cset
, __spamd_sig
);
721 sigprocmask(SIG_UNBLOCK
, &cset
, NULL
);
722 n_raise(__spamd_sig
);
727 #endif /* HAVE_SPAM_SPAMD */
729 #ifdef HAVE_SPAM_FILTER
731 _spamfilter_setup(struct spam_vc
*vcp
)
733 struct spam_filter
*sfp
;
734 char const *cp
, *var
;
738 sfp
= &vcp
->vc_t
.filter
;
740 switch (vcp
->vc_action
) {
742 cp
= ok_vlook(spamfilter_rate
);
743 var
= "spam-filter-rate";
746 cp
= ok_vlook(spamfilter_ham
);
747 var
= "spam-filter-ham";
750 cp
= ok_vlook(spamfilter_spam
);
751 var
= "spam-filter-spam";
755 n_err(_("`%s': *%s* is not set\n"), _spam_cmds
[vcp
->vc_action
], var
);
758 sfp
->f_super
.cf_cmd
= savestr(cp
);
761 var
= "spam-filter-nospam";
762 if ((cp
= ok_vlook(spamfilter_nospam
)) == NULL
)
764 sfp
->f_cmd_nospam
= savestr(cp
);
765 if ((cp
= ok_vlook(spamfilter_noham
)) == NULL
)
767 sfp
->f_cmd_noham
= savestr(cp
);
772 if (vcp
->vc_action
== _SPAM_RATE
&&
773 (cp
= ok_vlook(spamfilter_rate_scanscore
)) != NULL
) {
777 var
= strchr(cp
, ';');
779 n_err(_("`%s': *spamfilter-rate-scanscore*: missing semicolon;: %s\n"),
780 _spam_cmds
[vcp
->vc_action
], cp
);
785 if((n_idec_buf(&sfp
->f_score_grpno
, cp
, PTR2SIZE(var
- cp
), 0,
786 n_IDEC_MODE_LIMIT_32BIT
, NULL
787 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
788 ) != n_IDEC_STATE_CONSUMED
){
789 n_err(_("`%s': *spamfilter-rate-scanscore*: bad group: %s\n"),
790 _spam_cmds
[vcp
->vc_action
], cp
);
793 if (sfp
->f_score_grpno
>= SPAM_FILTER_MATCHES
) {
794 n_err(_("`%s': *spamfilter-rate-scanscore*: "
795 "group %u excesses limit %u\n"),
796 _spam_cmds
[vcp
->vc_action
], sfp
->f_score_grpno
,
797 SPAM_FILTER_MATCHES
);
801 if ((s
= regcomp(&sfp
->f_score_regex
, bp
, REG_EXTENDED
| REG_ICASE
))
803 n_err(_("`%s': invalid *spamfilter-rate-scanscore* regex: %s: %s\n"),
804 _spam_cmds
[vcp
->vc_action
], n_shexp_quote_cp(cp
, FAL0
),
805 n_regex_err_to_doc(NULL
, s
));
808 if (sfp
->f_score_grpno
> sfp
->f_score_regex
.re_nsub
) {
809 regfree(&sfp
->f_score_regex
);
810 n_err(_("`%s': *spamfilter-rate-scanscore*: no group %u: %s\n"),
811 _spam_cmds
[vcp
->vc_action
], sfp
->f_score_grpno
, cp
);
815 # endif /* HAVE_REGEX */
817 _spam_cf_setup(vcp
, TRU1
);
819 vcp
->vc_act
= &_spamfilter_interact
;
820 vcp
->vc_dtor
= &_spamfilter_dtor
;
828 _spamfilter_interact(struct spam_vc
*vcp
)
831 regmatch_t rem
[SPAM_FILTER_MATCHES
], *remp
;
832 struct spam_filter
*sfp
;
838 if (vcp
->vc_action
== _SPAM_FORGET
)
839 vcp
->vc_t
.cf
.cf_cmd
= (vcp
->vc_mp
->m_flag
& MSPAM
)
840 ? vcp
->vc_t
.filter
.f_cmd_nospam
: vcp
->vc_t
.filter
.f_cmd_noham
;
842 if (!(rv
= _spam_cf_interact(vcp
)))
845 vcp
->vc_mp
->m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
846 if (vcp
->vc_action
!= _SPAM_RATE
) {
847 if (vcp
->vc_action
== _SPAM_SPAM
)
848 vcp
->vc_mp
->m_flag
|= MSPAM
;
850 } else switch (WEXITSTATUS(vcp
->vc_t
.filter
.f_super
.cf_waitstat
)) {
852 vcp
->vc_mp
->m_flag
|= MSPAMUNSURE
;
857 vcp
->vc_mp
->m_flag
|= MSPAM
;
865 sfp
= &vcp
->vc_t
.filter
;
867 if (sfp
->f_score_grpno
== 0)
869 if (sfp
->f_super
.cf_result
== NULL
) {
870 n_err(_("`%s': *spamfilter-rate-scanscore*: filter does not "
871 "produce output!\n"));
875 remp
= rem
+ sfp
->f_score_grpno
;
877 if (regexec(&sfp
->f_score_regex
, sfp
->f_super
.cf_result
, n_NELEM(rem
), rem
,
878 0) == REG_NOMATCH
|| (remp
->rm_so
| remp
->rm_eo
) < 0) {
879 n_err(_("`%s': *spamfilter-rate-scanscore* "
880 "does not match filter output!\n"),
881 _spam_cmds
[vcp
->vc_action
]);
882 sfp
->f_score_grpno
= 0;
886 cp
= sfp
->f_super
.cf_result
;
887 cp
[remp
->rm_eo
] = '\0';
889 _spam_rate2score(vcp
, cp
);
890 # endif /* HAVE_REGEX */
898 _spamfilter_dtor(struct spam_vc
*vcp
)
900 struct spam_filter
*sfp
;
903 sfp
= &vcp
->vc_t
.filter
;
905 if (sfp
->f_super
.cf_result
!= NULL
)
906 n_free(sfp
->f_super
.cf_result
);
908 if (sfp
->f_score_grpno
> 0)
909 regfree(&sfp
->f_score_regex
);
913 #endif /* HAVE_SPAM_FILTER */
915 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
917 _spam_cf_setup(struct spam_vc
*vcp
, bool_t useshell
)
921 struct spam_cf
*scfp
;
923 n_LCTA(2 < n_NELEM(scfp
->cf_env
), "Preallocated buffer too small");
925 scfp
= &vcp
->vc_t
.cf
;
927 if ((scfp
->cf_useshell
= useshell
)) {
928 scfp
->cf_acmd
= ok_vlook(SHELL
);
932 /* MAILX_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
933 * TODO a file wherever he wants! *Do* create a zero-size temporary file
934 * TODO and give *that* path as MAILX_FILENAME_TEMPORARY, clean it up once
935 * TODO the pipe returns? Like this we *can* verify path/name issues! */
936 cp
= n_random_create_cp(n_MIN(NAME_MAX
/ 4, 16), NULL
);
937 scfp
->cf_env
[0] = str_concat_csvl(&s
,
938 n_PIPEENV_FILENAME_GENERATED
, "=", cp
, NULL
)->s
;
939 /* v15 compat NAIL_ environments vanish! */
940 scfp
->cf_env
[1] = str_concat_csvl(&s
,
941 "NAIL_FILENAME_GENERATED", "=", cp
, NULL
)->s
;
942 scfp
->cf_env
[2] = NULL
;
946 static sigjmp_buf __spam_cf_actjmp
; /* TODO someday, we won't need it */
947 static int volatile __spam_cf_sig
; /* TODO someday, we won't need it */
949 __spam_cf_onsig(int sig
) /* TODO someday, we won't need it no more */
951 NYD_X
; /* Signal handler */
953 siglongjmp(__spam_cf_actjmp
, 1);
957 _spam_cf_interact(struct spam_vc
*vcp
)
959 struct spam_cf
*scfp
;
970 _P2C
= _P2C_0
| _P2C_1
,
973 _C2P
= _C2P_0
| _C2P_1
,
978 } volatile state
= _NONE
;
981 scfp
= &vcp
->vc_t
.cf
;
982 if (scfp
->cf_result
!= NULL
) {
983 n_free(scfp
->cf_result
);
984 scfp
->cf_result
= NULL
;
987 /* TODO Avoid that we jump away; yet necessary signal mess */
988 /*__spam_cf_sig = 0;*/
991 scfp
->cf_otstp
= safe_signal(SIGTSTP
, SIG_DFL
);
992 scfp
->cf_ottin
= safe_signal(SIGTTIN
, SIG_DFL
);
993 scfp
->cf_ottou
= safe_signal(SIGTTOU
, SIG_DFL
);
994 scfp
->cf_opipe
= safe_signal(SIGPIPE
, SIG_IGN
);
995 scfp
->cf_ohup
= safe_signal(SIGHUP
, &__spam_cf_onsig
);
996 scfp
->cf_oint
= safe_signal(SIGINT
, &__spam_cf_onsig
);
997 scfp
->cf_oquit
= safe_signal(SIGQUIT
, &__spam_cf_onsig
);
998 /* Keep sigs blocked */
999 pid
= 0; /* cc uninit */
1001 if (!pipe_cloexec(p2c
)) {
1002 n_err(_("%s`%s': cannot create parent communication pipe: %s\n"),
1003 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], n_err_to_doc(n_err_no
));
1008 if (!pipe_cloexec(c2p
)) {
1009 n_err(_("%s`%s': cannot create child pipe: %s\n"),
1010 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
], n_err_to_doc(n_err_no
));
1015 if (sigsetjmp(__spam_cf_actjmp
, 1)) {
1016 if (*vcp
->vc_esep
!= '\0')
1017 n_err(vcp
->vc_esep
);
1024 /* Start our command as requested */
1026 if ((pid
= n_child_start(
1027 (scfp
->cf_acmd
!= NULL
? scfp
->cf_acmd
: scfp
->cf_cmd
),
1028 &cset
, p2c
[0], c2p
[1],
1029 scfp
->cf_a0
, (scfp
->cf_acmd
!= NULL
? scfp
->cf_cmd
: NULL
), NULL
,
1030 scfp
->cf_env
)) < 0) {
1038 /* Yes, we could sendmp(SEND_MBOX), but simply passing through the MBOX
1039 * content does the same in effect, however much more efficiently.
1040 * XXX NOTE: this may mean we pass a message without From_ line! */
1041 for (size
= vcp
->vc_mp
->m_size
; size
> 0;) {
1044 i
= fread(vcp
->vc_buffer
, 1, n_MIN(size
, BUFFER_SIZE
), vcp
->vc_ifp
);
1046 if (ferror(vcp
->vc_ifp
))
1051 if (i
!= (size_t)write(p2c
[1], vcp
->vc_buffer
, i
)) {
1058 /* TODO Quite racy -- block anything for a while? */
1059 if (state
& _SIGHOLD
) {
1064 if (state
& _P2C_0
) {
1068 if (state
& _C2P_1
) {
1072 /* And cause EOF for the reader */
1073 if (state
& _P2C_1
) {
1078 if (state
& _RUNNING
) {
1079 if (!(state
& _ERRORS
) &&
1080 vcp
->vc_action
== _SPAM_RATE
&& !(state
& (_JUMPED
| _ERRORS
))) {
1081 ssize_t i
= read(c2p
[0], vcp
->vc_buffer
, BUFFER_SIZE
- 1);
1083 vcp
->vc_buffer
[i
] = '\0';
1084 if ((cp
= strchr(vcp
->vc_buffer
, NETNL
[0])) == NULL
&&
1085 (cp
= strchr(vcp
->vc_buffer
, NETNL
[1])) == NULL
) {
1086 n_err(_("%s`%s': program generates too much output: %s\n"),
1087 vcp
->vc_esep
, _spam_cmds
[vcp
->vc_action
],
1088 n_shexp_quote_cp(scfp
->cf_cmd
, FAL0
));
1091 scfp
->cf_result
= sbufdup(vcp
->vc_buffer
,
1092 PTR2SIZE(cp
- vcp
->vc_buffer
));
1093 /* FIXME consume child output until EOF??? */
1100 n_child_wait(pid
, &scfp
->cf_waitstat
);
1101 if (WIFEXITED(scfp
->cf_waitstat
))
1105 if (state
& _C2P_0
) {
1110 safe_signal(SIGQUIT
, scfp
->cf_oquit
);
1111 safe_signal(SIGINT
, scfp
->cf_oint
);
1112 safe_signal(SIGHUP
, scfp
->cf_ohup
);
1113 safe_signal(SIGPIPE
, scfp
->cf_opipe
);
1114 safe_signal(SIGTSTP
, scfp
->cf_otstp
);
1115 safe_signal(SIGTTIN
, scfp
->cf_ottin
);
1116 safe_signal(SIGTTOU
, scfp
->cf_ottou
);
1119 if (state
& _JUMPED
) {
1120 assert(vcp
->vc_dtor
!= NULL
);
1121 (*vcp
->vc_dtor
)(vcp
);
1124 sigaddset(&cset
, __spam_cf_sig
);
1125 sigprocmask(SIG_UNBLOCK
, &cset
, NULL
);
1126 n_raise(__spam_cf_sig
);
1128 return !(state
& (_JUMPED
| _ERRORS
));
1130 #endif /* HAVE_SPAM_SPAMC || HAVE_SPAM_FILTER */
1132 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_SPAMD ||\
1133 (defined HAVE_SPAM_FILTER && defined HAVE_REGEX)
1135 _spam_rate2score(struct spam_vc
*vcp
, char *buf
){
1137 enum n_idec_state ids
;
1140 /* C99 */{ /* Overcome ISO C / compiler weirdness */
1144 ids
= n_idec_ui32_cp(&m
, buf
, 10, &cp
);
1145 if((ids
& n_IDEC_STATE_EMASK
) & ~n_IDEC_STATE_EBASE
)
1147 buf
= n_UNCONST(cp
);
1151 if(!(ids
& n_IDEC_STATE_CONSUMED
)){
1152 /* Floating-point rounding for non-mathematicians */
1156 if((c1
= buf
[0]) != '\0' && (c2
= buf
[1]) != '\0' &&
1157 (c3
= buf
[2]) != '\0'){
1173 ids
= n_idec_ui32_cp(&s
, buf
, 10, NULL
);
1174 if((ids
& (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
1175 ) != n_IDEC_STATE_CONSUMED
)
1180 vcp
->vc_mp
->m_spamscore
= (m
<< 8) | s
;
1184 #endif /* _SPAM_SPAMC || _SPAM_SPAMD || (_SPAM_FILTER && HAVE_REGEX) */
1187 c_spam_clear(void *v
)
1192 for (ip
= v
; *ip
!= 0; ++ip
)
1193 message
[(size_t)*ip
- 1].m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
1204 for (ip
= v
; *ip
!= 0; ++ip
) {
1205 message
[(size_t)*ip
- 1].m_flag
&= ~(MSPAM
| MSPAMUNSURE
);
1206 message
[(size_t)*ip
- 1].m_flag
|= MSPAM
;
1213 c_spam_forget(void *v
)
1218 rv
= _spam_action(_SPAM_FORGET
, (int*)v
) ? OKAY
: STOP
;
1229 rv
= _spam_action(_SPAM_HAM
, (int*)v
) ? OKAY
: STOP
;
1235 c_spam_rate(void *v
)
1240 rv
= _spam_action(_SPAM_RATE
, (int*)v
) ? OKAY
: STOP
;
1246 c_spam_spam(void *v
)
1251 rv
= _spam_action(_SPAM_SPAM
, (int*)v
) ? OKAY
: STOP
;
1255 #endif /* HAVE_SPAM */