"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/spam.c" (25 Mar 2018, 34377 Bytes) of package /linux/misc/s-nail-14.9.10.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 last Fossies "Diffs" side-by-side code changes report: 14.9.6_vs_14.9.7.

    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  *
    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.
    9  *
   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.
   17  */
   18 #undef n_FILE
   19 #define n_FILE spam
   20 
   21 #ifndef HAVE_AMALGAMATION
   22 # include "nail.h"
   23 #endif
   24 
   25 EMPTY_FILE()
   26 #ifdef HAVE_SPAM
   27 
   28 #ifdef HAVE_SPAM_SPAMD
   29 # include <sys/socket.h>
   30 # include <sys/un.h>
   31 #endif
   32 
   33 /* This is chosen rather arbitrarily.
   34  * It must be able to swallow the first line of a rate response,
   35  * and an entire CHECK/TELL spamd(1) response */
   36 #if BUFFER_SIZE < 1024
   37 # error *spam-interface* BUFFER_SIZE constraints are not matched
   38 #endif
   39 
   40 #ifdef HAVE_SPAM_SPAMD
   41 # define SPAMD_IDENT          "SPAMC/1.5"
   42 # ifndef SUN_LEN
   43 #  define SUN_LEN(SUP) \
   44         (sizeof(*(SUP)) - sizeof((SUP)->sun_path) + strlen((SUP)->sun_path))
   45 # endif
   46 #endif
   47 
   48 #ifdef HAVE_SPAM_FILTER
   49   /* n_NELEM() of regmatch_t groups */
   50 # define SPAM_FILTER_MATCHES  32u
   51 #endif
   52 
   53 enum spam_action {
   54    _SPAM_RATE,
   55    _SPAM_HAM,
   56    _SPAM_SPAM,
   57    _SPAM_FORGET
   58 };
   59 
   60 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
   61 struct spam_cf {
   62    char const        *cf_cmd;
   63    char              *cf_result; /* _SPAM_RATE: first response line */
   64    int               cf_waitstat;
   65    ui8_t             __pad[3];
   66    bool_t            cf_useshell;
   67    /* .cf_cmd may be adjusted for each call (`spamforget')... */
   68    char const        *cf_acmd;
   69    char const        *cf_a0;
   70    char const        *cf_env[4];
   71    sighandler_type   cf_otstp;
   72    sighandler_type   cf_ottin;
   73    sighandler_type   cf_ottou;
   74    sighandler_type   cf_ohup;
   75    sighandler_type   cf_opipe;
   76    sighandler_type   cf_oint;
   77    sighandler_type   cf_oquit;
   78 };
   79 #endif
   80 
   81 #ifdef HAVE_SPAM_SPAMC
   82 struct spam_spamc {
   83    struct spam_cf    c_super;
   84    char const        *c_cmd_arr[9];
   85 };
   86 #endif
   87 
   88 #ifdef HAVE_SPAM_SPAMD
   89 struct spam_spamd {
   90    struct str        d_user;
   91    sighandler_type   d_otstp;
   92    sighandler_type   d_ottin;
   93    sighandler_type   d_ottou;
   94    sighandler_type   d_ohup;
   95    sighandler_type   d_opipe;
   96    sighandler_type   d_oint;
   97    sighandler_type   d_oquit;
   98    struct sockaddr_un d_sun;
   99 };
  100 #endif
  101 
  102 #ifdef HAVE_SPAM_FILTER
  103 struct spam_filter {
  104    struct spam_cf    f_super;
  105    char const        *f_cmd_nospam; /* Working relative to current message.. */
  106    char const        *f_cmd_noham;
  107 # ifdef HAVE_REGEX
  108    ui8_t             __pad[4];
  109    ui32_t            f_score_grpno; /* 0 for not set */
  110    regex_t           f_score_regex;
  111 # endif
  112 };
  113 #endif
  114 
  115 struct spam_vc {
  116    enum spam_action  vc_action;
  117    bool_t            vc_verbose;    /* Verbose output */
  118    bool_t            vc_progress;   /* "Progress meter" (mutual verbose) */
  119    ui8_t             __pad[2];
  120    bool_t            (*vc_act)(struct spam_vc *);
  121    void              (*vc_dtor)(struct spam_vc *);
  122    char              *vc_buffer;    /* I/O buffer, BUFFER_SIZE bytes */
  123    size_t            vc_mno;        /* Current message number */
  124    struct message    *vc_mp;        /* Current message */
  125    FILE              *vc_ifp;       /* Input stream on .vc_mp */
  126    union {
  127 #ifdef HAVE_SPAM_SPAMC
  128       struct spam_spamc    spamc;
  129 #endif
  130 #ifdef HAVE_SPAM_SPAMD
  131       struct spam_spamd    spamd;
  132 #endif
  133 #ifdef HAVE_SPAM_FILTER
  134       struct spam_filter   filter;
  135 #endif
  136 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
  137    struct spam_cf          cf;
  138 #endif
  139    }                 vc_t;
  140    char const        *vc_esep;      /* Error separator for progress mode */
  141 };
  142 
  143 /* Indices according to enum spam_action */
  144 static char const _spam_cmds[][16] = {
  145    "spamrate", "spamham", "spamspam", "spamforget"
  146 };
  147 
  148 /* Shared action setup */
  149 static bool_t  _spam_action(enum spam_action sa, int *ip);
  150 
  151 /* *spam-interface*=spamc: initialize, communicate */
  152 #ifdef HAVE_SPAM_SPAMC
  153 static bool_t  _spamc_setup(struct spam_vc *vcp);
  154 static bool_t  _spamc_interact(struct spam_vc *vcp);
  155 static void    _spamc_dtor(struct spam_vc *vcp);
  156 #endif
  157 
  158 /* *spam-interface*=spamd: initialize, communicate */
  159 #ifdef HAVE_SPAM_SPAMD
  160 static bool_t  _spamd_setup(struct spam_vc *vcp);
  161 static bool_t  _spamd_interact(struct spam_vc *vcp);
  162 #endif
  163 
  164 /* *spam-interface*=filter: initialize, communicate */
  165 #ifdef HAVE_SPAM_FILTER
  166 static bool_t  _spamfilter_setup(struct spam_vc *vcp);
  167 static bool_t  _spamfilter_interact(struct spam_vc *vcp);
  168 static void    _spamfilter_dtor(struct spam_vc *vcp);
  169 #endif
  170 
  171 /* *spam-interface*=(spamc|filter): create child + communication */
  172 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
  173 static void    _spam_cf_setup(struct spam_vc *vcp, bool_t useshell);
  174 static bool_t  _spam_cf_interact(struct spam_vc *vcp);
  175 #endif
  176 
  177 /* Convert a floating-point spam rate into message.m_spamscore */
  178 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_SPAMD ||\
  179    (defined HAVE_SPAM_FILTER && defined HAVE_REGEX)
  180 static void    _spam_rate2score(struct spam_vc *vcp, char *buf);
  181 #endif
  182 
  183 static bool_t
  184 _spam_action(enum spam_action sa, int *ip)
  185 {
  186    struct spam_vc vc;
  187    size_t maxsize, skipped, cnt, curr;
  188    char const *cp;
  189    bool_t ok = FAL0;
  190    NYD_ENTER;
  191 
  192    memset(&vc, 0, sizeof vc);
  193    vc.vc_action = sa;
  194    vc.vc_verbose = ((n_poption & n_PO_VERB) != 0);
  195    vc.vc_progress = (!vc.vc_verbose && ((n_psonce & n_PSO_INTERACTIVE) != 0));
  196    vc.vc_esep = vc.vc_progress ? "\n" : n_empty;
  197 
  198    /* Check and setup the desired spam interface */
  199    if ((cp = ok_vlook(spam_interface)) == NULL) {
  200       n_err(_("`%s': no *spam-interface* set\n"), _spam_cmds[sa]);
  201       goto jleave;
  202 #ifdef HAVE_SPAM_SPAMC
  203    } else if (!asccasecmp(cp, "spamc")) {
  204        if (!_spamc_setup(&vc))
  205          goto jleave;
  206 #endif
  207 #ifdef HAVE_SPAM_SPAMD
  208    } else if (!asccasecmp(cp, "spamd")) { /* TODO v15: remove */
  209       n_OBSOLETE(_("*spam-interface*=spamd is obsolete, please use =spamc"));
  210       if (!_spamd_setup(&vc))
  211          goto jleave;
  212 #endif
  213 #ifdef HAVE_SPAM_FILTER
  214    } else if (!asccasecmp(cp, "filter")) {
  215       if (!_spamfilter_setup(&vc))
  216          goto jleave;
  217 #endif
  218    } else {
  219       n_err(_("`%s': unknown / unsupported *spam-interface*: %s\n"),
  220          _spam_cmds[sa], cp);
  221       goto jleave;
  222    }
  223 
  224    /* *spam-maxsize* we do handle ourselfs instead */
  225    if ((cp = ok_vlook(spam_maxsize)) == NULL ||
  226          (n_idec_ui32_cp(&maxsize, cp, 0, NULL), maxsize) == 0)
  227       maxsize = SPAM_MAXSIZE;
  228 
  229    /* Finally get an I/O buffer */
  230    vc.vc_buffer = salloc(BUFFER_SIZE);
  231 
  232    skipped = cnt = 0;
  233    if (vc.vc_progress) {
  234       while (ip[cnt] != 0)
  235          ++cnt;
  236    }
  237    for (curr = 0, ok = TRU1; *ip != 0; --cnt, ++curr, ++ip) {
  238       vc.vc_mno = (size_t)*ip;
  239       vc.vc_mp = message + vc.vc_mno - 1;
  240       if (sa == _SPAM_RATE)
  241          vc.vc_mp->m_spamscore = 0;
  242 
  243       if (vc.vc_mp->m_size > maxsize) {
  244          if (vc.vc_verbose)
  245             n_err(_("`%s': message %lu exceeds maxsize (%lu > %lu), skip\n"),
  246                _spam_cmds[sa], (ul_i)vc.vc_mno, (ul_i)(size_t)vc.vc_mp->m_size,
  247                (ul_i)maxsize);
  248          else if (vc.vc_progress) {
  249             fprintf(n_stdout, "\r%s: !%-6" PRIuZ " %6" PRIuZ "/%-6" PRIuZ,
  250                _spam_cmds[sa], vc.vc_mno, cnt, curr);
  251             fflush(n_stdout);
  252          }
  253          ++skipped;
  254       } else {
  255          if (vc.vc_verbose)
  256             n_err(_("`%s': message %lu\n"), _spam_cmds[sa], (ul_i)vc.vc_mno);
  257          else if (vc.vc_progress) {
  258             fprintf(n_stdout, "\r%s: .%-6" PRIuZ " %6" PRIuZ "/%-6" PRIuZ,
  259                _spam_cmds[sa], vc.vc_mno, cnt, curr);
  260             fflush(n_stdout);
  261          }
  262 
  263          setdot(vc.vc_mp);
  264          if ((vc.vc_ifp = setinput(&mb, vc.vc_mp, NEED_BODY)) == NULL) {
  265             n_err(_("%s`%s': cannot load message %lu: %s\n"),
  266                vc.vc_esep, _spam_cmds[sa], (ul_i)vc.vc_mno,
  267                n_err_to_doc(n_err_no));
  268             ok = FAL0;
  269             break;
  270          }
  271 
  272          if (!(ok = (*vc.vc_act)(&vc)))
  273             break;
  274       }
  275    }
  276    if (vc.vc_progress) {
  277       if (curr > 0)
  278          fprintf(n_stdout, _(" %s (%" PRIuZ "/%" PRIuZ " all/skipped)\n"),
  279             (ok ? _("done") : V_(n_error)), curr, skipped);
  280       fflush(n_stdout);
  281    }
  282 
  283    if (vc.vc_dtor != NULL)
  284       (*vc.vc_dtor)(&vc);
  285 jleave:
  286    NYD_LEAVE;
  287    return !ok;
  288 }
  289 
  290 #ifdef HAVE_SPAM_SPAMC
  291 static bool_t
  292 _spamc_setup(struct spam_vc *vcp)
  293 {
  294    struct spam_spamc *sscp;
  295    struct str str;
  296    char const **args, *cp;
  297    bool_t rv = FAL0;
  298    NYD2_ENTER;
  299 
  300    sscp = &vcp->vc_t.spamc;
  301    args = sscp->c_cmd_arr;
  302 
  303    if ((cp = ok_vlook(spamc_command)) == NULL) {
  304 # ifdef SPAM_SPAMC_PATH
  305       cp = SPAM_SPAMC_PATH;
  306 # else
  307       n_err(_("`%s': *spamc-command* is not set\n"),
  308          _spam_cmds[vcp->vc_action]);
  309       goto jleave;
  310 # endif
  311    }
  312    *args++ = cp;
  313 
  314    switch (vcp->vc_action) {
  315    case _SPAM_RATE:
  316       *args = "-c";
  317       break;
  318    case _SPAM_HAM:
  319       args[1] = "ham";
  320       goto jlearn;
  321    case _SPAM_SPAM:
  322       args[1] = "spam";
  323       goto jlearn;
  324    case _SPAM_FORGET:
  325       args[1] = "forget";
  326 jlearn:
  327       *args = "-L";
  328       ++args;
  329       break;
  330    }
  331    ++args;
  332 
  333    *args++ = "-l"; /* --log-to-stderr */
  334    *args++ = "-x"; /* No "safe callback", we need to react on errors! */
  335 
  336    if ((cp = ok_vlook(spamc_arguments)) != NULL)
  337       *args++ = cp;
  338 
  339    if ((cp = ok_vlook(spamc_user)) != NULL) {
  340       if (*cp == '\0')
  341          cp = ok_vlook(LOGNAME);
  342       *args++ = "-u";
  343       *args++ = cp;
  344    }
  345    assert(PTR2SIZE(args - sscp->c_cmd_arr) <= n_NELEM(sscp->c_cmd_arr));
  346 
  347    *args = NULL;
  348    sscp->c_super.cf_cmd = str_concat_cpa(&str, sscp->c_cmd_arr, " ")->s;
  349    if (vcp->vc_verbose)
  350       n_err(_("spamc(1) via %s\n"),
  351          n_shexp_quote_cp(sscp->c_super.cf_cmd, FAL0));
  352 
  353    _spam_cf_setup(vcp, FAL0);
  354 
  355    vcp->vc_act = &_spamc_interact;
  356    vcp->vc_dtor = &_spamc_dtor;
  357    rv = TRU1;
  358 # ifndef SPAM_SPAMC_PATH
  359 jleave:
  360 # endif
  361    NYD2_LEAVE;
  362    return rv;
  363 }
  364 
  365 static bool_t
  366 _spamc_interact(struct spam_vc *vcp)
  367 {
  368    bool_t rv;
  369    NYD2_ENTER;
  370 
  371    if (!(rv = _spam_cf_interact(vcp)))
  372       goto jleave;
  373 
  374    vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  375    if (vcp->vc_action != _SPAM_RATE) {
  376       if (vcp->vc_action == _SPAM_SPAM)
  377          vcp->vc_mp->m_flag |= MSPAM;
  378    } else {
  379       char *buf, *cp;
  380 
  381       switch (WEXITSTATUS(vcp->vc_t.spamc.c_super.cf_waitstat)) {
  382       case 1:
  383          vcp->vc_mp->m_flag |= MSPAM;
  384          /* FALLTHRU */
  385       case 0:
  386          break;
  387       default:
  388          rv = FAL0;
  389          goto jleave;
  390       }
  391 
  392       if ((cp = strchr(buf = vcp->vc_t.spamc.c_super.cf_result, '/')) != NULL)
  393          buf[PTR2SIZE(cp - buf)] = '\0';
  394       _spam_rate2score(vcp, buf);
  395    }
  396 jleave:
  397    NYD2_LEAVE;
  398    return rv;
  399 }
  400 
  401 static void
  402 _spamc_dtor(struct spam_vc *vcp)
  403 {
  404    NYD2_ENTER;
  405    if (vcp->vc_t.spamc.c_super.cf_result != NULL)
  406       free(vcp->vc_t.spamc.c_super.cf_result);
  407    NYD2_LEAVE;
  408 }
  409 #endif /* HAVE_SPAM_SPAMC */
  410 
  411 #ifdef HAVE_SPAM_SPAMD
  412 static bool_t
  413 _spamd_setup(struct spam_vc *vcp)
  414 {
  415    struct spam_spamd *ssdp;
  416    char const *cp;
  417    size_t l;
  418    bool_t rv = FAL0;
  419    NYD2_ENTER;
  420 
  421    ssdp = &vcp->vc_t.spamd;
  422 
  423    if ((cp = ok_vlook(spamd_user)) != NULL) {
  424       if (*cp == '\0')
  425          cp = ok_vlook(LOGNAME);
  426       ssdp->d_user.l = strlen(ssdp->d_user.s = n_UNCONST(cp));
  427    }
  428 
  429    if ((cp = ok_vlook(spamd_socket)) == NULL) {
  430       n_err(_("`%s': required *spamd-socket* is not set\n"),
  431          _spam_cmds[vcp->vc_action]);
  432       goto jleave;
  433    }
  434    if ((l = strlen(cp) +1) >= sizeof(ssdp->d_sun.sun_path)) {
  435       n_err(_("`%s': *spamd-socket* too long: %s\n"),
  436          _spam_cmds[vcp->vc_action], n_shexp_quote_cp(cp, FAL0));
  437       goto jleave;
  438    }
  439    ssdp->d_sun.sun_family = AF_UNIX;
  440    memcpy(ssdp->d_sun.sun_path, cp, l);
  441 
  442    vcp->vc_act = &_spamd_interact;
  443    rv = TRU1;
  444 jleave:
  445    NYD2_LEAVE;
  446    return rv;
  447 }
  448 
  449 static sigjmp_buf    __spamd_actjmp; /* TODO oneday, we won't need it no more */
  450 static int volatile  __spamd_sig; /* TODO oneday, we won't need it no more */
  451 static void
  452 __spamd_onsig(int sig) /* TODO someday, we won't need it no more */
  453 {
  454    NYD_X; /* Signal handler */
  455    __spamd_sig = sig;
  456    siglongjmp(__spamd_actjmp, 1);
  457 }
  458 
  459 static bool_t
  460 _spamd_interact(struct spam_vc *vcp)
  461 {
  462    struct spam_spamd *ssdp;
  463    size_t size, i;
  464    char *lp, *cp, * volatile headbuf = NULL;
  465    int volatile dsfd = -1;
  466    bool_t volatile rv = FAL0;
  467    NYD2_ENTER;
  468 
  469    ssdp = &vcp->vc_t.spamd;
  470 
  471    __spamd_sig = 0;
  472    hold_sigs();
  473    ssdp->d_otstp = safe_signal(SIGTSTP, SIG_DFL);
  474    ssdp->d_ottin = safe_signal(SIGTTIN, SIG_DFL);
  475    ssdp->d_ottou = safe_signal(SIGTTOU, SIG_DFL);
  476    ssdp->d_opipe = safe_signal(SIGPIPE, SIG_IGN);
  477    ssdp->d_ohup = safe_signal(SIGHUP, &__spamd_onsig);
  478    ssdp->d_oint = safe_signal(SIGINT, &__spamd_onsig);
  479    ssdp->d_oquit = safe_signal(SIGQUIT, &__spamd_onsig);
  480    if (sigsetjmp(__spamd_actjmp, 1)) {
  481       if (*vcp->vc_esep != '\0')
  482          n_err(vcp->vc_esep);
  483       goto jleave;
  484    }
  485    rele_sigs();
  486 
  487    if ((dsfd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
  488       n_err(_("%s`%s': can't create unix(4) socket: %s\n"),
  489          vcp->vc_esep, _spam_cmds[vcp->vc_action], n_err_to_doc(n_err_no));
  490       goto jleave;
  491    }
  492 
  493    if (connect(dsfd, (struct sockaddr*)&ssdp->d_sun, SUN_LEN(&ssdp->d_sun)) ==
  494          -1) {
  495       n_err(_("%s`%s': can't connect to *spam-socket*: %s\n"),
  496          vcp->vc_esep, _spam_cmds[vcp->vc_action], n_err_to_doc(n_err_no));
  497       close(dsfd);
  498       dsfd = -1;
  499       goto jleave;
  500    }
  501 
  502    /* The command header, finalized with an empty line.
  503     * This needs to be written in a single write(2)! */
  504 # undef _X
  505 # define _X(X) do {memcpy(lp, X, sizeof(X) -1); lp += sizeof(X) -1;} while (0)
  506 
  507    i = ((cp = ssdp->d_user.s) != NULL) ? ssdp->d_user.l : 0;
  508    size = sizeof(NETLINE("A_VERY_LONG_COMMAND " SPAMD_IDENT)) +
  509          sizeof(NETLINE("Content-length: 9223372036854775807")) +
  510          ((cp != NULL) ? sizeof("User: ") + i + sizeof(NETNL) : 0) +
  511          sizeof(NETLINE("Message-class: spam")) +
  512          sizeof(NETLINE("Set: local")) +
  513          sizeof(NETLINE("Remove: local")) +
  514          sizeof(NETNL) /*+1*/;
  515    lp = headbuf = n_lofi_alloc(size);
  516 
  517    switch (vcp->vc_action) {
  518    case _SPAM_RATE:
  519       _X(NETLINE("CHECK " SPAMD_IDENT));
  520       break;
  521    case _SPAM_HAM:
  522    case _SPAM_SPAM:
  523    case _SPAM_FORGET:
  524       _X(NETLINE("TELL " SPAMD_IDENT));
  525       break;
  526    }
  527 
  528    lp += snprintf(lp, size, NETLINE("Content-length: %" PRIuZ),
  529          (size_t)vcp->vc_mp->m_size);
  530 
  531    if (cp != NULL) {
  532       _X("User: ");
  533       memcpy(lp, cp, i);
  534       lp += i;
  535       _X(NETNL);
  536    }
  537 
  538    switch (vcp->vc_action) {
  539    case _SPAM_RATE:
  540       _X(NETNL);
  541       break;
  542    case _SPAM_HAM:
  543       _X(NETLINE("Message-class: ham")
  544          NETLINE("Set: local")
  545          NETNL);
  546       break;
  547    case _SPAM_SPAM:
  548       _X(NETLINE("Message-class: spam")
  549          NETLINE("Set: local")
  550          NETNL);
  551       break;
  552    case _SPAM_FORGET:
  553       if (vcp->vc_mp->m_flag & MSPAM)
  554          _X(NETLINE("Message-class: spam"));
  555       else
  556          _X(NETLINE("Message-class: ham"));
  557       _X(NETLINE("Remove: local")
  558          NETNL);
  559       break;
  560    }
  561 # undef _X
  562 
  563    i = PTR2SIZE(lp - headbuf);
  564    if (n_poption & n_PO_VERBVERB)
  565       n_err(">>> %.*s <<<\n", (int)i, headbuf);
  566    if (i != (size_t)write(dsfd, headbuf, i))
  567       goto jeso;
  568 
  569    /* Then simply pass through the message "as-is" */
  570    for (size = vcp->vc_mp->m_size; size > 0;) {
  571       i = fread(vcp->vc_buffer, sizeof *vcp->vc_buffer,
  572             n_MIN(size, BUFFER_SIZE), vcp->vc_ifp);
  573       if (i == 0) {
  574          if (ferror(vcp->vc_ifp))
  575             goto jeso;
  576          break;
  577       }
  578       size -= i;
  579 
  580       if (i != (size_t)write(dsfd, vcp->vc_buffer, i)) {
  581 jeso:
  582          n_err(_("%s`%s': I/O on *spamd-socket* failed: %s\n"),
  583             vcp->vc_esep, _spam_cmds[vcp->vc_action], n_err_to_doc(n_err_no));
  584          goto jleave;
  585       }
  586    }
  587 
  588    /* We are finished, say so */
  589    shutdown(dsfd, SHUT_WR);
  590 
  591    /* Be aware on goto: i will be a line counter after this loop! */
  592    for (size = 0, i = BUFFER_SIZE -1;;) {
  593       ssize_t j = read(dsfd, vcp->vc_buffer + size, i);
  594       if (j == -1)
  595          goto jeso;
  596       if (j == 0)
  597          break;
  598       size += j;
  599       /* For the current way of doing things a single read will suffice.
  600        * Note we'll be "penaltized" when awaiting EOF on the socket, at least
  601        * in blocking mode, so do avoid that and break off */
  602       break;
  603    }
  604    i = 0;
  605    vcp->vc_buffer[size] = '\0';
  606 
  607    if (size == 0 || size == BUFFER_SIZE) {
  608 jebogus:
  609       n_err(_("%s`%s': bogus spamd(1) I/O interaction (%lu)\n"),
  610          vcp->vc_esep, _spam_cmds[vcp->vc_action], (ul_i)i);
  611 # ifdef HAVE_DEVEL
  612       if (n_poption & n_PO_VERBVERB)
  613          n_err(">>> BUFFER: %s <<<\n", vcp->vc_buffer);
  614 # endif
  615       goto jleave;
  616    }
  617 
  618    /* From the response, read those lines that interest us */
  619    for (lp = vcp->vc_buffer; size > 0; ++i) {
  620       cp = lp;
  621       lp = strchr(lp, NETNL[0]);
  622       if (lp == NULL)
  623          goto jebogus;
  624       lp[0] = '\0';
  625       if (lp[1] != NETNL[1])
  626          goto jebogus;
  627       lp += 2;
  628       size -= PTR2SIZE(lp - cp);
  629 
  630       if (i == 0) {
  631          if (!strncmp(cp, "SPAMD/1.1 0 EX_OK", sizeof("SPAMD/1.1 0 EX_OK") -1))
  632             continue;
  633          if (vcp->vc_action != _SPAM_RATE ||
  634                strstr(cp, "Service Unavailable") == NULL)
  635             goto jebogus;
  636          else {
  637             /* Unfortunately a missing --allow-tell drops connection.. */
  638             n_err(_("%s`%s': service not available in spamd(1) instance\n"),
  639                vcp->vc_esep, _spam_cmds[vcp->vc_action]);
  640             goto jleave;
  641          }
  642       } else if (i == 1) {
  643          switch (vcp->vc_action) {
  644          case _SPAM_RATE:
  645             if (strncmp(cp, "Spam: ", sizeof("Spam: ") -1))
  646                goto jebogus;
  647             cp += sizeof("Spam: ") -1;
  648 
  649             if (!strncmp(cp, "False", sizeof("False") -1)) {
  650                cp += sizeof("False") -1;
  651                vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  652             } else if (!strncmp(cp, "True", sizeof("True") -1)) {
  653                cp += sizeof("True") -1;
  654                vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  655                vcp->vc_mp->m_flag |= MSPAM;
  656             } else
  657                goto jebogus;
  658 
  659             while (blankspacechar(*cp))
  660                ++cp;
  661 
  662             if (*cp++ != ';')
  663                goto jebogus;
  664             else {
  665                char *xcp = strchr(cp, '/');
  666                if (xcp != NULL) {
  667                   size = PTR2SIZE(xcp - cp);
  668                   cp[size] = '\0';
  669                }
  670                _spam_rate2score(vcp, cp);
  671             }
  672             goto jdone;
  673 
  674          case _SPAM_HAM:
  675          case _SPAM_SPAM:
  676             /* Empty response means ok but "did nothing" */
  677             if (*cp != '\0' &&
  678                   strncmp(cp, "DidSet: local", sizeof("DidSet: local") -1))
  679                goto jebogus;
  680             if (*cp == '\0' && vcp->vc_verbose)
  681                n_err(_("\tBut spamd(1) \"did nothing\" for message\n"));
  682             vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  683             if (vcp->vc_action == _SPAM_SPAM)
  684                vcp->vc_mp->m_flag |= MSPAM;
  685             goto jdone;
  686 
  687          case _SPAM_FORGET:
  688             if (*cp != '\0' &&
  689                   strncmp(cp, "DidRemove: local", sizeof("DidSet: local") -1))
  690                goto jebogus;
  691             if (*cp == '\0' && vcp->vc_verbose)
  692                n_err(_("\tBut spamd(1) \"did nothing\" for message\n"));
  693             vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  694             goto jdone;
  695          }
  696       }
  697    }
  698 
  699 jdone:
  700    rv = TRU1;
  701 jleave:
  702    if (headbuf != NULL)
  703       ac_free(headbuf);
  704    if (dsfd >= 0)
  705       close(dsfd);
  706 
  707    safe_signal(SIGQUIT, ssdp->d_oquit);
  708    safe_signal(SIGINT, ssdp->d_oint);
  709    safe_signal(SIGHUP, ssdp->d_ohup);
  710    safe_signal(SIGPIPE, ssdp->d_opipe);
  711    safe_signal(SIGTSTP, ssdp->d_otstp);
  712    safe_signal(SIGTTIN, ssdp->d_ottin);
  713    safe_signal(SIGTTOU, ssdp->d_ottou);
  714 
  715    NYD2_LEAVE;
  716    if (__spamd_sig != 0) {
  717       sigset_t cset;
  718       sigemptyset(&cset);
  719       sigaddset(&cset, __spamd_sig);
  720       sigprocmask(SIG_UNBLOCK, &cset, NULL);
  721       n_raise(__spamd_sig);
  722       assert(rv == FAL0);
  723    }
  724    return rv;
  725 }
  726 #endif /* HAVE_SPAM_SPAMD */
  727 
  728 #ifdef HAVE_SPAM_FILTER
  729 static bool_t
  730 _spamfilter_setup(struct spam_vc *vcp)
  731 {
  732    struct spam_filter *sfp;
  733    char const *cp, *var;
  734    bool_t rv = FAL0;
  735    NYD2_ENTER;
  736 
  737    sfp = &vcp->vc_t.filter;
  738 
  739    switch (vcp->vc_action) {
  740    case _SPAM_RATE:
  741       cp = ok_vlook(spamfilter_rate);
  742       var = "spam-filter-rate";
  743       goto jonecmd;
  744    case _SPAM_HAM:
  745       cp = ok_vlook(spamfilter_ham);
  746       var = "spam-filter-ham";
  747       goto jonecmd;
  748    case _SPAM_SPAM:
  749       cp = ok_vlook(spamfilter_spam);
  750       var = "spam-filter-spam";
  751 jonecmd:
  752       if (cp == NULL) {
  753 jecmd:
  754          n_err(_("`%s': *%s* is not set\n"), _spam_cmds[vcp->vc_action], var);
  755          goto jleave;
  756       }
  757       sfp->f_super.cf_cmd = savestr(cp);
  758       break;
  759    case _SPAM_FORGET:
  760       var = "spam-filter-nospam";
  761       if ((cp =  ok_vlook(spamfilter_nospam)) == NULL)
  762          goto jecmd;
  763       sfp->f_cmd_nospam = savestr(cp);
  764       if ((cp =  ok_vlook(spamfilter_noham)) == NULL)
  765          goto jecmd;
  766       sfp->f_cmd_noham = savestr(cp);
  767       break;
  768    }
  769 
  770 # ifdef HAVE_REGEX
  771    if (vcp->vc_action == _SPAM_RATE &&
  772          (cp = ok_vlook(spamfilter_rate_scanscore)) != NULL) {
  773       int s;
  774       char const *bp;
  775 
  776       var = strchr(cp, ';');
  777       if (var == NULL) {
  778          n_err(_("`%s': *spamfilter-rate-scanscore*: missing semicolon;: %s\n"),
  779             _spam_cmds[vcp->vc_action], cp);
  780          goto jleave;
  781       }
  782       bp = &var[1];
  783 
  784       if((n_idec_buf(&sfp->f_score_grpno, cp, PTR2SIZE(var - cp), 0,
  785                   n_IDEC_MODE_LIMIT_32BIT, NULL
  786                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
  787             ) != n_IDEC_STATE_CONSUMED){
  788          n_err(_("`%s': *spamfilter-rate-scanscore*: bad group: %s\n"),
  789             _spam_cmds[vcp->vc_action], cp);
  790          goto jleave;
  791       }
  792       if (sfp->f_score_grpno >= SPAM_FILTER_MATCHES) {
  793          n_err(_("`%s': *spamfilter-rate-scanscore*: "
  794             "group %u excesses limit %u\n"),
  795             _spam_cmds[vcp->vc_action], sfp->f_score_grpno,
  796             SPAM_FILTER_MATCHES);
  797          goto jleave;
  798       }
  799 
  800       if ((s = regcomp(&sfp->f_score_regex, bp, REG_EXTENDED | REG_ICASE))
  801             != 0) {
  802          n_err(_("`%s': invalid *spamfilter-rate-scanscore* regex: %s: %s\n"),
  803             _spam_cmds[vcp->vc_action], n_shexp_quote_cp(cp, FAL0),
  804             n_regex_err_to_doc(NULL, s));
  805          goto jleave;
  806       }
  807       if (sfp->f_score_grpno > sfp->f_score_regex.re_nsub) {
  808          regfree(&sfp->f_score_regex);
  809          n_err(_("`%s': *spamfilter-rate-scanscore*: no group %u: %s\n"),
  810             _spam_cmds[vcp->vc_action], sfp->f_score_grpno, cp);
  811          goto jleave;
  812       }
  813    }
  814 # endif /* HAVE_REGEX */
  815 
  816    _spam_cf_setup(vcp, TRU1);
  817 
  818    vcp->vc_act = &_spamfilter_interact;
  819    vcp->vc_dtor = &_spamfilter_dtor;
  820    rv = TRU1;
  821 jleave:
  822    NYD2_LEAVE;
  823    return rv;
  824 }
  825 
  826 static bool_t
  827 _spamfilter_interact(struct spam_vc *vcp)
  828 {
  829 # ifdef HAVE_REGEX
  830    regmatch_t rem[SPAM_FILTER_MATCHES], *remp;
  831    struct spam_filter *sfp;
  832    char *cp;
  833 # endif
  834    bool_t rv;
  835    NYD2_ENTER;
  836 
  837    if (vcp->vc_action == _SPAM_FORGET)
  838       vcp->vc_t.cf.cf_cmd = (vcp->vc_mp->m_flag & MSPAM)
  839             ? vcp->vc_t.filter.f_cmd_nospam : vcp->vc_t.filter.f_cmd_noham;
  840 
  841    if (!(rv = _spam_cf_interact(vcp)))
  842       goto jleave;
  843 
  844    vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
  845    if (vcp->vc_action != _SPAM_RATE) {
  846       if (vcp->vc_action == _SPAM_SPAM)
  847          vcp->vc_mp->m_flag |= MSPAM;
  848       goto jleave;
  849    } else switch (WEXITSTATUS(vcp->vc_t.filter.f_super.cf_waitstat)) {
  850    case 2:
  851       vcp->vc_mp->m_flag |= MSPAMUNSURE;
  852       /* FALLTHRU */
  853    case 1:
  854       break;
  855    case 0:
  856       vcp->vc_mp->m_flag |= MSPAM;
  857       break;
  858    default:
  859       rv = FAL0;
  860       goto jleave;
  861    }
  862 
  863 # ifdef HAVE_REGEX
  864    sfp = &vcp->vc_t.filter;
  865 
  866    if (sfp->f_score_grpno == 0)
  867       goto jleave;
  868 
  869    assert(sfp->f_super.cf_result != NULL);
  870    remp = rem + sfp->f_score_grpno;
  871 
  872    if (regexec(&sfp->f_score_regex, sfp->f_super.cf_result, n_NELEM(rem), rem,
  873          0) == REG_NOMATCH || (remp->rm_so | remp->rm_eo) < 0) {
  874       n_err(_("`%s': *spamfilter-rate-scanscore* "
  875          "doesn't match filter output!\n"),
  876          _spam_cmds[vcp->vc_action]);
  877       sfp->f_score_grpno = 0;
  878       goto jleave;
  879    }
  880 
  881    cp = sfp->f_super.cf_result;
  882    cp[remp->rm_eo] = '\0';
  883    cp += remp->rm_so;
  884    _spam_rate2score(vcp, cp);
  885 # endif /* HAVE_REGEX */
  886 
  887 jleave:
  888    NYD2_LEAVE;
  889    return rv;
  890 }
  891 
  892 static void
  893 _spamfilter_dtor(struct spam_vc *vcp)
  894 {
  895    struct spam_filter *sfp;
  896    NYD2_ENTER;
  897 
  898    sfp = &vcp->vc_t.filter;
  899 
  900    if (sfp->f_super.cf_result != NULL)
  901       free(sfp->f_super.cf_result);
  902 # ifdef HAVE_REGEX
  903    if (sfp->f_score_grpno > 0)
  904       regfree(&sfp->f_score_regex);
  905 # endif
  906    NYD2_LEAVE;
  907 }
  908 #endif /* HAVE_SPAM_FILTER */
  909 
  910 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_FILTER
  911 static void
  912 _spam_cf_setup(struct spam_vc *vcp, bool_t useshell)
  913 {
  914    struct str s;
  915    char const *cp;
  916    struct spam_cf *scfp;
  917    NYD2_ENTER;
  918    n_LCTA(2 < n_NELEM(scfp->cf_env), "Preallocated buffer too small");
  919 
  920    scfp = &vcp->vc_t.cf;
  921 
  922    if ((scfp->cf_useshell = useshell)) {
  923       scfp->cf_acmd = ok_vlook(SHELL);
  924       scfp->cf_a0 = "-c";
  925    }
  926 
  927    /* MAILX_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
  928     * TODO a file wherever he wants!  *Do* create a zero-size temporary file
  929     * TODO and give *that* path as MAILX_FILENAME_TEMPORARY, clean it up once
  930     * TODO the pipe returns?  Like this we *can* verify path/name issues! */
  931    cp = n_random_create_cp(n_MIN(NAME_MAX / 4, 16), NULL);
  932    scfp->cf_env[0] = str_concat_csvl(&s,
  933          n_PIPEENV_FILENAME_GENERATED, "=", cp, NULL)->s;
  934    /* v15 compat NAIL_ environments vanish! */
  935    scfp->cf_env[1] = str_concat_csvl(&s,
  936          "NAIL_FILENAME_GENERATED", "=", cp, NULL)->s;
  937    scfp->cf_env[2] = NULL;
  938    NYD2_LEAVE;
  939 }
  940 
  941 static sigjmp_buf    __spam_cf_actjmp; /* TODO someday, we won't need it */
  942 static int volatile  __spam_cf_sig; /* TODO someday, we won't need it */
  943 static void
  944 __spam_cf_onsig(int sig) /* TODO someday, we won't need it no more */
  945 {
  946    NYD_X; /* Signal handler */
  947    __spam_cf_sig = sig;
  948    siglongjmp(__spam_cf_actjmp, 1);
  949 }
  950 
  951 static bool_t
  952 _spam_cf_interact(struct spam_vc *vcp)
  953 {
  954    struct spam_cf *scfp;
  955    int p2c[2], c2p[2];
  956    sigset_t cset;
  957    char const *cp;
  958    size_t size;
  959    pid_t volatile pid;
  960    enum {
  961       _NONE    = 0,
  962       _SIGHOLD = 1<<0,
  963       _P2C_0   = 1<<1,
  964       _P2C_1   = 1<<2,
  965       _P2C     = _P2C_0 | _P2C_1,
  966       _C2P_0   = 1<<3,
  967       _C2P_1   = 1<<4,
  968       _C2P     = _C2P_0 | _C2P_1,
  969       _JUMPED  = 1<<5,
  970       _RUNNING = 1<<6,
  971       _GOODRUN = 1<<7,
  972       _ERRORS  = 1<<8
  973    } volatile state = _NONE;
  974    NYD2_ENTER;
  975 
  976    scfp = &vcp->vc_t.cf;
  977    if (scfp->cf_result != NULL) {
  978       free(scfp->cf_result);
  979       scfp->cf_result = NULL;
  980    }
  981 
  982    /* TODO Avoid that we jump away; yet necessary signal mess */
  983    /*__spam_cf_sig = 0;*/
  984    hold_sigs();
  985    state |= _SIGHOLD;
  986    scfp->cf_otstp = safe_signal(SIGTSTP, SIG_DFL);
  987    scfp->cf_ottin = safe_signal(SIGTTIN, SIG_DFL);
  988    scfp->cf_ottou = safe_signal(SIGTTOU, SIG_DFL);
  989    scfp->cf_opipe = safe_signal(SIGPIPE, SIG_IGN);
  990    scfp->cf_ohup = safe_signal(SIGHUP, &__spam_cf_onsig);
  991    scfp->cf_oint = safe_signal(SIGINT, &__spam_cf_onsig);
  992    scfp->cf_oquit = safe_signal(SIGQUIT, &__spam_cf_onsig);
  993    /* Keep sigs blocked */
  994    pid = 0; /* cc uninit */
  995 
  996    if (!pipe_cloexec(p2c)) {
  997       n_err(_("%s`%s': cannot create parent communication pipe: %s\n"),
  998          vcp->vc_esep, _spam_cmds[vcp->vc_action], n_err_to_doc(n_err_no));
  999       goto jtail;
 1000    }
 1001    state |= _P2C;
 1002 
 1003    if (!pipe_cloexec(c2p)) {
 1004       n_err(_("%s`%s': cannot create child pipe: %s\n"),
 1005          vcp->vc_esep, _spam_cmds[vcp->vc_action], n_err_to_doc(n_err_no));
 1006       goto jtail;
 1007    }
 1008    state |= _C2P;
 1009 
 1010    if (sigsetjmp(__spam_cf_actjmp, 1)) {
 1011       if (*vcp->vc_esep != '\0')
 1012          n_err(vcp->vc_esep);
 1013       state |= _JUMPED;
 1014       goto jtail;
 1015    }
 1016    rele_sigs();
 1017    state &= ~_SIGHOLD;
 1018 
 1019    /* Start our command as requested */
 1020    sigemptyset(&cset);
 1021    if ((pid = n_child_start(
 1022          (scfp->cf_acmd != NULL ? scfp->cf_acmd : scfp->cf_cmd),
 1023          &cset, p2c[0], c2p[1],
 1024          scfp->cf_a0, (scfp->cf_acmd != NULL ? scfp->cf_cmd : NULL), NULL,
 1025          scfp->cf_env)) < 0) {
 1026       state |= _ERRORS;
 1027       goto jtail;
 1028    }
 1029    state |= _RUNNING;
 1030    close(p2c[0]);
 1031    state &= ~_P2C_0;
 1032 
 1033    /* Yes, we could sendmp(SEND_MBOX), but simply passing through the MBOX
 1034     * content does the same in effect, however much more efficiently.
 1035     * XXX NOTE: this may mean we pass a message without From_ line! */
 1036    for (size = vcp->vc_mp->m_size; size > 0;) {
 1037       size_t i;
 1038 
 1039       i = fread(vcp->vc_buffer, 1, n_MIN(size, BUFFER_SIZE), vcp->vc_ifp);
 1040       if (i == 0) {
 1041          if (ferror(vcp->vc_ifp))
 1042             state |= _ERRORS;
 1043          break;
 1044       }
 1045       size -= i;
 1046       if (i != (size_t)write(p2c[1], vcp->vc_buffer, i)) {
 1047          state |= _ERRORS;
 1048          break;
 1049       }
 1050    }
 1051 
 1052 jtail:
 1053    /* TODO Quite racy -- block anything for a while? */
 1054    if (state & _SIGHOLD) {
 1055       state &= ~_SIGHOLD;
 1056       rele_sigs();
 1057    }
 1058 
 1059    if (state & _P2C_0) {
 1060       state &= ~_P2C_0;
 1061       close(p2c[0]);
 1062    }
 1063    if (state & _C2P_1) {
 1064       state &= ~_C2P_1;
 1065       close(c2p[1]);
 1066    }
 1067    /* And cause EOF for the reader */
 1068    if (state & _P2C_1) {
 1069       state &= ~_P2C_1;
 1070       close(p2c[1]);
 1071    }
 1072 
 1073    if (state & _RUNNING) {
 1074       if (!(state & _ERRORS) &&
 1075             vcp->vc_action == _SPAM_RATE && !(state & (_JUMPED | _ERRORS))) {
 1076          ssize_t i = read(c2p[0], vcp->vc_buffer, BUFFER_SIZE - 1);
 1077          if (i > 0) {
 1078             vcp->vc_buffer[i] = '\0';
 1079             if ((cp = strchr(vcp->vc_buffer, NETNL[0])) == NULL &&
 1080                   (cp = strchr(vcp->vc_buffer, NETNL[1])) == NULL) {
 1081                n_err(_("%s`%s': program generates too much output: %s\n"),
 1082                   vcp->vc_esep, _spam_cmds[vcp->vc_action],
 1083                   n_shexp_quote_cp(scfp->cf_cmd, FAL0));
 1084                state |= _ERRORS;
 1085             } else {
 1086                scfp->cf_result = sbufdup(vcp->vc_buffer,
 1087                      PTR2SIZE(cp - vcp->vc_buffer));
 1088 /* FIXME consume child output until EOF??? */
 1089             }
 1090          } else if (i != 0)
 1091             state |= _ERRORS;
 1092       }
 1093 
 1094       state &= ~_RUNNING;
 1095       n_child_wait(pid, &scfp->cf_waitstat);
 1096       if (WIFEXITED(scfp->cf_waitstat))
 1097          state |= _GOODRUN;
 1098    }
 1099 
 1100    if (state & _C2P_0) {
 1101       state &= ~_C2P_0;
 1102       close(c2p[0]);
 1103    }
 1104 
 1105    safe_signal(SIGQUIT, scfp->cf_oquit);
 1106    safe_signal(SIGINT, scfp->cf_oint);
 1107    safe_signal(SIGHUP, scfp->cf_ohup);
 1108    safe_signal(SIGPIPE, scfp->cf_opipe);
 1109    safe_signal(SIGTSTP, scfp->cf_otstp);
 1110    safe_signal(SIGTTIN, scfp->cf_ottin);
 1111    safe_signal(SIGTTOU, scfp->cf_ottou);
 1112 
 1113    NYD2_LEAVE;
 1114    if (state & _JUMPED) {
 1115       assert(vcp->vc_dtor != NULL);
 1116       (*vcp->vc_dtor)(vcp);
 1117 
 1118       sigemptyset(&cset);
 1119       sigaddset(&cset, __spam_cf_sig);
 1120       sigprocmask(SIG_UNBLOCK, &cset, NULL);
 1121       n_raise(__spam_cf_sig);
 1122    }
 1123    return !(state & (_JUMPED | _ERRORS));
 1124 }
 1125 #endif /* HAVE_SPAM_SPAMC || HAVE_SPAM_FILTER */
 1126 
 1127 #if defined HAVE_SPAM_SPAMC || defined HAVE_SPAM_SPAMD ||\
 1128    (defined HAVE_SPAM_FILTER && defined HAVE_REGEX)
 1129 static void
 1130 _spam_rate2score(struct spam_vc *vcp, char *buf){
 1131    ui32_t m, s;
 1132    enum n_idec_state ids;
 1133    NYD2_ENTER;
 1134 
 1135    /* C99 */{ /* Overcome ISO C / compiler weirdness */
 1136       char const *cp;
 1137 
 1138       cp = buf;
 1139       ids = n_idec_ui32_cp(&m, buf, 10, &cp);
 1140       if((ids & n_IDEC_STATE_EMASK) & ~n_IDEC_STATE_EBASE)
 1141          goto jleave;
 1142       buf = n_UNCONST(cp);
 1143    }
 1144 
 1145    s = 0;
 1146    if(!(ids & n_IDEC_STATE_CONSUMED)){
 1147       /* Floating-point rounding for non-mathematicians */
 1148       char c1, c2, c3;
 1149 
 1150       ++buf; /* '.' */
 1151       if((c1 = buf[0]) != '\0' && (c2 = buf[1]) != '\0' &&
 1152             (c3 = buf[2]) != '\0'){
 1153          buf[2] = '\0';
 1154          if(c3 >= '5'){
 1155             if(c2 == '9'){
 1156                if(c1 == '9'){
 1157                   ++m;
 1158                   goto jscore_ok;
 1159                }else
 1160                   buf[0] = ++c1;
 1161                c2 = '0';
 1162             }else
 1163                ++c2;
 1164             buf[1] = c2;
 1165          }
 1166       }
 1167 
 1168       ids = n_idec_ui32_cp(&s, buf, 10, NULL);
 1169       if((ids & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1170             ) != n_IDEC_STATE_CONSUMED)
 1171          goto jleave;
 1172    }
 1173 
 1174 jscore_ok:
 1175    vcp->vc_mp->m_spamscore = (m << 8) | s;
 1176 jleave:
 1177    NYD2_LEAVE;
 1178 }
 1179 #endif /* _SPAM_SPAMC || _SPAM_SPAMD || (_SPAM_FILTER && HAVE_REGEX) */
 1180 
 1181 FL int
 1182 c_spam_clear(void *v)
 1183 {
 1184    int *ip;
 1185    NYD_ENTER;
 1186 
 1187    for (ip = v; *ip != 0; ++ip)
 1188       message[(size_t)*ip - 1].m_flag &= ~(MSPAM | MSPAMUNSURE);
 1189    NYD_LEAVE;
 1190    return 0;
 1191 }
 1192 
 1193 FL int
 1194 c_spam_set(void *v)
 1195 {
 1196    int *ip;
 1197    NYD_ENTER;
 1198 
 1199    for (ip = v; *ip != 0; ++ip) {
 1200       message[(size_t)*ip - 1].m_flag &= ~(MSPAM | MSPAMUNSURE);
 1201       message[(size_t)*ip - 1].m_flag |= MSPAM;
 1202    }
 1203    NYD_LEAVE;
 1204    return 0;
 1205 }
 1206 
 1207 FL int
 1208 c_spam_forget(void *v)
 1209 {
 1210    int rv;
 1211    NYD_ENTER;
 1212 
 1213    rv = _spam_action(_SPAM_FORGET, (int*)v) ? OKAY : STOP;
 1214    NYD_LEAVE;
 1215    return rv;
 1216 }
 1217 
 1218 FL int
 1219 c_spam_ham(void *v)
 1220 {
 1221    int rv;
 1222    NYD_ENTER;
 1223 
 1224    rv = _spam_action(_SPAM_HAM, (int*)v) ? OKAY : STOP;
 1225    NYD_LEAVE;
 1226    return rv;
 1227 }
 1228 
 1229 FL int
 1230 c_spam_rate(void *v)
 1231 {
 1232    int rv;
 1233    NYD_ENTER;
 1234 
 1235    rv = _spam_action(_SPAM_RATE, (int*)v) ? OKAY : STOP;
 1236    NYD_LEAVE;
 1237    return rv;
 1238 }
 1239 
 1240 FL int
 1241 c_spam_spam(void *v)
 1242 {
 1243    int rv;
 1244    NYD_ENTER;
 1245 
 1246    rv = _spam_action(_SPAM_SPAM, (int*)v) ? OKAY : STOP;
 1247    NYD_LEAVE;
 1248    return rv;
 1249 }
 1250 #endif /* HAVE_SPAM */
 1251 
 1252 /* s-it-mode */