"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/spam.c" (8 Aug 2018, 34551 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "spam.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Spam related facilities.
    3  *
    4  * Copyright (c) 2013 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    5  * SPDX-License-Identifier: ISC
    6  *
    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.
   10  *
   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.
   18  */
   19 #undef n_FILE
   20 #define n_FILE spam
   21 
   22 #ifndef HAVE_AMALGAMATION
   23 # include "nail.h"
   24 #endif
   25 
   26 EMPTY_FILE()
   27 #ifdef HAVE_SPAM
   28 
   29 #ifdef HAVE_SPAM_SPAMD
   30 # include <sys/socket.h>
   31 # include <sys/un.h>
   32 #endif
   33 
   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
   39 #endif
   40 
   41 #ifdef HAVE_SPAM_SPAMD
   42 # define SPAMD_IDENT          "SPAMC/1.5"
   43 # ifndef SUN_LEN
   44 #  define SUN_LEN(SUP) \
   45         (sizeof(*(SUP)) - sizeof((SUP)->sun_path) + strlen((SUP)->sun_path))
   46 # endif
   47 #endif
   48 
   49 #ifdef HAVE_SPAM_FILTER
   50   /* n_NELEM() of regmatch_t groups */
   51 # define SPAM_FILTER_MATCHES  32u
   52 #endif
   53 
   54 enum spam_action {
   55    _SPAM_RATE,
   56    _SPAM_HAM,
   57    _SPAM_SPAM,
   58    _SPAM_FORGET
   59 };
   60 
   61 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
   62 struct spam_cf {
   63    char const        *cf_cmd;
   64    char              *cf_result; /* _SPAM_RATE: first response line */
   65    int               cf_waitstat;
   66    ui8_t             __pad[3];
   67    bool_t            cf_useshell;
   68    /* .cf_cmd may be adjusted for each call (`spamforget')... */
   69    char const        *cf_acmd;
   70    char const        *cf_a0;
   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;
   79 };
   80 #endif
   81 
   82 #ifdef HAVE_SPAM_SPAMC
   83 struct spam_spamc {
   84    struct spam_cf    c_super;
   85    char const        *c_cmd_arr[9];
   86 };
   87 #endif
   88 
   89 #ifdef HAVE_SPAM_SPAMD
   90 struct spam_spamd {
   91    struct str        d_user;
   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;
  100 };
  101 #endif
  102 
  103 #ifdef HAVE_SPAM_FILTER
  104 struct 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;
  108 # ifdef HAVE_REGEX
  109    ui8_t             __pad[4];
  110    ui32_t            f_score_grpno; /* 0 for not set */
  111    regex_t           f_score_regex;
  112 # endif
  113 };
  114 #endif
  115 
  116 struct spam_vc {
  117    enum spam_action  vc_action;
  118    bool_t            vc_verbose;    /* Verbose output */
  119    bool_t            vc_progress;   /* "Progress meter" (mutual verbose) */
  120    ui8_t             __pad[2];
  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 */
  127    union {
  128 #ifdef HAVE_SPAM_SPAMC
  129       struct spam_spamc    spamc;
  130 #endif
  131 #ifdef HAVE_SPAM_SPAMD
  132       struct spam_spamd    spamd;
  133 #endif
  134 #ifdef HAVE_SPAM_FILTER
  135       struct spam_filter   filter;
  136 #endif
  137 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
  138    struct spam_cf          cf;
  139 #endif
  140    }                 vc_t;
  141    char const        *vc_esep;      /* Error separator for progress mode */
  142 };
  143 
  144 /* Indices according to enum spam_action */
  145 static char const _spam_cmds[][16] = {
  146    "spamrate", "spamham", "spamspam", "spamforget"
  147 };
  148 
  149 /* Shared action setup */
  150 static bool_t  _spam_action(enum spam_action sa, int *ip);
  151 
  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);
  157 #endif
  158 
  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);
  163 #endif
  164 
  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);
  170 #endif
  171 
  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);
  176 #endif
  177 
  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);
  182 #endif
  183 
  184 static bool_t
  185 _spam_action(enum spam_action sa, int *ip)
  186 {
  187    struct spam_vc vc;
  188    size_t maxsize, skipped, cnt, curr;
  189    char const *cp;
  190    bool_t ok = FAL0;
  191    NYD_ENTER;
  192 
  193    memset(&vc, 0, sizeof vc);
  194    vc.vc_action = sa;
  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;
  198 
  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]);
  202       goto jleave;
  203 #ifdef HAVE_SPAM_SPAMC
  204    } else if (!asccasecmp(cp, "spamc")) {
  205        if (!_spamc_setup(&vc))
  206          goto jleave;
  207 #endif
  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))
  212          goto jleave;
  213 #endif
  214 #ifdef HAVE_SPAM_FILTER
  215    } else if (!asccasecmp(cp, "filter")) {
  216       if (!_spamfilter_setup(&vc))
  217          goto jleave;
  218 #endif
  219    } else {
  220       n_err(_("`%s': unknown / unsupported *spam-interface*: %s\n"),
  221          _spam_cmds[sa], cp);
  222       goto jleave;
  223    }
  224 
  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;
  229 
  230    /* Finally get an I/O buffer */
  231    vc.vc_buffer = n_autorec_alloc(BUFFER_SIZE);
  232 
  233    skipped = cnt = 0;
  234    if (vc.vc_progress) {
  235       while (ip[cnt] != 0)
  236          ++cnt;
  237    }
  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;
  243 
  244       if (vc.vc_mp->m_size > maxsize) {
  245          if (vc.vc_verbose)
  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,
  248                (ul_i)maxsize);
  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);
  252             fflush(n_stdout);
  253          }
  254          ++skipped;
  255       } else {
  256          if (vc.vc_verbose)
  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);
  261             fflush(n_stdout);
  262          }
  263 
  264          setdot(vc.vc_mp);
  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));
  269             ok = FAL0;
  270             break;
  271          }
  272 
  273          if (!(ok = (*vc.vc_act)(&vc)))
  274             break;
  275       }
  276    }
  277    if (vc.vc_progress) {
  278       if (curr > 0)
  279          fprintf(n_stdout, _(" %s (%" PRIuZ "/%" PRIuZ " all/skipped)\n"),
  280             (ok ? _("done") : V_(n_error)), curr, skipped);
  281       fflush(n_stdout);
  282    }
  283 
  284    if (vc.vc_dtor != NULL)
  285       (*vc.vc_dtor)(&vc);
  286 jleave:
  287    NYD_LEAVE;
  288    return !ok;
  289 }
  290 
  291 #ifdef HAVE_SPAM_SPAMC
  292 static bool_t
  293 _spamc_setup(struct spam_vc *vcp)
  294 {
  295    struct spam_spamc *sscp;
  296    struct str str;
  297    char const **args, *cp;
  298    bool_t rv = FAL0;
  299    NYD2_ENTER;
  300 
  301    sscp = &vcp->vc_t.spamc;
  302    args = sscp->c_cmd_arr;
  303 
  304    if ((cp = ok_vlook(spamc_command)) == NULL) {
  305 # ifdef SPAM_SPAMC_PATH
  306       cp = SPAM_SPAMC_PATH;
  307 # else
  308       n_err(_("`%s': *spamc-command* is not set\n"),
  309          _spam_cmds[vcp->vc_action]);
  310       goto jleave;
  311 # endif
  312    }
  313    *args++ = cp;
  314 
  315    switch (vcp->vc_action) {
  316    case _SPAM_RATE:
  317       *args = "-c";
  318       break;
  319    case _SPAM_HAM:
  320       args[1] = "ham";
  321       goto jlearn;
  322    case _SPAM_SPAM:
  323       args[1] = "spam";
  324       goto jlearn;
  325    case _SPAM_FORGET:
  326       args[1] = "forget";
  327 jlearn:
  328       *args = "-L";
  329       ++args;
  330       break;
  331    }
  332    ++args;
  333 
  334    *args++ = "-l"; /* --log-to-stderr */
  335    *args++ = "-x"; /* No "safe callback", we need to react on errors! */
  336 
  337    if ((cp = ok_vlook(spamc_arguments)) != NULL)
  338       *args++ = cp;
  339 
  340    if ((cp = ok_vlook(spamc_user)) != NULL) {
  341       if (*cp == '\0')
  342          cp = ok_vlook(LOGNAME);
  343       *args++ = "-u";
  344       *args++ = cp;
  345    }
  346    assert(PTR2SIZE(args - sscp->c_cmd_arr) <= n_NELEM(sscp->c_cmd_arr));
  347 
  348    *args = NULL;
  349    sscp->c_super.cf_cmd = str_concat_cpa(&str, sscp->c_cmd_arr, " ")->s;
  350    if (vcp->vc_verbose)
  351       n_err(_("spamc(1) via %s\n"),
  352          n_shexp_quote_cp(sscp->c_super.cf_cmd, FAL0));
  353 
  354    _spam_cf_setup(vcp, FAL0);
  355 
  356    vcp->vc_act = &_spamc_interact;
  357    vcp->vc_dtor = &_spamc_dtor;
  358    rv = TRU1;
  359 # ifndef SPAM_SPAMC_PATH
  360 jleave:
  361 # endif
  362    NYD2_LEAVE;
  363    return rv;
  364 }
  365 
  366 static bool_t
  367 _spamc_interact(struct spam_vc *vcp)
  368 {
  369    bool_t rv;
  370    NYD2_ENTER;
  371 
  372    if (!(rv = _spam_cf_interact(vcp)))
  373       goto jleave;
  374 
  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;
  379    } else {
  380       char *buf, *cp;
  381 
  382       switch (WEXITSTATUS(vcp->vc_t.spamc.c_super.cf_waitstat)) {
  383       case 1:
  384          vcp->vc_mp->m_flag |= MSPAM;
  385          /* FALLTHRU */
  386       case 0:
  387          break;
  388       default:
  389          rv = FAL0;
  390          goto jleave;
  391       }
  392 
  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);
  396    }
  397 jleave:
  398    NYD2_LEAVE;
  399    return rv;
  400 }
  401 
  402 static void
  403 _spamc_dtor(struct spam_vc *vcp)
  404 {
  405    NYD2_ENTER;
  406    if (vcp->vc_t.spamc.c_super.cf_result != NULL)
  407       n_free(vcp->vc_t.spamc.c_super.cf_result);
  408    NYD2_LEAVE;
  409 }
  410 #endif /* HAVE_SPAM_SPAMC */
  411 
  412 #ifdef HAVE_SPAM_SPAMD
  413 static bool_t
  414 _spamd_setup(struct spam_vc *vcp)
  415 {
  416    struct spam_spamd *ssdp;
  417    char const *cp;
  418    size_t l;
  419    bool_t rv = FAL0;
  420    NYD2_ENTER;
  421 
  422    ssdp = &vcp->vc_t.spamd;
  423 
  424    if ((cp = ok_vlook(spamd_user)) != NULL) {
  425       if (*cp == '\0')
  426          cp = ok_vlook(LOGNAME);
  427       ssdp->d_user.l = strlen(ssdp->d_user.s = n_UNCONST(cp));
  428    }
  429 
  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]);
  433       goto jleave;
  434    }
  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));
  438       goto jleave;
  439    }
  440    ssdp->d_sun.sun_family = AF_UNIX;
  441    memcpy(ssdp->d_sun.sun_path, cp, l);
  442 
  443    vcp->vc_act = &_spamd_interact;
  444    rv = TRU1;
  445 jleave:
  446    NYD2_LEAVE;
  447    return rv;
  448 }
  449 
  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 */
  452 static void
  453 __spamd_onsig(int sig) /* TODO someday, we won't need it no more */
  454 {
  455    NYD_X; /* Signal handler */
  456    __spamd_sig = sig;
  457    siglongjmp(__spamd_actjmp, 1);
  458 }
  459 
  460 static bool_t
  461 _spamd_interact(struct spam_vc *vcp)
  462 {
  463    struct spam_spamd *ssdp;
  464    size_t size, i;
  465    char *lp, *cp, * volatile headbuf = NULL;
  466    int volatile dsfd = -1;
  467    bool_t volatile rv = FAL0;
  468    NYD2_ENTER;
  469 
  470    ssdp = &vcp->vc_t.spamd;
  471 
  472    __spamd_sig = 0;
  473    hold_sigs();
  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')
  483          n_err(vcp->vc_esep);
  484       goto jleave;
  485    }
  486    rele_sigs();
  487 
  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));
  491       goto jleave;
  492    }
  493 
  494    if (connect(dsfd, (struct sockaddr*)&ssdp->d_sun, SUN_LEN(&ssdp->d_sun)) ==
  495          -1) {
  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));
  498       close(dsfd);
  499       dsfd = -1;
  500       goto jleave;
  501    }
  502 
  503    /* The command header, finalized with an empty line.
  504     * This needs to be written in a single write(2)! */
  505 # undef _X
  506 # define _X(X) do {memcpy(lp, X, sizeof(X) -1); lp += sizeof(X) -1;} while (0)
  507 
  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);
  517 
  518    switch (vcp->vc_action) {
  519    case _SPAM_RATE:
  520       _X(NETLINE("CHECK " SPAMD_IDENT));
  521       break;
  522    case _SPAM_HAM:
  523    case _SPAM_SPAM:
  524    case _SPAM_FORGET:
  525       _X(NETLINE("TELL " SPAMD_IDENT));
  526       break;
  527    }
  528 
  529    lp += snprintf(lp, size, NETLINE("Content-length: %" PRIuZ),
  530          (size_t)vcp->vc_mp->m_size);
  531 
  532    if (cp != NULL) {
  533       _X("User: ");
  534       memcpy(lp, cp, i);
  535       lp += i;
  536       _X(NETNL);
  537    }
  538 
  539    switch (vcp->vc_action) {
  540    case _SPAM_RATE:
  541       _X(NETNL);
  542       break;
  543    case _SPAM_HAM:
  544       _X(NETLINE("Message-class: ham")
  545          NETLINE("Set: local")
  546          NETNL);
  547       break;
  548    case _SPAM_SPAM:
  549       _X(NETLINE("Message-class: spam")
  550          NETLINE("Set: local")
  551          NETNL);
  552       break;
  553    case _SPAM_FORGET:
  554       if (vcp->vc_mp->m_flag & MSPAM)
  555          _X(NETLINE("Message-class: spam"));
  556       else
  557          _X(NETLINE("Message-class: ham"));
  558       _X(NETLINE("Remove: local")
  559          NETNL);
  560       break;
  561    }
  562 # undef _X
  563 
  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))
  568       goto jeso;
  569 
  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);
  574       if (i == 0) {
  575          if (ferror(vcp->vc_ifp))
  576             goto jeso;
  577          break;
  578       }
  579       size -= i;
  580 
  581       if (i != (size_t)write(dsfd, vcp->vc_buffer, i)) {
  582 jeso:
  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));
  585          goto jleave;
  586       }
  587    }
  588 
  589    /* We are finished, say so */
  590    shutdown(dsfd, SHUT_WR);
  591 
  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);
  595       if (j == -1)
  596          goto jeso;
  597       if (j == 0)
  598          break;
  599       size += j;
  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 */
  603       break;
  604    }
  605    i = 0;
  606    vcp->vc_buffer[size] = '\0';
  607 
  608    if (size == 0 || size == BUFFER_SIZE) {
  609 jebogus:
  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);
  612 # ifdef HAVE_DEVEL
  613       if (n_poption & n_PO_VERBVERB)
  614          n_err(">>> BUFFER: %s <<<\n", vcp->vc_buffer);
  615 # endif
  616       goto jleave;
  617    }
  618 
  619    /* From the response, read those lines that interest us */
  620    for (lp = vcp->vc_buffer; size > 0; ++i) {
  621       cp = lp;
  622       lp = strchr(lp, NETNL[0]);
  623       if (lp == NULL)
  624          goto jebogus;
  625       lp[0] = '\0';
  626       if (lp[1] != NETNL[1])
  627          goto jebogus;
  628       lp += 2;
  629       size -= PTR2SIZE(lp - cp);
  630 
  631       if (i == 0) {
  632          if (!strncmp(cp, "SPAMD/1.1 0 EX_OK", sizeof("SPAMD/1.1 0 EX_OK") -1))
  633             continue;
  634          if (vcp->vc_action != _SPAM_RATE ||
  635                strstr(cp, "Service Unavailable") == NULL)
  636             goto jebogus;
  637          else {
  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]);
  641             goto jleave;
  642          }
  643       } else if (i == 1) {
  644          switch (vcp->vc_action) {
  645          case _SPAM_RATE:
  646             if (strncmp(cp, "Spam: ", sizeof("Spam: ") -1))
  647                goto jebogus;
  648             cp += sizeof("Spam: ") -1;
  649 
  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;
  657             } else
  658                goto jebogus;
  659 
  660             while (blankspacechar(*cp))
  661                ++cp;
  662 
  663             if (*cp++ != ';')
  664                goto jebogus;
  665             else {
  666                char *xcp = strchr(cp, '/');
  667                if (xcp != NULL) {
  668                   size = PTR2SIZE(xcp - cp);
  669                   cp[size] = '\0';
  670                }
  671                _spam_rate2score(vcp, cp);
  672             }
  673             goto jdone;
  674 
  675          case _SPAM_HAM:
  676          case _SPAM_SPAM:
  677             /* Empty response means ok but "did nothing" */
  678             if (*cp != '\0' &&
  679                   strncmp(cp, "DidSet: local", sizeof("DidSet: local") -1))
  680                goto jebogus;
  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;
  686             goto jdone;
  687 
  688          case _SPAM_FORGET:
  689             if (*cp != '\0' &&
  690                   strncmp(cp, "DidRemove: local", sizeof("DidSet: local") -1))
  691                goto jebogus;
  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);
  695             goto jdone;
  696          }
  697       }
  698    }
  699 
  700 jdone:
  701    rv = TRU1;
  702 jleave:
  703    if (headbuf != NULL)
  704       n_lofi_free(headbuf);
  705    if (dsfd >= 0)
  706       close(dsfd);
  707 
  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);
  715 
  716    NYD2_LEAVE;
  717    if (__spamd_sig != 0) {
  718       sigset_t cset;
  719       sigemptyset(&cset);
  720       sigaddset(&cset, __spamd_sig);
  721       sigprocmask(SIG_UNBLOCK, &cset, NULL);
  722       n_raise(__spamd_sig);
  723       assert(rv == FAL0);
  724    }
  725    return rv;
  726 }
  727 #endif /* HAVE_SPAM_SPAMD */
  728 
  729 #ifdef HAVE_SPAM_FILTER
  730 static bool_t
  731 _spamfilter_setup(struct spam_vc *vcp)
  732 {
  733    struct spam_filter *sfp;
  734    char const *cp, *var;
  735    bool_t rv = FAL0;
  736    NYD2_ENTER;
  737 
  738    sfp = &vcp->vc_t.filter;
  739 
  740    switch (vcp->vc_action) {
  741    case _SPAM_RATE:
  742       cp = ok_vlook(spamfilter_rate);
  743       var = "spam-filter-rate";
  744       goto jonecmd;
  745    case _SPAM_HAM:
  746       cp = ok_vlook(spamfilter_ham);
  747       var = "spam-filter-ham";
  748       goto jonecmd;
  749    case _SPAM_SPAM:
  750       cp = ok_vlook(spamfilter_spam);
  751       var = "spam-filter-spam";
  752 jonecmd:
  753       if (cp == NULL) {
  754 jecmd:
  755          n_err(_("`%s': *%s* is not set\n"), _spam_cmds[vcp->vc_action], var);
  756          goto jleave;
  757       }
  758       sfp->f_super.cf_cmd = savestr(cp);
  759       break;
  760    case _SPAM_FORGET:
  761       var = "spam-filter-nospam";
  762       if ((cp =  ok_vlook(spamfilter_nospam)) == NULL)
  763          goto jecmd;
  764       sfp->f_cmd_nospam = savestr(cp);
  765       if ((cp =  ok_vlook(spamfilter_noham)) == NULL)
  766          goto jecmd;
  767       sfp->f_cmd_noham = savestr(cp);
  768       break;
  769    }
  770 
  771 # ifdef HAVE_REGEX
  772    if (vcp->vc_action == _SPAM_RATE &&
  773          (cp = ok_vlook(spamfilter_rate_scanscore)) != NULL) {
  774       int s;
  775       char const *bp;
  776 
  777       var = strchr(cp, ';');
  778       if (var == NULL) {
  779          n_err(_("`%s': *spamfilter-rate-scanscore*: missing semicolon;: %s\n"),
  780             _spam_cmds[vcp->vc_action], cp);
  781          goto jleave;
  782       }
  783       bp = &var[1];
  784 
  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);
  791          goto jleave;
  792       }
  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);
  798          goto jleave;
  799       }
  800 
  801       if ((s = regcomp(&sfp->f_score_regex, bp, REG_EXTENDED | REG_ICASE))
  802             != 0) {
  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));
  806          goto jleave;
  807       }
  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);
  812          goto jleave;
  813       }
  814    }
  815 # endif /* HAVE_REGEX */
  816 
  817    _spam_cf_setup(vcp, TRU1);
  818 
  819    vcp->vc_act = &_spamfilter_interact;
  820    vcp->vc_dtor = &_spamfilter_dtor;
  821    rv = TRU1;
  822 jleave:
  823    NYD2_LEAVE;
  824    return rv;
  825 }
  826 
  827 static bool_t
  828 _spamfilter_interact(struct spam_vc *vcp)
  829 {
  830 # ifdef HAVE_REGEX
  831    regmatch_t rem[SPAM_FILTER_MATCHES], *remp;
  832    struct spam_filter *sfp;
  833    char *cp;
  834 # endif
  835    bool_t rv;
  836    NYD2_ENTER;
  837 
  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;
  841 
  842    if (!(rv = _spam_cf_interact(vcp)))
  843       goto jleave;
  844 
  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;
  849       goto jleave;
  850    } else switch (WEXITSTATUS(vcp->vc_t.filter.f_super.cf_waitstat)) {
  851    case 2:
  852       vcp->vc_mp->m_flag |= MSPAMUNSURE;
  853       /* FALLTHRU */
  854    case 1:
  855       break;
  856    case 0:
  857       vcp->vc_mp->m_flag |= MSPAM;
  858       break;
  859    default:
  860       rv = FAL0;
  861       goto jleave;
  862    }
  863 
  864 # ifdef HAVE_REGEX
  865    sfp = &vcp->vc_t.filter;
  866 
  867    if (sfp->f_score_grpno == 0)
  868       goto jleave;
  869    if (sfp->f_super.cf_result == NULL) {
  870       n_err(_("`%s': *spamfilter-rate-scanscore*: filter does not "
  871          "produce output!\n"));
  872       goto jleave;
  873    }
  874 
  875    remp = rem + sfp->f_score_grpno;
  876 
  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;
  883       goto jleave;
  884    }
  885 
  886    cp = sfp->f_super.cf_result;
  887    cp[remp->rm_eo] = '\0';
  888    cp += remp->rm_so;
  889    _spam_rate2score(vcp, cp);
  890 # endif /* HAVE_REGEX */
  891 
  892 jleave:
  893    NYD2_LEAVE;
  894    return rv;
  895 }
  896 
  897 static void
  898 _spamfilter_dtor(struct spam_vc *vcp)
  899 {
  900    struct spam_filter *sfp;
  901    NYD2_ENTER;
  902 
  903    sfp = &vcp->vc_t.filter;
  904 
  905    if (sfp->f_super.cf_result != NULL)
  906       n_free(sfp->f_super.cf_result);
  907 # ifdef HAVE_REGEX
  908    if (sfp->f_score_grpno > 0)
  909       regfree(&sfp->f_score_regex);
  910 # endif
  911    NYD2_LEAVE;
  912 }
  913 #endif /* HAVE_SPAM_FILTER */
  914 
  915 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
  916 static void
  917 _spam_cf_setup(struct spam_vc *vcp, bool_t useshell)
  918 {
  919    struct str s;
  920    char const *cp;
  921    struct spam_cf *scfp;
  922    NYD2_ENTER;
  923    n_LCTA(2 < n_NELEM(scfp->cf_env), "Preallocated buffer too small");
  924 
  925    scfp = &vcp->vc_t.cf;
  926 
  927    if ((scfp->cf_useshell = useshell)) {
  928       scfp->cf_acmd = ok_vlook(SHELL);
  929       scfp->cf_a0 = "-c";
  930    }
  931 
  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;
  943    NYD2_LEAVE;
  944 }
  945 
  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 */
  948 static void
  949 __spam_cf_onsig(int sig) /* TODO someday, we won't need it no more */
  950 {
  951    NYD_X; /* Signal handler */
  952    __spam_cf_sig = sig;
  953    siglongjmp(__spam_cf_actjmp, 1);
  954 }
  955 
  956 static bool_t
  957 _spam_cf_interact(struct spam_vc *vcp)
  958 {
  959    struct spam_cf *scfp;
  960    int p2c[2], c2p[2];
  961    sigset_t cset;
  962    char const *cp;
  963    size_t size;
  964    pid_t volatile pid;
  965    enum {
  966       _NONE    = 0,
  967       _SIGHOLD = 1<<0,
  968       _P2C_0   = 1<<1,
  969       _P2C_1   = 1<<2,
  970       _P2C     = _P2C_0 | _P2C_1,
  971       _C2P_0   = 1<<3,
  972       _C2P_1   = 1<<4,
  973       _C2P     = _C2P_0 | _C2P_1,
  974       _JUMPED  = 1<<5,
  975       _RUNNING = 1<<6,
  976       _GOODRUN = 1<<7,
  977       _ERRORS  = 1<<8
  978    } volatile state = _NONE;
  979    NYD2_ENTER;
  980 
  981    scfp = &vcp->vc_t.cf;
  982    if (scfp->cf_result != NULL) {
  983       n_free(scfp->cf_result);
  984       scfp->cf_result = NULL;
  985    }
  986 
  987    /* TODO Avoid that we jump away; yet necessary signal mess */
  988    /*__spam_cf_sig = 0;*/
  989    hold_sigs();
  990    state |= _SIGHOLD;
  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 */
 1000 
 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));
 1004       goto jtail;
 1005    }
 1006    state |= _P2C;
 1007 
 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));
 1011       goto jtail;
 1012    }
 1013    state |= _C2P;
 1014 
 1015    if (sigsetjmp(__spam_cf_actjmp, 1)) {
 1016       if (*vcp->vc_esep != '\0')
 1017          n_err(vcp->vc_esep);
 1018       state |= _JUMPED;
 1019       goto jtail;
 1020    }
 1021    rele_sigs();
 1022    state &= ~_SIGHOLD;
 1023 
 1024    /* Start our command as requested */
 1025    sigemptyset(&cset);
 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) {
 1031       state |= _ERRORS;
 1032       goto jtail;
 1033    }
 1034    state |= _RUNNING;
 1035    close(p2c[0]);
 1036    state &= ~_P2C_0;
 1037 
 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;) {
 1042       size_t i;
 1043 
 1044       i = fread(vcp->vc_buffer, 1, n_MIN(size, BUFFER_SIZE), vcp->vc_ifp);
 1045       if (i == 0) {
 1046          if (ferror(vcp->vc_ifp))
 1047             state |= _ERRORS;
 1048          break;
 1049       }
 1050       size -= i;
 1051       if (i != (size_t)write(p2c[1], vcp->vc_buffer, i)) {
 1052          state |= _ERRORS;
 1053          break;
 1054       }
 1055    }
 1056 
 1057 jtail:
 1058    /* TODO Quite racy -- block anything for a while? */
 1059    if (state & _SIGHOLD) {
 1060       state &= ~_SIGHOLD;
 1061       rele_sigs();
 1062    }
 1063 
 1064    if (state & _P2C_0) {
 1065       state &= ~_P2C_0;
 1066       close(p2c[0]);
 1067    }
 1068    if (state & _C2P_1) {
 1069       state &= ~_C2P_1;
 1070       close(c2p[1]);
 1071    }
 1072    /* And cause EOF for the reader */
 1073    if (state & _P2C_1) {
 1074       state &= ~_P2C_1;
 1075       close(p2c[1]);
 1076    }
 1077 
 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);
 1082          if (i > 0) {
 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));
 1089                state |= _ERRORS;
 1090             } else {
 1091                scfp->cf_result = sbufdup(vcp->vc_buffer,
 1092                      PTR2SIZE(cp - vcp->vc_buffer));
 1093 /* FIXME consume child output until EOF??? */
 1094             }
 1095          } else if (i != 0)
 1096             state |= _ERRORS;
 1097       }
 1098 
 1099       state &= ~_RUNNING;
 1100       n_child_wait(pid, &scfp->cf_waitstat);
 1101       if (WIFEXITED(scfp->cf_waitstat))
 1102          state |= _GOODRUN;
 1103    }
 1104 
 1105    if (state & _C2P_0) {
 1106       state &= ~_C2P_0;
 1107       close(c2p[0]);
 1108    }
 1109 
 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);
 1117 
 1118    NYD2_LEAVE;
 1119    if (state & _JUMPED) {
 1120       assert(vcp->vc_dtor != NULL);
 1121       (*vcp->vc_dtor)(vcp);
 1122 
 1123       sigemptyset(&cset);
 1124       sigaddset(&cset, __spam_cf_sig);
 1125       sigprocmask(SIG_UNBLOCK, &cset, NULL);
 1126       n_raise(__spam_cf_sig);
 1127    }
 1128    return !(state & (_JUMPED | _ERRORS));
 1129 }
 1130 #endif /* HAVE_SPAM_SPAMC || HAVE_SPAM_FILTER */
 1131 
 1132 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_SPAMD ||\
 1133    (defined HAVE_SPAM_FILTER && defined HAVE_REGEX)
 1134 static void
 1135 _spam_rate2score(struct spam_vc *vcp, char *buf){
 1136    ui32_t m, s;
 1137    enum n_idec_state ids;
 1138    NYD2_ENTER;
 1139 
 1140    /* C99 */{ /* Overcome ISO C / compiler weirdness */
 1141       char const *cp;
 1142 
 1143       cp = buf;
 1144       ids = n_idec_ui32_cp(&m, buf, 10, &cp);
 1145       if((ids & n_IDEC_STATE_EMASK) & ~n_IDEC_STATE_EBASE)
 1146          goto jleave;
 1147       buf = n_UNCONST(cp);
 1148    }
 1149 
 1150    s = 0;
 1151    if(!(ids & n_IDEC_STATE_CONSUMED)){
 1152       /* Floating-point rounding for non-mathematicians */
 1153       char c1, c2, c3;
 1154 
 1155       ++buf; /* '.' */
 1156       if((c1 = buf[0]) != '\0' && (c2 = buf[1]) != '\0' &&
 1157             (c3 = buf[2]) != '\0'){
 1158          buf[2] = '\0';
 1159          if(c3 >= '5'){
 1160             if(c2 == '9'){
 1161                if(c1 == '9'){
 1162                   ++m;
 1163                   goto jscore_ok;
 1164                }else
 1165                   buf[0] = ++c1;
 1166                c2 = '0';
 1167             }else
 1168                ++c2;
 1169             buf[1] = c2;
 1170          }
 1171       }
 1172 
 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)
 1176          goto jleave;
 1177    }
 1178 
 1179 jscore_ok:
 1180    vcp->vc_mp->m_spamscore = (m << 8) | s;
 1181 jleave:
 1182    NYD2_LEAVE;
 1183 }
 1184 #endif /* _SPAM_SPAMC || _SPAM_SPAMD || (_SPAM_FILTER && HAVE_REGEX) */
 1185 
 1186 FL int
 1187 c_spam_clear(void *v)
 1188 {
 1189    int *ip;
 1190    NYD_ENTER;
 1191 
 1192    for (ip = v; *ip != 0; ++ip)
 1193       message[(size_t)*ip - 1].m_flag &= ~(MSPAM | MSPAMUNSURE);
 1194    NYD_LEAVE;
 1195    return 0;
 1196 }
 1197 
 1198 FL int
 1199 c_spam_set(void *v)
 1200 {
 1201    int *ip;
 1202    NYD_ENTER;
 1203 
 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;
 1207    }
 1208    NYD_LEAVE;
 1209    return 0;
 1210 }
 1211 
 1212 FL int
 1213 c_spam_forget(void *v)
 1214 {
 1215    int rv;
 1216    NYD_ENTER;
 1217 
 1218    rv = _spam_action(_SPAM_FORGET, (int*)v) ? OKAY : STOP;
 1219    NYD_LEAVE;
 1220    return rv;
 1221 }
 1222 
 1223 FL int
 1224 c_spam_ham(void *v)
 1225 {
 1226    int rv;
 1227    NYD_ENTER;
 1228 
 1229    rv = _spam_action(_SPAM_HAM, (int*)v) ? OKAY : STOP;
 1230    NYD_LEAVE;
 1231    return rv;
 1232 }
 1233 
 1234 FL int
 1235 c_spam_rate(void *v)
 1236 {
 1237    int rv;
 1238    NYD_ENTER;
 1239 
 1240    rv = _spam_action(_SPAM_RATE, (int*)v) ? OKAY : STOP;
 1241    NYD_LEAVE;
 1242    return rv;
 1243 }
 1244 
 1245 FL int
 1246 c_spam_spam(void *v)
 1247 {
 1248    int rv;
 1249    NYD_ENTER;
 1250 
 1251    rv = _spam_action(_SPAM_SPAM, (int*)v) ? OKAY : STOP;
 1252    NYD_LEAVE;
 1253    return rv;
 1254 }
 1255 #endif /* HAVE_SPAM */
 1256 
 1257 /* s-it-mode */