"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/collect.c" (25 Mar 2018, 83494 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 "collect.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.9_vs_14.9.10.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Collect input from standard input, handling ~ escapes.
    3  *@ TODO This needs a complete rewrite, with carriers, etc.
    4  *
    5  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    6  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    7  */
    8 /*
    9  * Copyright (c) 1980, 1993
   10  *      The Regents of the University of California.  All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. Neither the name of the University nor the names of its contributors
   21  *    may be used to endorse or promote products derived from this software
   22  *    without specific prior written permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   34  * SUCH DAMAGE.
   35  */
   36 #undef n_FILE
   37 #define n_FILE collect
   38 
   39 #ifndef HAVE_AMALGAMATION
   40 # include "nail.h"
   41 #endif
   42 
   43 struct a_coll_ocs_arg{
   44    sighandler_type coa_opipe;
   45    sighandler_type coa_oint;
   46    FILE *coa_stdin;  /* The descriptor (pipe(2)+Fdopen()) we read from */
   47    FILE *coa_stdout; /* The Popen()ed pipe through which we write to the hook */
   48    int coa_pipe[2];  /* ..backing .coa_stdin */
   49    si8_t *coa_senderr; /* Set to 1 on failure */
   50    char coa_cmd[n_VFIELD_SIZE(0)];
   51 };
   52 
   53 /* The following hookiness with global variables is so that on receipt of an
   54  * interrupt signal, the partial message can be salted away on *DEAD* */
   55 
   56 static sighandler_type  _coll_saveint;    /* Previous SIGINT value */
   57 static sighandler_type  _coll_savehup;    /* Previous SIGHUP value */
   58 static FILE             *_coll_fp;        /* File for saving away */
   59 static int volatile     _coll_hadintr;    /* Have seen one SIGINT so far */
   60 static sigjmp_buf       _coll_jmp;        /* To get back to work */
   61 static sigjmp_buf       _coll_abort;      /* To end collection with error */
   62 static char const *a_coll_ocs__macname;   /* *on-compose-splice* */
   63 
   64 /* Handle `~:', `~_' and some hooks; hp may be NULL */
   65 static void       _execute_command(struct header *hp, char const *linebuf,
   66                      size_t linesize);
   67 
   68 /* Return errno */
   69 static si32_t a_coll_include_file(char const *name, bool_t indent,
   70                bool_t writestat);
   71 
   72 /* Execute cmd and insert its standard output into fp, return errno */
   73 static si32_t a_coll_insert_cmd(FILE *fp, char const *cmd);
   74 
   75 /* ~p command */
   76 static void       print_collf(FILE *collf, struct header *hp);
   77 
   78 /* Write a file, ex-like if f set */
   79 static si32_t a_coll_write(char const *name, FILE *fp, int f);
   80 
   81 /* *message-inject-head* */
   82 static bool_t a_coll_message_inject_head(FILE *fp);
   83 
   84 /* Parse off the message header from fp and store relevant fields in hp,
   85  * replace _coll_fp with a shiny new version without any header */
   86 static bool_t a_coll_makeheader(FILE *fp, struct header *hp,
   87                si8_t *checkaddr_err, bool_t do_delayed_due_t);
   88 
   89 /* Edit the message being collected on fp.  On return, make the edit file the
   90  * new temp file.  Return errno */
   91 static si32_t a_coll_edit(int c, struct header *hp);
   92 
   93 /* Pipe the message through the command.  Old message is on stdin of command,
   94  * new message collected from stdout.  Shell must return 0 to accept new msg */
   95 static si32_t a_coll_pipe(char const *cmd);
   96 
   97 /* Interpolate the named messages into the current message, possibly doing
   98  * indent stuff.  The flag argument is one of the command escapes: [mMfFuU].
   99  * Return errno */
  100 static si32_t a_coll_forward(char const *ms, FILE *fp, int f);
  101 
  102 /* ~^ mode */
  103 static bool_t a_collect_plumbing(char const *ms, struct header *p);
  104 
  105 static bool_t a_collect__plumb_header(char const *cp, struct header *p,
  106                char const *cmd[4]);
  107 static bool_t a_collect__plumb_attach(char const *cp, struct header *p,
  108                char const *cmd[4]);
  109 
  110 /* On interrupt, come here to save the partial message in ~/dead.letter.
  111  * Then jump out of the collection loop */
  112 static void       _collint(int s);
  113 
  114 static void       collhup(int s);
  115 
  116 /* ~[AaIi], *message-inject-**: put value, expand \[nt] if *posix* */
  117 static bool_t a_coll_putesc(char const *s, bool_t addnl, FILE *stream);
  118 
  119 /* *on-compose-splice* driver and *on-compose-splice(-shell)?* finalizer */
  120 static int a_coll_ocs__mac(void);
  121 static void a_coll_ocs__finalize(void *vp);
  122 
  123 static void
  124 _execute_command(struct header *hp, char const *linebuf, size_t linesize){
  125    /* The problem arises if there are rfc822 message attachments and the
  126     * user uses `~:' to change the current file.  TODO Unfortunately we
  127     * TODO cannot simply keep a pointer to, or increment a reference count
  128     * TODO of the current `file' (mailbox that is) object, because the
  129     * TODO codebase doesn't deal with that at all; so, until some far
  130     * TODO later time, copy the name of the path, and warn the user if it
  131     * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
  132     * TODO copy the message attachments over to temporary files, but that
  133     * TODO would require more changes so that the user still can recognize
  134     * TODO in `~@' etc. that its a rfc822 message attachment; see below */
  135    struct n_sigman sm;
  136    struct attachment *ap;
  137    char * volatile mnbuf;
  138    NYD_ENTER;
  139 
  140    n_UNUSED(linesize);
  141    mnbuf = NULL;
  142 
  143    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT){
  144    case 0:
  145       break;
  146    default:
  147       n_pstate_err_no = n_ERR_INTR;
  148       n_pstate_ex_no = 1;
  149       goto jleave;
  150    }
  151 
  152    /* If the above todo is worked, remove or outsource to attachment.c! */
  153    if(hp != NULL && (ap = hp->h_attach) != NULL) do
  154       if(ap->a_msgno){
  155          mnbuf = sstrdup(mailname);
  156          break;
  157       }
  158    while((ap = ap->a_flink) != NULL);
  159 
  160    n_go_command(n_GO_INPUT_CTX_COMPOSE, linebuf);
  161 
  162    n_sigman_cleanup_ping(&sm);
  163 jleave:
  164    if(mnbuf != NULL){
  165       if(strcmp(mnbuf, mailname))
  166          n_err(_("Mailbox changed: it is likely that existing "
  167             "rfc822 attachments became invalid!\n"));
  168       free(mnbuf);
  169    }
  170    NYD_LEAVE;
  171    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  172 }
  173 
  174 static si32_t
  175 a_coll_include_file(char const *name, bool_t indent, bool_t writestat){
  176    FILE *fbuf;
  177    char const *heredb, *indb;
  178    size_t linesize, heredl, indl, cnt, linelen;
  179    char *linebuf;
  180    si64_t lc, cc;
  181    si32_t rv;
  182    NYD_ENTER;
  183 
  184    rv = n_ERR_NONE;
  185    lc = cc = 0;
  186    linebuf = NULL; /* TODO line pool */
  187    linesize = 0;
  188    heredb = NULL;
  189    heredl = 0;
  190 
  191    /* The -M case is special */
  192    if(name == (char*)-1){
  193       fbuf = n_stdin;
  194       name = "-";
  195    }else if(name[0] == '-' &&
  196          (name[1] == '\0' || blankspacechar(name[1]))){
  197       fbuf = n_stdin;
  198       if(name[1] == '\0'){
  199          if(!(n_psonce & n_PSO_INTERACTIVE)){
  200             n_err(_("~< -: HERE-delimiter required in non-interactive mode\n"));
  201             rv = n_ERR_INVAL;
  202             goto jleave;
  203          }
  204       }else{
  205          for(heredb = &name[2]; *heredb != '\0' && blankspacechar(*heredb);
  206                ++heredb)
  207             ;
  208          if((heredl = strlen(heredb)) == 0){
  209 jdelim_empty:
  210             n_err(_("~< - HERE-delimiter: delimiter must not be empty\n"));
  211             rv = n_ERR_INVAL;
  212             goto jleave;
  213          }
  214 
  215          if(*heredb == '\''){
  216             for(indb = ++heredb; *indb != '\0' && *indb != '\''; ++indb)
  217                ;
  218             if(*indb == '\0'){
  219                n_err(_("~< - HERE-delimiter: missing trailing quote\n"));
  220                rv = n_ERR_INVAL;
  221                goto jleave;
  222             }else if(indb[1] != '\0'){
  223                n_err(_("~< - HERE-delimiter: trailing characters after "
  224                   "quote\n"));
  225                rv = n_ERR_INVAL;
  226                goto jleave;
  227             }
  228             if((heredl = PTR2SIZE(indb - heredb)) == 0)
  229                goto jdelim_empty;
  230             heredb = savestrbuf(heredb, heredl);
  231          }
  232       }
  233       name = "-";
  234    }else if((fbuf = Fopen(name, "r")) == NULL){
  235       n_perr(name, rv = n_err_no);
  236       goto jleave;
  237    }
  238 
  239    indl = indent ? strlen(indb = ok_vlook(indentprefix)) : 0;
  240 
  241    if(fbuf != n_stdin)
  242       cnt = fsize(fbuf);
  243    while(fgetline(&linebuf, &linesize, (fbuf == n_stdin ? NULL : &cnt),
  244          &linelen, fbuf, 0) != NULL){
  245       if(heredl > 0 && heredl == linelen - 1 &&
  246             !memcmp(heredb, linebuf, heredl)){
  247          heredb = NULL;
  248          break;
  249       }
  250 
  251       if(indl > 0){
  252          if(fwrite(indb, sizeof *indb, indl, _coll_fp) != indl){
  253             rv = n_err_no;
  254             goto jleave;
  255          }
  256          cc += indl;
  257       }
  258 
  259       if(fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen){
  260          rv = n_err_no;
  261          goto jleave;
  262       }
  263       cc += linelen;
  264       ++lc;
  265    }
  266    if(fflush(_coll_fp)){
  267       rv = n_err_no;
  268       goto jleave;
  269    }
  270 
  271    if(heredb != NULL)
  272       rv = n_ERR_NOTOBACCO;
  273 jleave:
  274    if(linebuf != NULL)
  275       free(linebuf);
  276    if(fbuf != NULL){
  277       if(fbuf != n_stdin)
  278          Fclose(fbuf);
  279       else if(heredl > 0)
  280          clearerr(n_stdin);
  281    }
  282 
  283    if(writestat)
  284       fprintf(n_stdout, "%s%s %" PRId64 "/%" PRId64 "\n",
  285          n_shexp_quote_cp(name, FAL0), (rv ? " " n_ERROR : n_empty), lc, cc);
  286    NYD_LEAVE;
  287    return rv;
  288 }
  289 
  290 static si32_t
  291 a_coll_insert_cmd(FILE *fp, char const *cmd){
  292    FILE *ibuf;
  293    si64_t lc, cc;
  294    si32_t rv;
  295    NYD_ENTER;
  296 
  297    rv = n_ERR_NONE;
  298    lc = cc = 0;
  299 
  300    if((ibuf = Popen(cmd, "r", ok_vlook(SHELL), NULL, 0)) != NULL){
  301       int c;
  302 
  303       while((c = getc(ibuf)) != EOF){ /* XXX bytewise, yuck! */
  304          if(putc(c, fp) == EOF){
  305             rv = n_err_no;
  306             break;
  307          }
  308          ++cc;
  309          if(c == '\n')
  310             ++lc;
  311       }
  312       if(!feof(ibuf) || ferror(ibuf)){
  313          if(rv == n_ERR_NONE)
  314             rv = n_ERR_IO;
  315       }
  316       if(!Pclose(ibuf, TRU1)){
  317          if(rv == n_ERR_NONE)
  318             rv = n_ERR_IO;
  319       }
  320    }else
  321       n_perr(cmd, rv = n_err_no);
  322 
  323    fprintf(n_stdout, "CMD%s %" PRId64 "/%" PRId64 "\n",
  324       (rv == n_ERR_NONE ? n_empty : " " n_ERROR), lc, cc);
  325    NYD_LEAVE;
  326    return rv;
  327 }
  328 
  329 static void
  330 print_collf(FILE *cf, struct header *hp)
  331 {
  332    char *lbuf;
  333    FILE *obuf;
  334    size_t cnt, linesize, linelen;
  335    NYD_ENTER;
  336 
  337    fflush_rewind(cf);
  338    cnt = (size_t)fsize(cf);
  339 
  340    if((obuf = Ftmp(NULL, "collfp", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
  341       n_perr(_("Can't create temporary file for `~p' command"), 0);
  342       goto jleave;
  343    }
  344 
  345    hold_all_sigs();
  346 
  347    fprintf(obuf, A_("-------\nMessage contains:\n")); /* xxx SEARCH112 */
  348    puthead(TRU1, hp, obuf,
  349       (GIDENT | GTO | GSUBJECT | GCC | GBCC | GNL | GFILES | GCOMMA),
  350       SEND_TODISP, CONV_NONE, NULL, NULL);
  351 
  352    lbuf = NULL;
  353    linesize = 0;
  354    while(fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
  355       prout(lbuf, linelen, obuf);
  356    if(lbuf != NULL)
  357       free(lbuf);
  358 
  359    if(hp->h_attach != NULL){
  360       fputs(A_("-------\nAttachments:\n"), obuf);
  361       n_attachment_list_print(hp->h_attach, obuf);
  362    }
  363 
  364    rele_all_sigs();
  365 
  366    page_or_print(obuf, 0);
  367 
  368    Fclose(obuf);
  369 jleave:
  370    NYD_LEAVE;
  371 }
  372 
  373 static si32_t
  374 a_coll_write(char const *name, FILE *fp, int f)
  375 {
  376    FILE *of;
  377    int c;
  378    si64_t lc, cc;
  379    si32_t rv;
  380    NYD_ENTER;
  381 
  382    rv = n_ERR_NONE;
  383 
  384    if(f) {
  385       fprintf(n_stdout, "%s ", n_shexp_quote_cp(name, FAL0));
  386       fflush(n_stdout);
  387    }
  388 
  389    if ((of = Fopen(name, "a")) == NULL) {
  390       n_perr(name, rv = n_err_no);
  391       goto jerr;
  392    }
  393 
  394    lc = cc = 0;
  395    while ((c = getc(fp)) != EOF) {
  396       ++cc;
  397       if (c == '\n')
  398          ++lc;
  399       if (putc(c, of) == EOF) {
  400          n_perr(name, rv = n_err_no);
  401          goto jerr;
  402       }
  403    }
  404    fprintf(n_stdout, _("%" PRId64 "/%" PRId64 "\n"), lc, cc);
  405 
  406 jleave:
  407    if(of != NULL)
  408       Fclose(of);
  409    fflush(n_stdout);
  410    NYD_LEAVE;
  411    return rv;
  412 jerr:
  413    putc('-', n_stdout);
  414    putc('\n', n_stdout);
  415    goto jleave;
  416 }
  417 
  418 static bool_t
  419 a_coll_message_inject_head(FILE *fp){
  420    bool_t rv;
  421    char const *cp, *cp_obsolete;
  422    NYD2_ENTER;
  423 
  424    cp_obsolete = ok_vlook(NAIL_HEAD);
  425    if(cp_obsolete != NULL)
  426       n_OBSOLETE(_("please use *message-inject-head*, not *NAIL_HEAD*"));
  427 
  428    if(((cp = ok_vlook(message_inject_head)) != NULL ||
  429          (cp = cp_obsolete) != NULL) && !a_coll_putesc(cp, TRU1, fp))
  430       rv = FAL0;
  431    else
  432       rv = TRU1;
  433    NYD2_LEAVE;
  434    return rv;
  435 }
  436 
  437 static bool_t
  438 a_coll_makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err,
  439    bool_t do_delayed_due_t)
  440 {
  441    FILE *nf;
  442    int c;
  443    bool_t rv;
  444    NYD_ENTER;
  445 
  446    rv = FAL0;
  447 
  448    if ((nf = Ftmp(NULL, "colhead", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
  449       n_perr(_("temporary mail edit file"), 0);
  450       goto jleave;
  451    }
  452 
  453    extract_header(fp, hp, checkaddr_err);
  454    if (checkaddr_err != NULL && *checkaddr_err != 0)
  455       goto jleave;
  456 
  457    /* In template mode some things have been delayed until the template has
  458     * been read */
  459    if(do_delayed_due_t){
  460       char const *cp;
  461 
  462       if((cp = ok_vlook(on_compose_enter)) != NULL){
  463          setup_from_and_sender(hp);
  464          temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
  465             hp);
  466       }
  467 
  468       if(!a_coll_message_inject_head(nf))
  469          goto jleave;
  470    }
  471 
  472    while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
  473       putc(c, nf);
  474 
  475    if (fp != _coll_fp)
  476       Fclose(_coll_fp);
  477    Fclose(fp);
  478    _coll_fp = nf;
  479    nf = NULL;
  480 
  481    if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL)
  482       goto jleave;
  483    rv = TRU1;
  484 jleave:
  485    if(nf != NULL)
  486       Fclose(nf);
  487    NYD_LEAVE;
  488    return rv;
  489 }
  490 
  491 static si32_t
  492 a_coll_edit(int c, struct header *hp) /* TODO error(return) weird */
  493 {
  494    struct n_sigman sm;
  495    FILE *nf;
  496    sighandler_type volatile sigint;
  497    bool_t saved;
  498    si32_t volatile rv;
  499    NYD_ENTER;
  500 
  501    n_UNINIT(sigint, SIG_ERR);
  502    rv = n_ERR_NONE;
  503 
  504    if(!(saved = ok_blook(add_file_recipients)))
  505       ok_bset(add_file_recipients);
  506 
  507    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
  508    case 0:
  509       sigint = safe_signal(SIGINT, SIG_IGN);
  510       break;
  511    default:
  512       rv = n_ERR_INTR;
  513       goto jleave;
  514    }
  515 
  516    nf = run_editor(_coll_fp, (off_t)-1, c, FAL0, hp, NULL, SEND_MBOX, sigint);
  517    if (nf != NULL) {
  518       if (hp) {
  519          if(!a_coll_makeheader(nf, hp, NULL, FAL0))
  520             rv = n_ERR_INVAL;
  521       } else {
  522          fseek(nf, 0L, SEEK_END);
  523          Fclose(_coll_fp);
  524          _coll_fp = nf;
  525       }
  526    } else
  527       rv = n_ERR_CHILD;
  528 
  529    n_sigman_cleanup_ping(&sm);
  530 jleave:
  531    if(!saved)
  532       ok_bclear(add_file_recipients);
  533    safe_signal(SIGINT, sigint);
  534    NYD_LEAVE;
  535    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  536    return rv;
  537 }
  538 
  539 static si32_t
  540 a_coll_pipe(char const *cmd)
  541 {
  542    int ws;
  543    FILE *nf;
  544    sighandler_type sigint;
  545    si32_t rv;
  546    NYD_ENTER;
  547 
  548    rv = n_ERR_NONE;
  549    sigint = safe_signal(SIGINT, SIG_IGN);
  550 
  551    if ((nf = Ftmp(NULL, "colpipe", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
  552       n_perr(_("temporary mail edit file"), rv = n_err_no);
  553       goto jout;
  554    }
  555 
  556    /* stdin = current message.  stdout = new message */
  557    fflush(_coll_fp);
  558    if (n_child_run(ok_vlook(SHELL), 0, fileno(_coll_fp), fileno(nf), "-c",
  559          cmd, NULL, NULL, &ws) < 0 || WEXITSTATUS(ws) != 0) {
  560       Fclose(nf);
  561       rv = n_ERR_CHILD;
  562       goto jout;
  563    }
  564 
  565    if (fsize(nf) == 0) {
  566       n_err(_("No bytes from %s !?\n"), n_shexp_quote_cp(cmd, FAL0));
  567       Fclose(nf);
  568       rv = n_ERR_NODATA;
  569       goto jout;
  570    }
  571 
  572    /* Take new files */
  573    fseek(nf, 0L, SEEK_END);
  574    Fclose(_coll_fp);
  575    _coll_fp = nf;
  576 jout:
  577    safe_signal(SIGINT, sigint);
  578    NYD_LEAVE;
  579    return rv;
  580 }
  581 
  582 static si32_t
  583 a_coll_forward(char const *ms, FILE *fp, int f)
  584 {
  585    int *msgvec, rv = 0;
  586    struct n_ignore const *itp;
  587    char const *tabst;
  588    enum sendaction action;
  589    NYD_ENTER;
  590 
  591    msgvec = salloc((size_t)(msgCount + 1) * sizeof *msgvec);
  592    if (getmsglist(ms, msgvec, 0) < 0) {
  593       rv = n_ERR_NOENT; /* XXX not really, should be handled there! */
  594       goto jleave;
  595    }
  596    if (*msgvec == 0) {
  597       *msgvec = first(0, MMNORM);
  598       if (*msgvec == 0) {
  599          n_err(_("No appropriate messages\n"));
  600          rv = n_ERR_NOENT;
  601          goto jleave;
  602       }
  603       msgvec[1] = 0;
  604    }
  605 
  606    if (f == 'f' || f == 'F' || f == 'u')
  607       tabst = NULL;
  608    else
  609       tabst = ok_vlook(indentprefix);
  610    if (f == 'u' || f == 'U')
  611       itp = n_IGNORE_ALL;
  612    else
  613       itp = upperchar(f) ? NULL : n_IGNORE_TYPE;
  614    action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
  615 
  616    fprintf(n_stdout, _("Interpolating:"));
  617    srelax_hold();
  618    for (; *msgvec != 0; ++msgvec) {
  619       struct message *mp = message + *msgvec - 1;
  620 
  621       touch(mp);
  622       fprintf(n_stdout, " %d", *msgvec);
  623       fflush(n_stdout);
  624       if (sendmp(mp, fp, itp, tabst, action, NULL) < 0) {
  625          n_perr(_("forward: temporary mail file"), 0);
  626          rv = n_ERR_IO;
  627          break;
  628       }
  629       srelax();
  630    }
  631    srelax_rele();
  632    fprintf(n_stdout, "\n");
  633 jleave:
  634    NYD_LEAVE;
  635    return rv;
  636 }
  637 
  638 static bool_t
  639 a_collect_plumbing(char const *ms, struct header *hp){
  640    /* TODO _collect_plumbing: instead of fields the basic headers should
  641     * TODO be in an array and have IDs, like in termcap etc., so then this
  642     * TODO could be simplified as table-walks.  Also true for arg-checks! */
  643    bool_t rv;
  644    char const *cp, *cmd[4];
  645    NYD2_ENTER;
  646 
  647    /* Protcol version for *on-compose-splice** -- update manual on change! */
  648 #define a_COLL_PLUMBING_VERSION "0 0 1"
  649    cp = ms;
  650 
  651    /* C99 */{
  652       size_t i;
  653 
  654       for(i = 0; i < n_NELEM(cmd); ++i){ /* TODO trim+strlist_split(_ifs?)() */
  655          while(blankchar(*cp))
  656             ++cp;
  657          if(*cp == '\0')
  658             cmd[i] = NULL;
  659          else{
  660             if(i < n_NELEM(cmd) - 1)
  661                for(cmd[i] = cp++; *cp != '\0' && !blankchar(*cp); ++cp)
  662                   ;
  663             else{
  664                /* Last slot takes all the rest of the line, less trailing WS */
  665                for(cmd[i] = cp++; *cp != '\0'; ++cp)
  666                   ;
  667                while(blankchar(cp[-1]))
  668                   --cp;
  669             }
  670             cmd[i] = savestrbuf(cmd[i], PTR2SIZE(cp - cmd[i]));
  671          }
  672       }
  673    }
  674 
  675    if(n_UNLIKELY(cmd[0] == NULL))
  676       goto jecmd;
  677    if(is_asccaseprefix(cmd[0], "header"))
  678       rv = a_collect__plumb_header(cp, hp, cmd);
  679    else if(is_asccaseprefix(cmd[0], "attachment"))
  680       rv = a_collect__plumb_attach(cp, hp, cmd);
  681    else{
  682 jecmd:
  683       fputs("500\n", n_stdout);
  684       rv = FAL0;
  685    }
  686    fflush(n_stdout);
  687 
  688    NYD2_LEAVE;
  689    return rv;
  690 }
  691 
  692 static bool_t
  693 a_collect__plumb_header(char const *cp, struct header *hp,
  694       char const *cmd[4]){
  695    uiz_t i;
  696    struct n_header_field *hfp;
  697    struct name *np, **npp;
  698    NYD2_ENTER;
  699 
  700    if(cmd[1] == NULL)
  701       goto jdefault;
  702 
  703    if(is_asccaseprefix(cmd[1], "insert")){ /* TODO LOGIC BELONGS head.c
  704        * TODO That is: Header::factory(string) -> object (blahblah).
  705        * TODO I.e., as long as we don't have regular RFC compliant parsers
  706        * TODO which differentiate in between structured and unstructured
  707        * TODO header fields etc., a little workaround */
  708       struct name *xnp;
  709       si8_t aerr;
  710       enum expand_addr_check_mode eacm;
  711       enum gfield ntype;
  712       bool_t mult_ok;
  713 
  714       if(cmd[2] == NULL || cmd[3] == NULL)
  715          goto jecmd;
  716 
  717       /* Strip [\r\n] which would render a body invalid XXX all controls? */
  718       /* C99 */{
  719          char *xp, c;
  720 
  721          cmd[3] = xp = savestr(cmd[3]);
  722          for(; (c = *xp) != '\0'; ++xp)
  723             if(c == '\n' || c == '\r')
  724                *xp = ' ';
  725       }
  726 
  727       if(!asccasecmp(cmd[2], cp = "Subject")){
  728          if(cmd[3][0] != '\0'){
  729             if(hp->h_subject != NULL)
  730                hp->h_subject = savecatsep(hp->h_subject, ' ', cmd[3]);
  731             else
  732                hp->h_subject = n_UNCONST(cmd[3]);
  733             fprintf(n_stdout, "210 %s 1\n", cp);
  734             goto jleave;
  735          }else
  736             goto j501cp;
  737       }
  738 
  739       mult_ok = TRU1;
  740       ntype = GEXTRA | GFULL | GFULLEXTRA;
  741       eacm = EACM_STRICT;
  742 
  743       if(!asccasecmp(cmd[2], cp = "From")){
  744          npp = &hp->h_from;
  745 jins:
  746          aerr = 0;
  747          if((np = lextract(cmd[3], ntype)) == NULL)
  748             goto j501cp;
  749 
  750          if((np = checkaddrs(np, eacm, &aerr), aerr != 0)){
  751             fprintf(n_stdout, "505 %s\n", cp);
  752             goto jleave;
  753          }
  754 
  755          /* Go to the end of the list, track whether it contains any
  756           * non-deleted entries */
  757          i = 0;
  758          if((xnp = *npp) != NULL)
  759             for(;; xnp = xnp->n_flink){
  760                if(!(xnp->n_type & GDEL))
  761                   ++i;
  762                if(xnp->n_flink == NULL)
  763                   break;
  764             }
  765 
  766          if(!mult_ok && (i != 0 || np->n_flink != NULL))
  767             fprintf(n_stdout, "506 %s\n", cp);
  768          else{
  769             if(xnp == NULL)
  770                *npp = np;
  771             else
  772                xnp->n_flink = np;
  773             np->n_blink = xnp;
  774             fprintf(n_stdout, "210 %s %" PRIuZ "\n", cp, ++i);
  775          }
  776          goto jleave;
  777       }
  778       if(!asccasecmp(cmd[2], cp = "Sender")){
  779          mult_ok = FAL0;
  780          npp = &hp->h_sender;
  781          goto jins;
  782       }
  783       if(!asccasecmp(cmd[2], cp = "To")){
  784          npp = &hp->h_to;
  785          ntype = GTO | GFULL;
  786          eacm = EACM_NORMAL | EAF_NAME;
  787          goto jins;
  788       }
  789       if(!asccasecmp(cmd[2], cp = "Cc")){
  790          npp = &hp->h_cc;
  791          ntype = GCC | GFULL;
  792          eacm = EACM_NORMAL | EAF_NAME;
  793          goto jins;
  794       }
  795       if(!asccasecmp(cmd[2], cp = "Bcc")){
  796          npp = &hp->h_bcc;
  797          ntype = GBCC | GFULL;
  798          eacm = EACM_NORMAL | EAF_NAME;
  799          goto jins;
  800       }
  801       if(!asccasecmp(cmd[2], cp = "Reply-To")){
  802          npp = &hp->h_reply_to;
  803          eacm = EACM_NONAME;
  804          goto jins;
  805       }
  806       if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
  807          npp = &hp->h_mft;
  808          eacm = EACM_NONAME;
  809          goto jins;
  810       }
  811       if(!asccasecmp(cmd[2], cp = "Message-ID")){
  812          mult_ok = FAL0;
  813          npp = &hp->h_message_id;
  814          ntype = GREF;
  815          eacm = EACM_NONAME;
  816          goto jins;
  817       }
  818       if(!asccasecmp(cmd[2], cp = "References")){
  819          npp = &hp->h_ref;
  820          ntype = GREF;
  821          eacm = EACM_NONAME;
  822          goto jins;
  823       }
  824       if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
  825          npp = &hp->h_in_reply_to;
  826          ntype = GREF;
  827          eacm = EACM_NONAME;
  828          goto jins;
  829       }
  830 
  831       if((cp = n_header_is_standard(cmd[2], UIZ_MAX)) != NULL){
  832          fprintf(n_stdout, "505 %s\n", cp);
  833          goto jleave;
  834       }
  835 
  836       /* Free-form header fields */
  837       /* C99 */{
  838          size_t nl, bl;
  839          struct n_header_field **hfpp;
  840 
  841          for(cp = cmd[2]; *cp != '\0'; ++cp)
  842             if(!fieldnamechar(*cp)){
  843                cp = cmd[2];
  844                goto j501cp;
  845             }
  846 
  847          for(i = 0, hfpp = &hp->h_user_headers; *hfpp != NULL; ++i)
  848             hfpp = &(*hfpp)->hf_next;
  849 
  850          nl = strlen(cp = cmd[2]) +1;
  851          bl = strlen(cmd[3]) +1;
  852          *hfpp = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
  853                ) + nl + bl);
  854          hfp->hf_next = NULL;
  855          hfp->hf_nl = nl - 1;
  856          hfp->hf_bl = bl - 1;
  857          memcpy(&hfp->hf_dat[0], cp, nl);
  858          memcpy(&hfp->hf_dat[nl], cmd[3], bl);
  859          fprintf(n_stdout, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], ++i);
  860       }
  861       goto jleave;
  862    }
  863 
  864    if(is_asccaseprefix(cmd[1], "list")){
  865 jdefault:
  866       if(cmd[2] == NULL){
  867          fputs("210", n_stdout);
  868          if(hp->h_subject != NULL) fputs(" Subject", n_stdout);
  869          if(hp->h_from != NULL) fputs(" From", n_stdout);
  870          if(hp->h_sender != NULL) fputs(" Sender", n_stdout);
  871          if(hp->h_to != NULL) fputs(" To", n_stdout);
  872          if(hp->h_cc != NULL) fputs(" Cc", n_stdout);
  873          if(hp->h_bcc != NULL) fputs(" Bcc", n_stdout);
  874          if(hp->h_reply_to != NULL) fputs(" Reply-To", n_stdout);
  875          if(hp->h_mft != NULL) fputs(" Mail-Followup-To", n_stdout);
  876          if(hp->h_message_id != NULL) fputs(" Message-ID", n_stdout);
  877          if(hp->h_ref != NULL) fputs(" References", n_stdout);
  878          if(hp->h_in_reply_to != NULL) fputs(" In-Reply-To", n_stdout);
  879          if(hp->h_mailx_command != NULL) fputs(" Mailx-Command", n_stdout);
  880          if(hp->h_mailx_raw_to != NULL) fputs(" Mailx-Raw-To", n_stdout);
  881          if(hp->h_mailx_raw_cc != NULL) fputs(" Mailx-Raw-Cc", n_stdout);
  882          if(hp->h_mailx_raw_bcc != NULL) fputs(" Mailx-Raw-Bcc", n_stdout);
  883          if(hp->h_mailx_orig_from != NULL) fputs(" Mailx-Orig-From", n_stdout);
  884          if(hp->h_mailx_orig_to != NULL) fputs(" Mailx-Orig-To", n_stdout);
  885          if(hp->h_mailx_orig_cc != NULL) fputs(" Mailx-Orig-Cc", n_stdout);
  886          if(hp->h_mailx_orig_bcc != NULL) fputs(" Mailx-Orig-Bcc", n_stdout);
  887 
  888          /* Print only one instance of each free-form header */
  889          for(hfp = hp->h_user_headers; hfp != NULL; hfp = hfp->hf_next){
  890             struct n_header_field *hfpx;
  891 
  892             for(hfpx = hp->h_user_headers;; hfpx = hfpx->hf_next)
  893                if(hfpx == hfp){
  894                   putc(' ', n_stdout);
  895                   fputs(&hfp->hf_dat[0], n_stdout);
  896                   break;
  897                }else if(!asccasecmp(&hfpx->hf_dat[0], &hfp->hf_dat[0]))
  898                   break;
  899          }
  900          putc('\n', n_stdout);
  901          goto jleave;
  902       }
  903 
  904       if(cmd[3] != NULL)
  905          goto jecmd;
  906 
  907       if(!asccasecmp(cmd[2], cp = "Subject")){
  908          np = (hp->h_subject != NULL) ? (struct name*)-1 : NULL;
  909          goto jlist;
  910       }
  911       if(!asccasecmp(cmd[2], cp = "From")){
  912          np = hp->h_from;
  913 jlist:
  914          fprintf(n_stdout, "%s %s\n", (np == NULL ? "501" : "210"), cp);
  915          goto jleave;
  916       }
  917       if(!asccasecmp(cmd[2], cp = "Sender")){
  918          np = hp->h_sender;
  919          goto jlist;
  920       }
  921       if(!asccasecmp(cmd[2], cp = "To")){
  922          np = hp->h_to;
  923          goto jlist;
  924       }
  925       if(!asccasecmp(cmd[2], cp = "Cc")){
  926          np = hp->h_cc;
  927          goto jlist;
  928       }
  929       if(!asccasecmp(cmd[2], cp = "Bcc")){
  930          np = hp->h_bcc;
  931          goto jlist;
  932       }
  933       if(!asccasecmp(cmd[2], cp = "Reply-To")){
  934          np = hp->h_reply_to;
  935          goto jlist;
  936       }
  937       if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
  938          np = hp->h_mft;
  939          goto jlist;
  940       }
  941       if(!asccasecmp(cmd[2], cp = "Message-ID")){
  942          np = hp->h_message_id;
  943          goto jlist;
  944       }
  945       if(!asccasecmp(cmd[2], cp = "References")){
  946          np = hp->h_ref;
  947          goto jlist;
  948       }
  949       if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
  950          np = hp->h_in_reply_to;
  951          goto jlist;
  952       }
  953 
  954       if(!asccasecmp(cmd[2], cp = "Mailx-Command")){
  955          np = (hp->h_mailx_command != NULL) ? (struct name*)-1 : NULL;
  956          goto jlist;
  957       }
  958       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-To")){
  959          np = hp->h_mailx_raw_to;
  960          goto jlist;
  961       }
  962       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Cc")){
  963          np = hp->h_mailx_raw_cc;
  964          goto jlist;
  965       }
  966       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Bcc")){
  967          np = hp->h_mailx_raw_bcc;
  968          goto jlist;
  969       }
  970       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-From")){
  971          np = hp->h_mailx_orig_from;
  972          goto jlist;
  973       }
  974       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-To")){
  975          np = hp->h_mailx_orig_to;
  976          goto jlist;
  977       }
  978       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Cc")){
  979          np = hp->h_mailx_orig_cc;
  980          goto jlist;
  981       }
  982       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Bcc")){
  983          np = hp->h_mailx_orig_bcc;
  984          goto jlist;
  985       }
  986 
  987       /* Free-form header fields */
  988       for(cp = cmd[2]; *cp != '\0'; ++cp)
  989          if(!fieldnamechar(*cp)){
  990             cp = cmd[2];
  991             goto j501cp;
  992          }
  993       cp = cmd[2];
  994       for(hfp = hp->h_user_headers;; hfp = hfp->hf_next){
  995          if(hfp == NULL)
  996             goto j501cp;
  997          else if(!asccasecmp(cp, &hfp->hf_dat[0])){
  998             fprintf(n_stdout, "210 %s\n", &hfp->hf_dat[0]);
  999             break;
 1000          }
 1001       }
 1002       goto jleave;
 1003    }
 1004 
 1005    if(is_asccaseprefix(cmd[1], "remove")){
 1006       if(cmd[2] == NULL || cmd[3] != NULL)
 1007          goto jecmd;
 1008 
 1009       if(!asccasecmp(cmd[2], cp = "Subject")){
 1010          if(hp->h_subject != NULL){
 1011             hp->h_subject = NULL;
 1012             fprintf(n_stdout, "210 %s\n", cp);
 1013             goto jleave;
 1014          }else
 1015             goto j501cp;
 1016       }
 1017 
 1018       if(!asccasecmp(cmd[2], cp = "From")){
 1019          npp = &hp->h_from;
 1020 jrem:
 1021          if(*npp != NULL){
 1022             *npp = NULL;
 1023             fprintf(n_stdout, "210 %s\n", cp);
 1024             goto jleave;
 1025          }else
 1026             goto j501cp;
 1027       }
 1028       if(!asccasecmp(cmd[2], cp = "Sender")){
 1029          npp = &hp->h_sender;
 1030          goto jrem;
 1031       }
 1032       if(!asccasecmp(cmd[2], cp = "To")){
 1033          npp = &hp->h_to;
 1034          goto jrem;
 1035       }
 1036       if(!asccasecmp(cmd[2], cp = "Cc")){
 1037          npp = &hp->h_cc;
 1038          goto jrem;
 1039       }
 1040       if(!asccasecmp(cmd[2], cp = "Bcc")){
 1041          npp = &hp->h_bcc;
 1042          goto jrem;
 1043       }
 1044       if(!asccasecmp(cmd[2], cp = "Reply-To")){
 1045          npp = &hp->h_reply_to;
 1046          goto jrem;
 1047       }
 1048       if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
 1049          npp = &hp->h_mft;
 1050          goto jrem;
 1051       }
 1052       if(!asccasecmp(cmd[2], cp = "Message-ID")){
 1053          npp = &hp->h_message_id;
 1054          goto jrem;
 1055       }
 1056       if(!asccasecmp(cmd[2], cp = "References")){
 1057          npp = &hp->h_ref;
 1058          goto jrem;
 1059       }
 1060       if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
 1061          npp = &hp->h_in_reply_to;
 1062          goto jrem;
 1063       }
 1064 
 1065       if((cp = n_header_is_standard(cmd[2], UIZ_MAX)) != NULL){
 1066          fprintf(n_stdout, "505 %s\n", cp);
 1067          goto jleave;
 1068       }
 1069 
 1070       /* Free-form header fields (note j501cp may print non-normalized name) */
 1071       /* C99 */{
 1072          struct n_header_field **hfpp;
 1073          bool_t any;
 1074 
 1075          for(cp = cmd[2]; *cp != '\0'; ++cp)
 1076             if(!fieldnamechar(*cp)){
 1077                cp = cmd[2];
 1078                goto j501cp;
 1079             }
 1080          cp = cmd[2];
 1081 
 1082          for(any = FAL0, hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){
 1083             if(!asccasecmp(cp, &hfp->hf_dat[0])){
 1084                *hfpp = hfp->hf_next;
 1085                if(!any)
 1086                   fprintf(n_stdout, "210 %s\n", &hfp->hf_dat[0]);
 1087                any = TRU1;
 1088             }else
 1089                hfpp = &hfp->hf_next;
 1090          }
 1091          if(!any)
 1092             goto j501cp;
 1093       }
 1094       goto jleave;
 1095    }
 1096 
 1097    if(is_asccaseprefix(cmd[1], "remove-at")){
 1098       if(cmd[2] == NULL || cmd[3] == NULL)
 1099          goto jecmd;
 1100 
 1101       if((n_idec_uiz_cp(&i, cmd[3], 0, NULL
 1102                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1103             ) != n_IDEC_STATE_CONSUMED || i == 0){
 1104          fputs("505\n", n_stdout);
 1105          goto jleave;
 1106       }
 1107 
 1108       if(!asccasecmp(cmd[2], cp = "Subject")){
 1109          if(hp->h_subject != NULL && i == 1){
 1110             hp->h_subject = NULL;
 1111             fprintf(n_stdout, "210 %s 1\n", cp);
 1112             goto jleave;
 1113          }else
 1114             goto j501cp;
 1115       }
 1116 
 1117       if(!asccasecmp(cmd[2], cp = "From")){
 1118          npp = &hp->h_from;
 1119 jremat:
 1120          if((np = *npp) == NULL)
 1121             goto j501cp;
 1122          while(--i != 0 && np != NULL)
 1123             np = np->n_flink;
 1124          if(np == NULL)
 1125             goto j501cp;
 1126 
 1127          if(np->n_blink != NULL)
 1128             np->n_blink->n_flink = np->n_flink;
 1129          else
 1130             *npp = np->n_flink;
 1131          if(np->n_flink != NULL)
 1132             np->n_flink->n_blink = np->n_blink;
 1133 
 1134          fprintf(n_stdout, "210 %s\n", cp);
 1135          goto jleave;
 1136       }
 1137       if(!asccasecmp(cmd[2], cp = "Sender")){
 1138          npp = &hp->h_sender;
 1139          goto jremat;
 1140       }
 1141       if(!asccasecmp(cmd[2], cp = "To")){
 1142          npp = &hp->h_to;
 1143          goto jremat;
 1144       }
 1145       if(!asccasecmp(cmd[2], cp = "Cc")){
 1146          npp = &hp->h_cc;
 1147          goto jremat;
 1148       }
 1149       if(!asccasecmp(cmd[2], cp = "Bcc")){
 1150          npp = &hp->h_bcc;
 1151          goto jremat;
 1152       }
 1153       if(!asccasecmp(cmd[2], cp = "Reply-To")){
 1154          npp = &hp->h_reply_to;
 1155          goto jremat;
 1156       }
 1157       if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
 1158          npp = &hp->h_mft;
 1159          goto jremat;
 1160       }
 1161       if(!asccasecmp(cmd[2], cp = "Message-ID")){
 1162          npp = &hp->h_message_id;
 1163          goto jremat;
 1164       }
 1165       if(!asccasecmp(cmd[2], cp = "References")){
 1166          npp = &hp->h_ref;
 1167          goto jremat;
 1168       }
 1169       if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
 1170          npp = &hp->h_in_reply_to;
 1171          goto jremat;
 1172       }
 1173 
 1174       if((cp = n_header_is_standard(cmd[2], UIZ_MAX)) != NULL){
 1175          fprintf(n_stdout, "505 %s\n", cp);
 1176          goto jleave;
 1177       }
 1178 
 1179       /* Free-form header fields */
 1180       /* C99 */{
 1181          struct n_header_field **hfpp;
 1182 
 1183          for(cp = cmd[2]; *cp != '\0'; ++cp)
 1184             if(!fieldnamechar(*cp)){
 1185                cp = cmd[2];
 1186                goto j501cp;
 1187             }
 1188          cp = cmd[2];
 1189 
 1190          for(hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){
 1191             if(--i == 0){
 1192                *hfpp = hfp->hf_next;
 1193                fprintf(n_stdout, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], i);
 1194                break;
 1195             }else
 1196                hfpp = &hfp->hf_next;
 1197          }
 1198          if(hfp == NULL)
 1199             goto j501cp;
 1200       }
 1201       goto jleave;
 1202    }
 1203 
 1204    if(is_asccaseprefix(cmd[1], "show")){
 1205       if(cmd[2] == NULL || cmd[3] != NULL)
 1206          goto jecmd;
 1207 
 1208       if(!asccasecmp(cmd[2], cp = "Subject")){
 1209          if(hp->h_subject == NULL)
 1210             goto j501cp;
 1211          fprintf(n_stdout, "212 %s\n%s\n\n", cp, hp->h_subject);
 1212          goto jleave;
 1213       }
 1214 
 1215       if(!asccasecmp(cmd[2], cp = "From")){
 1216          np = hp->h_from;
 1217 jshow:
 1218          if(np != NULL){
 1219             fprintf(n_stdout, "211 %s\n", cp);
 1220             do if(!(np->n_type & GDEL))
 1221                fprintf(n_stdout, "%s %s\n", np->n_name, np->n_fullname);
 1222             while((np = np->n_flink) != NULL);
 1223             putc('\n', n_stdout);
 1224             goto jleave;
 1225          }else
 1226             goto j501cp;
 1227       }
 1228       if(!asccasecmp(cmd[2], cp = "Sender")){
 1229          np = hp->h_sender;
 1230          goto jshow;
 1231       }
 1232       if(!asccasecmp(cmd[2], cp = "To")){
 1233          np = hp->h_to;
 1234          goto jshow;
 1235       }
 1236       if(!asccasecmp(cmd[2], cp = "Cc")){
 1237          np = hp->h_cc;
 1238          goto jshow;
 1239       }
 1240       if(!asccasecmp(cmd[2], cp = "Bcc")){
 1241          np = hp->h_bcc;
 1242          goto jshow;
 1243       }
 1244       if(!asccasecmp(cmd[2], cp = "Reply-To")){
 1245          np = hp->h_reply_to;
 1246          goto jshow;
 1247       }
 1248       if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
 1249          np = hp->h_mft;
 1250          goto jshow;
 1251       }
 1252       if(!asccasecmp(cmd[2], cp = "Message-ID")){
 1253          np = hp->h_message_id;
 1254          goto jshow;
 1255       }
 1256       if(!asccasecmp(cmd[2], cp = "References")){
 1257          np = hp->h_ref;
 1258          goto jshow;
 1259       }
 1260       if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
 1261          np = hp->h_in_reply_to;
 1262          goto jshow;
 1263       }
 1264 
 1265       if(!asccasecmp(cmd[2], cp = "Mailx-Command")){
 1266          if(hp->h_mailx_command == NULL)
 1267             goto j501cp;
 1268          fprintf(n_stdout, "212 %s\n%s\n\n", cp, hp->h_mailx_command);
 1269          goto jleave;
 1270       }
 1271       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-To")){
 1272          np = hp->h_mailx_raw_to;
 1273          goto jshow;
 1274       }
 1275       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Cc")){
 1276          np = hp->h_mailx_raw_cc;
 1277          goto jshow;
 1278       }
 1279       if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Bcc")){
 1280          np = hp->h_mailx_raw_bcc;
 1281          goto jshow;
 1282       }
 1283       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-From")){
 1284          np = hp->h_mailx_orig_from;
 1285          goto jshow;
 1286       }
 1287       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-To")){
 1288          np = hp->h_mailx_orig_to;
 1289          goto jshow;
 1290       }
 1291       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Cc")){
 1292          np = hp->h_mailx_orig_cc;
 1293          goto jshow;
 1294       }
 1295       if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Bcc")){
 1296          np = hp->h_mailx_orig_bcc;
 1297          goto jshow;
 1298       }
 1299 
 1300       /* Free-form header fields */
 1301       /* C99 */{
 1302          bool_t any;
 1303 
 1304          for(cp = cmd[2]; *cp != '\0'; ++cp)
 1305             if(!fieldnamechar(*cp)){
 1306                cp = cmd[2];
 1307                goto j501cp;
 1308             }
 1309          cp = cmd[2];
 1310 
 1311          for(any = FAL0, hfp = hp->h_user_headers; hfp != NULL;
 1312                hfp = hfp->hf_next){
 1313             if(!asccasecmp(cp, &hfp->hf_dat[0])){
 1314                if(!any)
 1315                   fprintf(n_stdout, "212 %s\n", &hfp->hf_dat[0]);
 1316                any = TRU1;
 1317                fprintf(n_stdout, "%s\n", &hfp->hf_dat[hfp->hf_nl +1]);
 1318             }
 1319          }
 1320          if(any)
 1321             putc('\n', n_stdout);
 1322          else
 1323             goto j501cp;
 1324       }
 1325       goto jleave;
 1326    }
 1327 
 1328 jecmd:
 1329    fputs("500\n", n_stdout);
 1330    cp = NULL;
 1331 jleave:
 1332    NYD2_LEAVE;
 1333    return (cp != NULL);
 1334 
 1335 j501cp:
 1336    fputs("501 ", n_stdout);
 1337    fputs(cp, n_stdout);
 1338    putc('\n', n_stdout);
 1339    goto jleave;
 1340 }
 1341 
 1342 static bool_t
 1343 a_collect__plumb_attach(char const *cp, struct header *hp,
 1344       char const *cmd[4]){
 1345    bool_t status;
 1346    struct attachment *ap;
 1347    NYD2_ENTER;
 1348 
 1349    if(cmd[1] == NULL)
 1350       goto jdefault;
 1351 
 1352    if(is_asccaseprefix(cmd[1], "attribute")){
 1353       if(cmd[2] == NULL || cmd[3] != NULL)
 1354          goto jecmd;
 1355 
 1356       if((ap = n_attachment_find(hp->h_attach, cmd[2], NULL)) != NULL){
 1357 jatt_att:
 1358          fprintf(n_stdout, "212 %s\n", cmd[2]);
 1359          if(ap->a_msgno > 0)
 1360             fprintf(n_stdout, "message-number %d\n\n", ap->a_msgno);
 1361          else{
 1362             fprintf(n_stdout,
 1363                "creation-name %s\nopen-path %s\nfilename %s\n",
 1364                ap->a_path_user, ap->a_path, ap->a_name);
 1365             if(ap->a_content_description != NULL)
 1366                fprintf(n_stdout, "content-description %s\n",
 1367                   ap->a_content_description);
 1368             if(ap->a_content_id != NULL)
 1369                fprintf(n_stdout, "content-id %s\n",
 1370                   ap->a_content_id->n_name);
 1371             if(ap->a_content_type != NULL)
 1372                fprintf(n_stdout, "content-type %s\n", ap->a_content_type);
 1373             if(ap->a_content_disposition != NULL)
 1374                fprintf(n_stdout, "content-disposition %s\n",
 1375                   ap->a_content_disposition);
 1376             putc('\n', n_stdout);
 1377          }
 1378       }else
 1379          fputs("501\n", n_stdout);
 1380       goto jleave;
 1381    }
 1382 
 1383    if(is_asccaseprefix(cmd[1], "attribute-at")){
 1384       uiz_t i;
 1385 
 1386       if(cmd[2] == NULL || cmd[3] != NULL)
 1387          goto jecmd;
 1388 
 1389       if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
 1390                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1391             ) != n_IDEC_STATE_CONSUMED || i == 0)
 1392          fputs("505\n", n_stdout);
 1393       else{
 1394          for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
 1395             ;
 1396          if(ap != NULL)
 1397             goto jatt_att;
 1398          else
 1399             fputs("501\n", n_stdout);
 1400       }
 1401       goto jleave;
 1402    }
 1403 
 1404    if(is_asccaseprefix(cmd[1], "attribute-set")){
 1405       if(cmd[2] == NULL || cmd[3] == NULL)
 1406          goto jecmd;
 1407 
 1408       if((ap = n_attachment_find(hp->h_attach, cmd[2], NULL)) != NULL){
 1409 jatt_attset:
 1410          if(ap->a_msgno > 0)
 1411             fputs("505\n", n_stdout);
 1412          else{
 1413             char c, *keyw;
 1414 
 1415             cp = cmd[3];
 1416             while((c = *cp) != '\0' && !blankchar(c))
 1417                ++cp;
 1418             keyw = savestrbuf(cmd[3], PTR2SIZE(cp - cmd[3]));
 1419             if(c != '\0'){
 1420                for(; (c = *++cp) != '\0' && blankchar(c);)
 1421                   ;
 1422                if(c != '\0'){
 1423                   char *xp;
 1424 
 1425                   /* Strip [\r\n] which would render a parameter invalid XXX
 1426                    * XXX all controls? */
 1427                   cp = xp = savestr(cp);
 1428                   for(; (c = *xp) != '\0'; ++xp)
 1429                      if(c == '\n' || c == '\r')
 1430                         *xp = ' ';
 1431                   c = *cp;
 1432                }
 1433             }
 1434 
 1435             if(!asccasecmp(keyw, "filename"))
 1436                ap->a_name = (c == '\0') ? ap->a_path_bname : cp;
 1437             else if(!asccasecmp(keyw, "content-description"))
 1438                ap->a_content_description = (c == '\0') ? NULL : cp;
 1439             else if(!asccasecmp(keyw, "content-id")){
 1440                ap->a_content_id = NULL;
 1441 
 1442                if(c != '\0'){
 1443                   struct name *np;
 1444 
 1445                   np = checkaddrs(lextract(cp, GREF),
 1446                         /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG |
 1447                         EACM_NONAME, NULL);
 1448                   if(np != NULL && np->n_flink == NULL)
 1449                      ap->a_content_id = np;
 1450                   else
 1451                      cp = NULL;
 1452                }
 1453             }else if(!asccasecmp(keyw, "content-type"))
 1454                ap->a_content_type = (c == '\0') ? NULL : cp;
 1455             else if(!asccasecmp(keyw, "content-disposition"))
 1456                ap->a_content_disposition = (c == '\0') ? NULL : cp;
 1457             else
 1458                cp = NULL;
 1459 
 1460             if(cp != NULL){
 1461                size_t i;
 1462 
 1463                for(i = 0; ap != NULL; ++i, ap = ap->a_blink)
 1464                   ;
 1465                fprintf(n_stdout, "210 %" PRIuZ "\n", i);
 1466             }else
 1467                fputs("505\n", n_stdout);
 1468          }
 1469       }else
 1470          fputs("501\n", n_stdout);
 1471       goto jleave;
 1472    }
 1473 
 1474    if(is_asccaseprefix(cmd[1], "attribute-set-at")){
 1475       uiz_t i;
 1476 
 1477       if(cmd[2] == NULL || cmd[3] == NULL)
 1478          goto jecmd;
 1479 
 1480       if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
 1481                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1482             ) != n_IDEC_STATE_CONSUMED || i == 0)
 1483          fputs("505\n", n_stdout);
 1484       else{
 1485          for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
 1486             ;
 1487          if(ap != NULL)
 1488             goto jatt_attset;
 1489          else
 1490             fputs("501\n", n_stdout);
 1491       }
 1492       goto jleave;
 1493    }
 1494 
 1495    if(is_asccaseprefix(cmd[1], "insert")){
 1496       enum n_attach_error aerr;
 1497 
 1498       if(cmd[2] == NULL || cmd[3] != NULL)
 1499          goto jecmd;
 1500 
 1501       hp->h_attach = n_attachment_append(hp->h_attach, cmd[2], &aerr, &ap);
 1502       switch(aerr){
 1503       case n_ATTACH_ERR_FILE_OPEN: cp = "505\n"; goto jatt_ins;
 1504       case n_ATTACH_ERR_ICONV_FAILED: cp = "506\n"; goto jatt_ins;
 1505       case n_ATTACH_ERR_ICONV_NAVAIL:
 1506       case n_ATTACH_ERR_OTHER:
 1507       default:
 1508          cp = "501\n";
 1509 jatt_ins:
 1510          fputs(cp, n_stdout);
 1511          break;
 1512       case n_ATTACH_ERR_NONE:{
 1513          size_t i;
 1514 
 1515          for(i = 0; ap != NULL; ++i, ap = ap->a_blink)
 1516             ;
 1517          fprintf(n_stdout, "210 %" PRIuZ "\n", i);
 1518          }break;
 1519       }
 1520       goto jleave;
 1521    }
 1522 
 1523    if(is_asccaseprefix(cmd[1], "list")){
 1524 jdefault:
 1525       if(cmd[2] != NULL)
 1526          goto jecmd;
 1527 
 1528       if((ap = hp->h_attach) != NULL){
 1529          fputs("212\n", n_stdout);
 1530          do
 1531             fprintf(n_stdout, "%s\n", ap->a_path_user);
 1532          while((ap = ap->a_flink) != NULL);
 1533          putc('\n', n_stdout);
 1534       }else
 1535          fputs("501\n", n_stdout);
 1536       goto jleave;
 1537    }
 1538 
 1539    if(is_asccaseprefix(cmd[1], "remove")){
 1540       if(cmd[2] == NULL || cmd[3] != NULL)
 1541          goto jecmd;
 1542 
 1543       if((ap = n_attachment_find(hp->h_attach, cmd[2], &status)) != NULL){
 1544          if(status == TRUM1)
 1545             fputs("506\n", n_stdout);
 1546          else{
 1547             hp->h_attach = n_attachment_remove(hp->h_attach, ap);
 1548             fprintf(n_stdout, "210 %s\n", cmd[2]);
 1549          }
 1550       }else
 1551          fputs("501\n", n_stdout);
 1552       goto jleave;
 1553    }
 1554 
 1555    if(is_asccaseprefix(cmd[1], "remove-at")){
 1556       uiz_t i;
 1557 
 1558       if(cmd[2] == NULL || cmd[3] != NULL)
 1559          goto jecmd;
 1560 
 1561       if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
 1562                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1563             ) != n_IDEC_STATE_CONSUMED || i == 0)
 1564          fputs("505\n", n_stdout);
 1565       else{
 1566          for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
 1567             ;
 1568          if(ap != NULL){
 1569             hp->h_attach = n_attachment_remove(hp->h_attach, ap);
 1570             fprintf(n_stdout, "210 %s\n", cmd[2]);
 1571          }else
 1572             fputs("501\n", n_stdout);
 1573       }
 1574       goto jleave;
 1575    }
 1576 
 1577 jecmd:
 1578    fputs("500\n", n_stdout);
 1579    cp = NULL;
 1580 jleave:
 1581    NYD2_LEAVE;
 1582    return (cp != NULL);
 1583 }
 1584 
 1585 static void
 1586 _collint(int s)
 1587 {
 1588    NYD_X; /* Signal handler */
 1589 
 1590    /* the control flow is subtle, because we can be called from ~q */
 1591    if (_coll_hadintr == 0) {
 1592       if (ok_blook(ignore)) {
 1593          fputs("@\n", n_stdout);
 1594          fflush(n_stdout);
 1595          clearerr(n_stdin);
 1596       } else
 1597          _coll_hadintr = 1;
 1598       siglongjmp(_coll_jmp, 1);
 1599    }
 1600    n_exit_status |= n_EXIT_SEND_ERROR;
 1601    if (s != 0)
 1602       savedeadletter(_coll_fp, TRU1);
 1603    /* Aborting message, no need to fflush() .. */
 1604    siglongjmp(_coll_abort, 1);
 1605 }
 1606 
 1607 static void
 1608 collhup(int s)
 1609 {
 1610    NYD_X; /* Signal handler */
 1611    n_UNUSED(s);
 1612 
 1613    savedeadletter(_coll_fp, TRU1);
 1614    /* Let's pretend nobody else wants to clean up, a true statement at
 1615     * this time */
 1616    exit(n_EXIT_ERR);
 1617 }
 1618 
 1619 static bool_t
 1620 a_coll_putesc(char const *s, bool_t addnl, FILE *stream){
 1621    char c1, c2;
 1622    bool_t isposix;
 1623    NYD2_ENTER;
 1624 
 1625    isposix = ok_blook(posix);
 1626 
 1627    while((c1 = *s++) != '\0'){
 1628       if(c1 == '\\' && ((c2 = *s) == 't' || c2 == 'n')){
 1629          if(!isposix){
 1630             isposix = TRU1; /* TODO v15 OBSOLETE! */
 1631             n_err(_("Compose mode warning: expanding \\t or \\n in variable "
 1632                   "without *posix*!"
 1633                "\n  Support remains only for ~A,~a,~I,~i in *posix* mode!\n"));
 1634          }
 1635          ++s;
 1636          c1 = (c2 == 't') ? '\t' : '\n';
 1637       }
 1638 
 1639       if(putc(c1, stream) == EOF)
 1640          goto jleave;
 1641    }
 1642 
 1643    if(addnl && putc('\n', stream) == EOF)
 1644       goto jleave;
 1645 
 1646 jleave:
 1647    NYD2_LEAVE;
 1648    return (c1 == '\0');
 1649 }
 1650 
 1651 static int
 1652 a_coll_ocs__mac(void){
 1653    /* Executes in a fork(2)ed child  TODO if remains, global MASKs for those! */
 1654    setvbuf(n_stdin, NULL, _IOLBF, 0);
 1655    setvbuf(n_stdout, NULL, _IOLBF, 0);
 1656    n_psonce &= ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT);
 1657    n_pstate |= n_PS_COMPOSE_FORKHOOK;
 1658    n_readctl_overlay = NULL; /* TODO we need OnForkEvent! See c_readctl() */
 1659    if(n_poption & n_PO_D_VV){
 1660       char buf[128];
 1661 
 1662       snprintf(buf, sizeof buf, "[%d]%s", getpid(), ok_vlook(log_prefix));
 1663       ok_vset(log_prefix, buf);
 1664    }
 1665    /* TODO If that uses `!' it will effectively SIG_IGN SIGINT, ...and such */
 1666    temporary_compose_mode_hook_call(a_coll_ocs__macname, NULL, NULL);
 1667    return 0;
 1668 }
 1669 
 1670 static void
 1671 a_coll_ocs__finalize(void *vp){
 1672    /* Note we use this for destruction upon setup errors, thus */
 1673    sighandler_type opipe;
 1674    sighandler_type oint;
 1675    struct a_coll_ocs_arg **coapp, *coap;
 1676    NYD2_ENTER;
 1677 
 1678    temporary_compose_mode_hook_call((char*)-1, NULL, NULL);
 1679 
 1680    coap = *(coapp = vp);
 1681    *coapp = (struct a_coll_ocs_arg*)-1;
 1682 
 1683    if(coap->coa_stdin != NULL)
 1684       Fclose(coap->coa_stdin);
 1685    else if(coap->coa_pipe[0] != -1)
 1686       close(coap->coa_pipe[0]);
 1687 
 1688    if(coap->coa_stdout != NULL && !Pclose(coap->coa_stdout, TRU1))
 1689       *coap->coa_senderr = 111;
 1690    if(coap->coa_pipe[1] != -1)
 1691       close(coap->coa_pipe[1]);
 1692 
 1693    opipe = coap->coa_opipe;
 1694    oint = coap->coa_oint;
 1695 
 1696    n_lofi_free(coap);
 1697 
 1698    hold_all_sigs();
 1699    safe_signal(SIGPIPE, opipe);
 1700    safe_signal(SIGINT, oint);
 1701    rele_all_sigs();
 1702    NYD2_LEAVE;
 1703 }
 1704 
 1705 FL void
 1706 n_temporary_compose_hook_varset(void *arg){ /* TODO v15: drop */
 1707    struct header *hp;
 1708    char const *val;
 1709    NYD2_ENTER;
 1710 
 1711    hp = arg;
 1712 
 1713    if((val = hp->h_subject) == NULL)
 1714       val = n_empty;
 1715    ok_vset(mailx_subject, val);
 1716    if((val = detract(hp->h_from, GNAMEONLY)) == NULL)
 1717       val = n_empty;
 1718    ok_vset(mailx_from, val);
 1719    if((val = detract(hp->h_sender, GNAMEONLY)) == NULL)
 1720       val = n_empty;
 1721    ok_vset(mailx_sender, val);
 1722    if((val = detract(hp->h_to, GNAMEONLY)) == NULL)
 1723       val = n_empty;
 1724    ok_vset(mailx_to, val);
 1725    if((val = detract(hp->h_cc, GNAMEONLY)) == NULL)
 1726       val = n_empty;
 1727    ok_vset(mailx_cc, val);
 1728    if((val = detract(hp->h_bcc, GNAMEONLY)) == NULL)
 1729       val = n_empty;
 1730    ok_vset(mailx_bcc, val);
 1731 
 1732    if((val = hp->h_mailx_command) == NULL)
 1733       val = n_empty;
 1734    ok_vset(mailx_command, val);
 1735 
 1736    if((val = detract(hp->h_mailx_raw_to, GNAMEONLY)) == NULL)
 1737       val = n_empty;
 1738    ok_vset(mailx_raw_to, val);
 1739    if((val = detract(hp->h_mailx_raw_cc, GNAMEONLY)) == NULL)
 1740       val = n_empty;
 1741    ok_vset(mailx_raw_cc, val);
 1742    if((val = detract(hp->h_mailx_raw_bcc, GNAMEONLY)) == NULL)
 1743       val = n_empty;
 1744    ok_vset(mailx_raw_bcc, val);
 1745 
 1746    if((val = detract(hp->h_mailx_orig_from, GNAMEONLY)) == NULL)
 1747       val = n_empty;
 1748    ok_vset(mailx_orig_from, val);
 1749    if((val = detract(hp->h_mailx_orig_to, GNAMEONLY)) == NULL)
 1750       val = n_empty;
 1751    ok_vset(mailx_orig_to, val);
 1752    if((val = detract(hp->h_mailx_orig_cc, GNAMEONLY)) == NULL)
 1753       val = n_empty;
 1754    ok_vset(mailx_orig_cc, val);
 1755    if((val = detract(hp->h_mailx_orig_bcc, GNAMEONLY)) == NULL)
 1756       val = n_empty;
 1757    ok_vset(mailx_orig_bcc, val);
 1758    NYD2_LEAVE;
 1759 }
 1760 
 1761 FL FILE *
 1762 collect(struct header *hp, int printheaders, struct message *mp,
 1763    char const *quotefile, int doprefix, si8_t *checkaddr_err)
 1764 {
 1765    struct n_string s, * volatile sp;
 1766    struct n_ignore const *quoteitp;
 1767    struct a_coll_ocs_arg *coap;
 1768    int c;
 1769    int volatile t, eofcnt, getfields;
 1770    char volatile escape;
 1771    enum{
 1772       a_NONE,
 1773       a_ERREXIT = 1u<<0,
 1774       a_IGNERR = 1u<<1,
 1775       a_COAP_NOSIGTERM = 1u<<8
 1776 #define a_HARDERR() ((flags & (a_ERREXIT | a_IGNERR)) == a_ERREXIT)
 1777    } volatile flags;
 1778    char *linebuf;
 1779    char const *cp, *cp_base, * volatile coapm, * volatile ifs_saved;
 1780    size_t i, linesize; /* TODO line pool */
 1781    long cnt;
 1782    enum sendaction action;
 1783    sigset_t oset, nset;
 1784    FILE * volatile sigfp;
 1785    NYD_ENTER;
 1786 
 1787    _coll_fp = NULL;
 1788    sigfp = NULL;
 1789    linesize = 0;
 1790    linebuf = NULL;
 1791    flags = a_NONE;
 1792    eofcnt = 0;
 1793    ifs_saved = coapm = NULL;
 1794    coap = NULL;
 1795    sp = NULL;
 1796 
 1797    /* Start catching signals from here, but we're still die on interrupts
 1798     * until we're in the main loop */
 1799    sigfillset(&nset);
 1800    sigprocmask(SIG_BLOCK, &nset, &oset);
 1801    if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
 1802       safe_signal(SIGINT, &_collint);
 1803    if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
 1804       safe_signal(SIGHUP, collhup);
 1805    if (sigsetjmp(_coll_abort, 1))
 1806       goto jerr;
 1807    if (sigsetjmp(_coll_jmp, 1))
 1808       goto jerr;
 1809    n_pstate |= n_PS_COMPOSE_MODE;
 1810    sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
 1811 
 1812    if ((_coll_fp = Ftmp(NULL, "collect", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
 1813          NULL) {
 1814       n_perr(_("collect: temporary mail file"), 0);
 1815       goto jerr;
 1816    }
 1817 
 1818    /* If we are going to prompt for a subject, refrain from printing a newline
 1819     * after the headers (since some people mind) */
 1820    getfields = 0;
 1821    if(!(n_poption & n_PO_t_FLAG)){
 1822       t = GTO | GSUBJECT | GCC | GNL;
 1823       if(ok_blook(fullnames))
 1824          t |= GCOMMA;
 1825 
 1826       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
 1827          if(hp->h_subject == NULL && ok_blook(asksub)/* *ask* auto warped! */)
 1828             t &= ~GNL, getfields |= GSUBJECT;
 1829 
 1830          if(hp->h_to == NULL)
 1831             t &= ~GNL, getfields |= GTO;
 1832 
 1833          if(!ok_blook(bsdcompat) && !ok_blook(askatend)){
 1834             if(ok_blook(askbcc))
 1835                t &= ~GNL, getfields |= GBCC;
 1836             if(ok_blook(askcc))
 1837                t &= ~GNL, getfields |= GCC;
 1838          }
 1839       }
 1840    }else{
 1841       n_UNINIT(t, 0);
 1842    }
 1843 
 1844    _coll_hadintr = 0;
 1845 
 1846    if (!sigsetjmp(_coll_jmp, 1)) {
 1847       /* Ask for some headers first, as necessary */
 1848       if (getfields)
 1849          grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, getfields, 1);
 1850 
 1851       /* Execute compose-enter; delayed for -t mode */
 1852       if(!(n_poption & n_PO_t_FLAG) &&
 1853             (cp = ok_vlook(on_compose_enter)) != NULL){
 1854          setup_from_and_sender(hp);
 1855          temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
 1856             hp);
 1857       }
 1858 
 1859       /* TODO Mm: nope since it may require turning this into a multipart one */
 1860       if(!(n_poption & (n_PO_Mm_FLAG | n_PO_t_FLAG))){
 1861          if(!a_coll_message_inject_head(_coll_fp))
 1862             goto jerr;
 1863 
 1864          /* Quote an original message */
 1865          if (mp != NULL && (doprefix || (cp = ok_vlook(quote)) != NULL)) {
 1866             quoteitp = n_IGNORE_ALL;
 1867             action = SEND_QUOTE;
 1868             if (doprefix) {
 1869                char const *cp_v15compat;
 1870 
 1871                quoteitp = n_IGNORE_FWD;
 1872 
 1873                if((cp_v15compat = ok_vlook(fwdheading)) != NULL)
 1874                   n_OBSOLETE(_("please use *forward-inject-head*, "
 1875                      "not *fwdheading*"));
 1876                cp = ok_vlook(forward_inject_head);
 1877                if(cp == NULL && (cp = cp_v15compat) == NULL)
 1878                   cp = "-------- Original Message --------";
 1879                if (*cp != '\0' && fprintf(_coll_fp, "%s\n", cp) < 0)
 1880                   goto jerr;
 1881             } else if (!strcmp(cp, "noheading")) {
 1882                /*EMPTY*/;
 1883             } else if (!strcmp(cp, "headers")) {
 1884                quoteitp = n_IGNORE_TYPE;
 1885             } else if (!strcmp(cp, "allheaders")) {
 1886                quoteitp = NULL;
 1887                action = SEND_QUOTE_ALL;
 1888             } else {
 1889                cp = hfield1("from", mp);
 1890                if (cp != NULL && (cnt = (long)strlen(cp)) > 0) {
 1891                   if (xmime_write(cp, cnt, _coll_fp, CONV_FROMHDR, TD_NONE,
 1892                         NULL, NULL) < 0)
 1893                      goto jerr;
 1894                   if (fprintf(_coll_fp, _(" wrote:\n\n")) < 0)
 1895                      goto jerr;
 1896                }
 1897             }
 1898             if (fflush(_coll_fp))
 1899                goto jerr;
 1900             if (sendmp(mp, _coll_fp, quoteitp,
 1901                   (doprefix ? NULL : ok_vlook(indentprefix)),
 1902                   action, NULL) < 0)
 1903                goto jerr;
 1904          }
 1905       }
 1906 
 1907       if (quotefile != NULL) {
 1908          if((n_pstate_err_no = a_coll_include_file(quotefile, FAL0, FAL0)
 1909                ) != n_ERR_NONE)
 1910             goto jerr;
 1911       }
 1912 
 1913       if(n_psonce & n_PSO_INTERACTIVE){
 1914          if(!(n_pstate & n_PS_SOURCING)){
 1915             sp = n_string_creat_auto(&s);
 1916             sp = n_string_reserve(sp, 80);
 1917          }
 1918 
 1919          if(!(n_poption & n_PO_Mm_FLAG) && !(n_pstate & n_PS_ROBOT)){
 1920             /* Print what we have sofar also on the terminal (if useful) */
 1921             if((cp = ok_vlook(editalong)) == NULL){
 1922                if (printheaders)
 1923                   puthead(TRU1, hp, n_stdout, t,
 1924                      SEND_TODISP, CONV_NONE, NULL, NULL);
 1925 
 1926                rewind(_coll_fp);
 1927                while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
 1928                   putc(c, n_stdout);
 1929                if (fseek(_coll_fp, 0, SEEK_END))
 1930                   goto jerr;
 1931             }else{
 1932                rewind(_coll_fp);
 1933                if(a_coll_edit(((*cp == 'v') ? 'v' : 'e'), hp) != n_ERR_NONE)
 1934                   goto jerr;
 1935                /* Print msg mandated by the Mail Reference Manual */
 1936 jcont:
 1937                if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT))
 1938                   fputs(_("(continue)\n"), n_stdout);
 1939             }
 1940             fflush(n_stdout);
 1941          }
 1942       }
 1943    } else {
 1944       /* Come here for printing the after-signal message.  Duplicate messages
 1945        * won't be printed because the write is aborted if we get a SIGTTOU */
 1946       if(_coll_hadintr && (n_psonce & n_PSO_INTERACTIVE) &&
 1947             !(n_pstate & n_PS_ROBOT))
 1948          n_err(_("\n(Interrupt -- one more to kill letter)\n"));
 1949    }
 1950 
 1951    /* If not under shell hook control */
 1952    if(coap == NULL){
 1953       /* We're done with -M or -m TODO because: we are too stupid yet, above */
 1954       if(n_poption & n_PO_Mm_FLAG)
 1955          goto jout;
 1956       /* No command escapes, interrupts not expected.  Simply copy STDIN */
 1957       if(!(n_psonce & n_PSO_INTERACTIVE) &&
 1958             !(n_poption & (n_PO_t_FLAG | n_PO_TILDE_FLAG))){
 1959          linebuf = srealloc(linebuf, linesize = LINESIZE);
 1960          while ((i = fread(linebuf, sizeof *linebuf, linesize, n_stdin)) > 0) {
 1961             if (i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
 1962                goto jerr;
 1963          }
 1964          goto jout;
 1965       }
 1966    }
 1967 
 1968    /* The interactive collect loop */
 1969    if(coap == NULL)
 1970       escape = *ok_vlook(escape);
 1971    flags = ok_blook(errexit) ? a_ERREXIT : a_NONE;
 1972 
 1973    for(;;){
 1974       enum {a_HIST_NONE, a_HIST_ADD = 1u<<0, a_HIST_GABBY = 1u<<1} hist;
 1975 
 1976       /* C99 */{
 1977          enum n_go_input_flags gif;
 1978          bool_t histadd;
 1979 
 1980          gif = n_GO_INPUT_CTX_COMPOSE;
 1981          histadd = (sp != NULL);
 1982          if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_TILDE_FLAG)){
 1983             if(!(n_poption & n_PO_t_FLAG))
 1984                gif |= n_GO_INPUT_NL_ESC;
 1985          }
 1986          cnt = n_go_input(gif, n_empty, &linebuf, &linesize, NULL, &histadd);
 1987          hist = histadd ? a_HIST_ADD | a_HIST_GABBY : a_HIST_NONE;
 1988       }
 1989 
 1990       if(cnt < 0){
 1991          if(coap != NULL)
 1992             break;
 1993          if(n_poption & n_PO_t_FLAG){
 1994             fflush_rewind(_coll_fp);
 1995             /* It is important to set n_PSO_t_FLAG before extract_header()
 1996              * *and* keep n_PO_t_FLAG for the first parse of the message!
 1997              * TODO This is a hack, we need a clean approach for this */
 1998             n_psonce |= n_PSO_t_FLAG;
 1999             if(!a_coll_makeheader(_coll_fp, hp, checkaddr_err, TRU1))
 2000                goto jerr;
 2001             n_poption &= ~n_PO_t_FLAG;
 2002             continue;
 2003          }else if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 2004                ok_blook(ignoreeof) && ++eofcnt < 4){
 2005             fprintf(n_stdout,
 2006                _("*ignoreeof* set, use `~.' to terminate letter\n"));
 2007             n_go_input_clearerr();
 2008             continue;
 2009          }
 2010          break;
 2011       }
 2012 
 2013       _coll_hadintr = 0;
 2014 
 2015       cp = linebuf;
 2016       if(cnt == 0)
 2017          goto jputnl;
 2018       else if(coap == NULL){
 2019          if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_TILDE_FLAG))
 2020             goto jputline;
 2021          else if(cp[0] == '.'){
 2022             if(cnt == 1 && (ok_blook(dot) ||
 2023                   (ok_blook(posix) && ok_blook(ignoreeof))))
 2024                break;
 2025          }
 2026       }
 2027       if(cp[0] != escape){
 2028 jputline:
 2029          if(fwrite(cp, sizeof *cp, cnt, _coll_fp) != (size_t)cnt)
 2030             goto jerr;
 2031          /* TODO n_PS_READLINE_NL is a terrible hack to ensure that _in_all_-
 2032           * TODO _code_paths_ a file without trailing newline isn't modified
 2033           * TODO to contain one; the "saw-newline" needs to be part of an
 2034           * TODO I/O input machinery object */
 2035 jputnl:
 2036          if(n_pstate & n_PS_READLINE_NL){
 2037             if(putc('\n', _coll_fp) == EOF)
 2038                goto jerr;
 2039          }
 2040          continue;
 2041       }
 2042 
 2043       c = *(cp_base = ++cp);
 2044       if(--cnt == 0)
 2045          goto jearg;
 2046 
 2047       /* Avoid history entry? */
 2048       while(spacechar(c)){
 2049          hist = a_HIST_NONE;
 2050          c = *(cp_base = ++cp);
 2051          if(--cnt == 0)
 2052             goto jearg;
 2053       }
 2054 
 2055       /* It may just be an escaped escaped character, do that quick */
 2056       if(c == escape)
 2057          goto jputline;
 2058 
 2059       /* Avoid hard *errexit*? */
 2060       flags &= ~a_IGNERR;
 2061       if(c == '-'){
 2062          flags ^= a_IGNERR;
 2063          c = *++cp;
 2064          if(--cnt == 0)
 2065             goto jearg;
 2066       }
 2067 
 2068       /* Trim input, also to gain a somewhat normalized history entry */
 2069       ++cp;
 2070       if(--cnt > 0){
 2071          struct str x;
 2072 
 2073          x.s = n_UNCONST(cp);
 2074          x.l = (size_t)cnt;
 2075          n_str_trim_ifs(&x, TRU1);
 2076          x.s[x.l] = '\0';
 2077          cp = x.s;
 2078          cnt = (int)/*XXX*/x.l;
 2079       }
 2080 
 2081       if(hist != a_HIST_NONE){
 2082          sp = n_string_assign_c(sp, escape);
 2083          if(flags & a_IGNERR)
 2084             sp = n_string_push_c(sp, '-');
 2085          sp = n_string_push_c(sp, c);
 2086          if(cnt > 0){
 2087             sp = n_string_push_c(sp, ' ');
 2088             sp = n_string_push_buf(sp, cp, cnt);
 2089          }
 2090       }
 2091 
 2092       /* Switch over all command escapes */
 2093       switch(c){
 2094       default:
 2095          if(1){
 2096             char buf[sizeof(n_UNIREPL)];
 2097 
 2098             if(asciichar(c))
 2099                buf[0] = c, buf[1] = '\0';
 2100             else if(n_psonce & n_PSO_UNICODE)
 2101                memcpy(buf, n_unirepl, sizeof n_unirepl);
 2102             else
 2103                buf[0] = '?', buf[1] = '\0';
 2104             n_err(_("Unknown command escape: `%c%s'\n"), escape, buf);
 2105          }else
 2106 jearg:
 2107             n_err(_("Invalid command escape usage: %s\n"),
 2108                n_shexp_quote_cp(linebuf, FAL0));
 2109          if(a_HARDERR())
 2110             goto jerr;
 2111          n_pstate_err_no = n_ERR_INVAL;
 2112          n_pstate_ex_no = 1;
 2113          continue;
 2114       case '!':
 2115          /* Shell escape, send the balance of line to sh -c */
 2116          if(cnt == 0 || coap != NULL)
 2117             goto jearg;
 2118          else{
 2119             char const *argv[2];
 2120 
 2121             argv[0] = cp;
 2122             argv[1] = NULL;
 2123             n_pstate_ex_no = c_shell(argv); /* TODO history norm.; errexit? */
 2124          }
 2125          goto jhistcont;
 2126       case '.':
 2127          /* Simulate end of file on input */
 2128          if(cnt != 0 || coap != NULL)
 2129             goto jearg;
 2130          goto jout; /* TODO does not enter history, thus */
 2131       case ':':
 2132       case '_':
 2133          /* Escape to command mode, but be nice! *//* TODO command expansion
 2134           * TODO should be handled here so that we have unique history! */
 2135          if(cnt == 0)
 2136             goto jearg;
 2137          _execute_command(hp, cp, cnt);
 2138          if(ok_blook(errexit))
 2139             flags |= a_ERREXIT;
 2140          else
 2141             flags &= ~a_ERREXIT;
 2142          if(n_pstate_ex_no != 0 && a_HARDERR())
 2143             goto jerr;
 2144          if(coap == NULL)
 2145             escape = *ok_vlook(escape);
 2146          hist &= ~a_HIST_GABBY;
 2147          break;
 2148       /* case '<': <> 'd' */
 2149       case '?':
 2150 #ifdef HAVE_UISTRINGS
 2151          fputs(_(
 2152 "COMMAND ESCAPES (to be placed after a newline) excerpt:\n"
 2153 "~.            Commit and send message\n"
 2154 "~: <command>  Execute an internal command\n"
 2155 "~< <file>     Insert <file> (\"~<! <command>\" inserts shell command)\n"
 2156 "~@ [<files>]  Edit[/Add] attachments (file[=input-charset[#output-charset]])\n"
 2157 "~c <users>    Add users to Cc: list (`~b': to Bcc:)\n"
 2158 "~d            Read in $DEAD (dead.letter)\n"
 2159 "~e            Edit message via $EDITOR\n"
 2160 "~F <msglist>  Read in with headers, do not *indentprefix* lines\n"
 2161             ), n_stdout);
 2162          fputs(_(
 2163 "~f <msglist>  Like ~F, but honour `ignore' / `retain' configuration\n"
 2164 "~H            Edit From:, Reply-To: and Sender:\n"
 2165 "~h            Prompt for Subject:, To:, Cc: and \"blind\" Bcc:\n"
 2166 "~i <variable> Insert a value and a newline\n"
 2167 "~M <msglist>  Read in with headers, *indentprefix* (`~m': `retain' etc.)\n"
 2168 "~p            Show current message compose buffer\n"
 2169 "~r <file>     Insert <file> (`~R': likewise, but *indentprefix* lines)\n"
 2170 "              <file> may also be <- [HERE-DELIMITER]>\n"
 2171             ), n_stdout);
 2172          fputs(_(
 2173 "~s <subject>  Set Subject:\n"
 2174 "~t <users>    Add users to To: list\n"
 2175 "~u <msglist>  Read in message(s) without headers (`~U': indent lines)\n"
 2176 "~v            Edit message via $VISUAL\n"
 2177 "~w <file>     Write message onto file\n"
 2178 "~x            Abort composition, discard message (`~q': save in $DEAD)\n"
 2179 "~| <command>  Pipe message through shell filter\n"
 2180             ), n_stdout);
 2181 #endif /* HAVE_UISTRINGS */
 2182          if(cnt != 0)
 2183             goto jearg;
 2184          n_pstate_err_no = n_ERR_NONE;
 2185          n_pstate_ex_no = 0;
 2186          break;
 2187       case '@':{
 2188          struct attachment *aplist;
 2189 
 2190          /* Edit the attachment list */
 2191          aplist = hp->h_attach;
 2192          hp->h_attach = NULL;
 2193          if(cnt != 0)
 2194             hp->h_attach = n_attachment_append_list(aplist, cp);
 2195          else
 2196             hp->h_attach = n_attachment_list_edit(aplist,
 2197                   n_GO_INPUT_CTX_COMPOSE);
 2198          n_pstate_err_no = n_ERR_NONE; /* XXX ~@ does NOT handle $!/$?! */
 2199          n_pstate_ex_no = 0; /* XXX */
 2200          }break;
 2201       case '^':
 2202          if(!a_collect_plumbing(cp, hp)){
 2203             if(ferror(_coll_fp))
 2204                goto jerr;
 2205             goto jearg;
 2206          }
 2207          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2208          n_pstate_ex_no = 0; /* XXX */
 2209          hist &= ~a_HIST_GABBY;
 2210          break;
 2211       /* case '_': <> ':' */
 2212       case '|':
 2213          /* Pipe message through command. Collect output as new message */
 2214          if(cnt == 0)
 2215             goto jearg;
 2216          rewind(_coll_fp);
 2217          if((n_pstate_err_no = a_coll_pipe(cp)) == n_ERR_NONE)
 2218             n_pstate_ex_no = 0;
 2219          else if(a_HARDERR())
 2220             goto jerr;
 2221          else
 2222             n_pstate_ex_no = 1;
 2223          hist &= ~a_HIST_GABBY;
 2224          goto jhistcont;
 2225       case 'A':
 2226       case 'a':
 2227          /* Insert the contents of a sign variable */
 2228          if(cnt != 0)
 2229             goto jearg;
 2230          cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
 2231          goto jIi_putesc;
 2232       case 'b':
 2233          /* Add stuff to blind carbon copies list TODO join 'c' */
 2234          if(cnt == 0)
 2235             goto jearg;
 2236          else{
 2237             struct name *np;
 2238             si8_t soe;
 2239 
 2240             soe = 0;
 2241             if((np = checkaddrs(lextract(cp, GBCC | GFULL), EACM_NORMAL, &soe)
 2242                   ) != NULL)
 2243                hp->h_bcc = cat(hp->h_bcc, np);
 2244             if(soe == 0){
 2245                n_pstate_err_no = n_ERR_NONE;
 2246                n_pstate_ex_no = 0;
 2247             }else{
 2248                n_pstate_ex_no = 1;
 2249                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 2250             }
 2251          }
 2252          hist &= ~a_HIST_GABBY;
 2253          break;
 2254       case 'c':
 2255          /* Add to the CC list TODO join 'b' */
 2256          if(cnt == 0)
 2257             goto jearg;
 2258          else{
 2259             struct name *np;
 2260             si8_t soe;
 2261 
 2262             soe = 0;
 2263             if((np = checkaddrs(lextract(cp, GCC | GFULL), EACM_NORMAL, &soe)
 2264                   ) != NULL)
 2265                hp->h_cc = cat(hp->h_cc, np);
 2266             if(soe == 0){
 2267                n_pstate_err_no = n_ERR_NONE;
 2268                n_pstate_ex_no = 0;
 2269             }else{
 2270                n_pstate_ex_no = 1;
 2271                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 2272             }
 2273          }
 2274          hist &= ~a_HIST_GABBY;
 2275          break;
 2276       case 'd':
 2277          if(cnt != 0)
 2278             goto jearg;
 2279          cp = n_getdeadletter();
 2280          if(0){
 2281       case '<':
 2282       case 'R':
 2283       case 'r':
 2284             /* Invoke a file: Search for the file name, then open it and copy
 2285              * the contents to _coll_fp */
 2286             if(cnt == 0){
 2287                n_err(_("Interpolate what file?\n"));
 2288                if(a_HARDERR())
 2289                   goto jerr;
 2290                n_pstate_err_no = n_ERR_NOENT;
 2291                n_pstate_ex_no = 1;
 2292                break;
 2293             }
 2294             if(*cp == '!' && c == '<'){
 2295                /* TODO hist. normalization */
 2296                if((n_pstate_err_no = a_coll_insert_cmd(_coll_fp, ++cp)
 2297                      ) != n_ERR_NONE){
 2298                   if(ferror(_coll_fp))
 2299                      goto jerr;
 2300                   if(a_HARDERR())
 2301                      goto jerr;
 2302                   n_pstate_ex_no = 1;
 2303                   break;
 2304                }
 2305                goto jhistcont;
 2306             }
 2307             /* Note this also expands things like
 2308              *    !:vput vexpr delim random 0
 2309              *    !< - $delim */
 2310             if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO | FEXP_NSHELL)
 2311                   ) == NULL){
 2312                if(a_HARDERR())
 2313                   goto jerr;
 2314                n_pstate_err_no = n_ERR_INVAL;
 2315                n_pstate_ex_no = 1;
 2316                break;
 2317             }
 2318          }
 2319          if(n_is_dir(cp, FAL0)){
 2320             n_err(_("%s: is a directory\n"), n_shexp_quote_cp(cp, FAL0));
 2321             if(a_HARDERR())
 2322                goto jerr;
 2323             n_pstate_err_no = n_ERR_ISDIR;
 2324             n_pstate_ex_no = 1;
 2325             break;
 2326          }
 2327          if((n_pstate_err_no = a_coll_include_file(cp, (c == 'R'), TRU1)
 2328                ) != n_ERR_NONE){
 2329             if(ferror(_coll_fp))
 2330                goto jerr;
 2331             if(a_HARDERR())
 2332                goto jerr;
 2333             n_pstate_ex_no = 1;
 2334             break;
 2335          }
 2336          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2337          n_pstate_ex_no = 0; /* XXX */
 2338          break;
 2339       case 'e':
 2340       case 'v':
 2341          /* Edit the current message.  'e' -> use EDITOR, 'v' -> use VISUAL */
 2342          if(cnt != 0 || coap != NULL)
 2343             goto jearg;
 2344          rewind(_coll_fp);
 2345          if((n_pstate_err_no = a_coll_edit(c, ok_blook(editheaders) ? hp : NULL)
 2346                ) == n_ERR_NONE)
 2347             n_pstate_ex_no = 0;
 2348          else if(ferror(_coll_fp))
 2349             goto jerr;
 2350          else if(a_HARDERR())
 2351             goto jerr;
 2352          else
 2353             n_pstate_ex_no = 1;
 2354          goto jhistcont;
 2355       case 'F':
 2356       case 'f':
 2357       case 'M':
 2358       case 'm':
 2359       case 'U':
 2360       case 'u':
 2361          /* Interpolate the named messages, if we are in receiving mail mode.
 2362           * Does the standard list processing garbage.  If ~f is given, we
 2363           * don't shift over */
 2364          if(cnt == 0)
 2365             goto jearg;
 2366          if((n_pstate_err_no = a_coll_forward(cp, _coll_fp, c)) == n_ERR_NONE)
 2367             n_pstate_ex_no = 0;
 2368          else if(ferror(_coll_fp))
 2369             goto jerr;
 2370          else if(a_HARDERR())
 2371             goto jerr;
 2372          else
 2373             n_pstate_ex_no = 1;
 2374          break;
 2375       case 'H':
 2376          /* Grab extra headers */
 2377          if(cnt != 0)
 2378             goto jearg;
 2379          do
 2380             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, GEXTRA, 0);
 2381          while(check_from_and_sender(hp->h_from, hp->h_sender) == NULL);
 2382          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2383          n_pstate_ex_no = 0; /* XXX */
 2384          break;
 2385       case 'h':
 2386          /* Grab a bunch of headers */
 2387          if(cnt != 0)
 2388             goto jearg;
 2389          do
 2390             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp,
 2391               (GTO | GSUBJECT | GCC | GBCC),
 2392               (ok_blook(bsdcompat) && ok_blook(bsdorder)));
 2393          while(hp->h_to == NULL);
 2394          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2395          n_pstate_ex_no = 0; /* XXX */
 2396          break;
 2397       case 'I':
 2398       case 'i':
 2399          /* Insert a variable into the file */
 2400          if(cnt == 0)
 2401             goto jearg;
 2402          cp = n_var_vlook(cp, TRU1);
 2403 jIi_putesc:
 2404          if(cp == NULL || *cp == '\0')
 2405             break;
 2406          if(!a_coll_putesc(cp, (c != 'I'), _coll_fp))
 2407             goto jerr;
 2408          if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 2409                !a_coll_putesc(cp, (c != 'I'), n_stdout))
 2410             goto jerr;
 2411          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2412          n_pstate_ex_no = 0; /* XXX */
 2413          break;
 2414       /* case 'M': <> 'F' */
 2415       /* case 'm': <> 'f' */
 2416       case 'p':
 2417          /* Print current state of the message without altering anything */
 2418          if(cnt != 0)
 2419             goto jearg;
 2420          print_collf(_coll_fp, hp); /* XXX pstate_err_no ++ */
 2421          if(ferror(_coll_fp))
 2422             goto jerr;
 2423          n_pstate_err_no = n_ERR_NONE; /* XXX */
 2424          n_pstate_ex_no = 0; /* XXX */
 2425          break;
 2426       case 'q':
 2427       case 'x':
 2428          /* Force a quit, act like an interrupt had happened */
 2429          if(cnt != 0)
 2430             goto jearg;
 2431          /* If we are running a splice hook, assume it quits on its own now,
 2432           * otherwise we (no true out-of-band IPC to signal this state, XXX sic)
 2433           * have to SIGTERM it in order to stop this wild beast */
 2434          flags |= a_COAP_NOSIGTERM;
 2435          ++_coll_hadintr;
 2436          _collint((c == 'x') ? 0 : SIGINT);
 2437          exit(n_EXIT_ERR);
 2438          /*NOTREACHED*/
 2439       /* case 'R': <> 'd' */
 2440       /* case 'r': <> 'd' */
 2441       case 's':
 2442          /* Set the Subject list */
 2443          if(cnt == 0)
 2444             goto jearg;
 2445          /* Subject:; take care for Debian #419840 and strip any \r and \n */
 2446          if(n_anyof_cp("\n\r", hp->h_subject = savestr(cp))){
 2447             char *xp;
 2448 
 2449             n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n"));
 2450             for(xp = hp->h_subject; *xp != '\0'; ++xp)
 2451                if(*xp == '\n' || *xp == '\r')
 2452                   *xp = ' ';
 2453             n_pstate_err_no = n_ERR_INVAL;
 2454             n_pstate_ex_no = 1;
 2455          }else{
 2456             n_pstate_err_no = n_ERR_NONE;
 2457             n_pstate_ex_no = 0;
 2458          }
 2459          break;
 2460       case 't':
 2461          /* Add to the To: list TODO join 'b', 'c' */
 2462          if(cnt == 0)
 2463             goto jearg;
 2464          else{
 2465             struct name *np;
 2466             si8_t soe;
 2467 
 2468             soe = 0;
 2469             if((np = checkaddrs(lextract(cp, GTO | GFULL), EACM_NORMAL, &soe)
 2470                   ) != NULL)
 2471                hp->h_to = cat(hp->h_to, np);
 2472             if(soe == 0){
 2473                n_pstate_err_no = n_ERR_NONE;
 2474                n_pstate_ex_no = 0;
 2475             }else{
 2476                n_pstate_ex_no = 1;
 2477                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 2478             }
 2479          }
 2480          hist &= ~a_HIST_GABBY;
 2481          break;
 2482       /* case 'U': <> 'F' */
 2483       /* case 'u': <> 'f' */
 2484       /* case 'v': <> 'e' */
 2485       case 'w':
 2486          /* Write the message on a file */
 2487          if(cnt == 0)
 2488             goto jearg;
 2489          if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
 2490             n_err(_("Write what file!?\n"));
 2491             if(a_HARDERR())
 2492                goto jerr;
 2493             n_pstate_err_no = n_ERR_INVAL;
 2494             n_pstate_ex_no = 1;
 2495             break;
 2496          }
 2497          rewind(_coll_fp);
 2498          if((n_pstate_err_no = a_coll_write(cp, _coll_fp, 1)) == n_ERR_NONE)
 2499             n_pstate_ex_no = 0;
 2500          else if(ferror(_coll_fp))
 2501             goto jerr;
 2502          else if(a_HARDERR())
 2503             goto jerr;
 2504          else
 2505             n_pstate_ex_no = 1;
 2506          break;
 2507       /* case 'x': <> 'q' */
 2508       }
 2509 
 2510       /* Finally place an entry in history as applicable */
 2511       if(0){
 2512 jhistcont:
 2513          c = '\1';
 2514       }else
 2515          c = '\0';
 2516       if(hist & a_HIST_ADD){
 2517          /* Do not add *escape* to the history in order to allow history search
 2518           * to be handled generically in the MLE regardless of actual *escape*
 2519           * settings etc. */
 2520          n_tty_addhist(&n_string_cp(sp)[1], (n_GO_INPUT_CTX_COMPOSE |
 2521             (hist & a_HIST_GABBY ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE)));
 2522       }
 2523       if(c != '\0')
 2524          goto jcont;
 2525    }
 2526 
 2527 jout:
 2528    /* Do we have *on-compose-splice-shell*, or *on-compose-splice*?
 2529     * TODO Usual f...ed up state of signals and terminal etc. */
 2530    if(coap == NULL && (cp = ok_vlook(on_compose_splice_shell)) != NULL) Jocs:{
 2531       union {int (*ptf)(void); char const *sh;} u;
 2532       char const *cmd;
 2533 
 2534       /* Reset *escape* and more to their defaults.  On change update manual! */
 2535       if(ifs_saved == NULL)
 2536          ifs_saved = savestr(ok_vlook(ifs));
 2537       escape = n_ESCAPE[0];
 2538       ok_vclear(ifs);
 2539 
 2540       if(coapm != NULL){
 2541          /* XXX Due Popen() fflush(NULL) in PTF mode, ensure nothing to flush */
 2542          /*if(!n_real_seek(_coll_fp, 0, SEEK_END))
 2543           *  goto jerr;*/
 2544          u.ptf = &a_coll_ocs__mac;
 2545          cmd = (char*)-1;
 2546          a_coll_ocs__macname = cp = coapm;
 2547       }else{
 2548          u.sh = ok_vlook(SHELL);
 2549          cmd = cp;
 2550       }
 2551 
 2552       i = strlen(cp) +1;
 2553       coap = n_lofi_alloc(n_VSTRUCT_SIZEOF(struct a_coll_ocs_arg, coa_cmd
 2554             ) + i);
 2555       coap->coa_pipe[0] = coap->coa_pipe[1] = -1;
 2556       coap->coa_stdin = coap->coa_stdout = NULL;
 2557       coap->coa_senderr = checkaddr_err;
 2558       memcpy(coap->coa_cmd, cp, i);
 2559 
 2560       hold_all_sigs();
 2561       coap->coa_opipe = safe_signal(SIGPIPE, SIG_IGN);
 2562       coap->coa_oint = safe_signal(SIGINT, SIG_IGN);
 2563       rele_all_sigs();
 2564 
 2565       if(pipe_cloexec(coap->coa_pipe) != -1 &&
 2566             (coap->coa_stdin = Fdopen(coap->coa_pipe[0], "r", FAL0)) != NULL &&
 2567             (coap->coa_stdout = Popen(cmd, "W", u.sh, NULL, coap->coa_pipe[1])
 2568              ) != NULL){
 2569          close(coap->coa_pipe[1]);
 2570          coap->coa_pipe[1] = -1;
 2571 
 2572          temporary_compose_mode_hook_call(NULL, NULL, NULL);
 2573          n_go_splice_hack(coap->coa_cmd, coap->coa_stdin, coap->coa_stdout,
 2574             (n_psonce & ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT)),
 2575             &a_coll_ocs__finalize, &coap);
 2576          /* Hook version protocol for ~^: update manual upon change! */
 2577          fputs(a_COLL_PLUMBING_VERSION "\n", coap->coa_stdout);
 2578 #undef a_COLL_PLUMBING_VERSION
 2579          goto jcont;
 2580       }
 2581 
 2582       c = n_err_no;
 2583       a_coll_ocs__finalize(coap);
 2584       n_perr(_("Cannot invoke *on-compose-splice(-shell)?*"), c);
 2585       goto jerr;
 2586    }
 2587    if(*checkaddr_err != 0){
 2588       *checkaddr_err = 0;
 2589       goto jerr;
 2590    }
 2591    if(coapm == NULL && (coapm = ok_vlook(on_compose_splice)) != NULL)
 2592       goto Jocs;
 2593    if(coap != NULL){
 2594       ok_vset(ifs, ifs_saved);
 2595       ifs_saved = NULL;
 2596    }
 2597 
 2598    /* Final chance to edit headers, if not already above */
 2599    if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
 2600       if(ok_blook(bsdcompat) || ok_blook(askatend)){
 2601          enum gfield gf;
 2602 
 2603          gf = GNONE;
 2604          if(ok_blook(askcc))
 2605             gf |= GCC;
 2606          if(ok_blook(askbcc))
 2607             gf |= GBCC;
 2608          if(gf != 0)
 2609             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, gf, 1);
 2610       }
 2611       if(ok_blook(askattach))
 2612          hp->h_attach = n_attachment_list_edit(hp->h_attach,
 2613                n_GO_INPUT_CTX_COMPOSE);
 2614    }
 2615 
 2616    /* Execute compose-leave */
 2617    if((cp = ok_vlook(on_compose_leave)) != NULL){
 2618       setup_from_and_sender(hp);
 2619       temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
 2620          hp);
 2621    }
 2622 
 2623    /* Add automatic receivers */
 2624    if ((cp = ok_vlook(autocc)) != NULL && *cp != '\0')
 2625       hp->h_cc = cat(hp->h_cc, checkaddrs(lextract(cp, GCC |
 2626             (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
 2627             EACM_NORMAL, checkaddr_err));
 2628    if ((cp = ok_vlook(autobcc)) != NULL && *cp != '\0')
 2629       hp->h_bcc = cat(hp->h_bcc, checkaddrs(lextract(cp, GBCC |
 2630             (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
 2631             EACM_NORMAL, checkaddr_err));
 2632    if (*checkaddr_err != 0)
 2633       goto jerr;
 2634 
 2635    if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 2636          ok_blook(asksend)){
 2637       bool_t b;
 2638 
 2639       ifs_saved = coapm = NULL;
 2640       coap = NULL;
 2641 
 2642       fprintf(n_stdout, _("-------\nEnvelope contains:\n")); /* xxx SEARCH112 */
 2643       puthead(TRU1, hp, n_stdout, GIDENT | GSUBJECT | GTO | GCC | GBCC | GCOMMA,
 2644          SEND_TODISP, CONV_NONE, NULL, NULL);
 2645 
 2646 jreasksend:
 2647       if(n_go_input(n_GO_INPUT_CTX_COMPOSE | n_GO_INPUT_NL_ESC,
 2648             _("Send this message [yes/no, empty: recompose]? "),
 2649             &linebuf, &linesize, NULL, NULL) < 0){
 2650          if(!n_go_input_is_eof())
 2651             goto jerr;
 2652          cp = n_1;
 2653       }
 2654 
 2655       if((b = n_boolify(linebuf, UIZ_MAX, TRUM1)) < FAL0)
 2656          goto jreasksend;
 2657       if(b == TRU2)
 2658          goto jcont;
 2659       if(!b)
 2660          goto jerr;
 2661    }
 2662 
 2663    /* TODO Cannot do since it may require turning this into a multipart one */
 2664    if(n_poption & n_PO_Mm_FLAG)
 2665       goto jskiptails;
 2666 
 2667    /* Place signature? */
 2668    if((cp = ok_vlook(signature)) != NULL && *cp != '\0'){ /* TODO OBSOLETE */
 2669       char const *cpq;
 2670 
 2671       n_OBSOLETE(_("please use *on-compose-{leave,splice}* and/or "
 2672          "*message-inject-tail*, not *signature*"));
 2673 
 2674       if((cpq = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
 2675          n_err(_("*signature* expands to invalid file: %s\n"),
 2676             n_shexp_quote_cp(cp, FAL0));
 2677          goto jerr;
 2678       }
 2679       cpq = n_shexp_quote_cp(cp = cpq, FAL0);
 2680 
 2681       if((sigfp = Fopen(cp, "r")) == NULL){
 2682          n_err(_("Can't open *signature* %s: %s\n"),
 2683             cpq, n_err_to_doc(n_err_no));
 2684          goto jerr;
 2685       }
 2686 
 2687       if(linebuf == NULL)
 2688          linebuf = smalloc(linesize = LINESIZE);
 2689       c = '\0';
 2690       while((i = fread(linebuf, sizeof *linebuf, linesize, n_UNVOLATILE(sigfp)))
 2691             > 0){
 2692          c = linebuf[i - 1];
 2693          if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
 2694             goto jerr;
 2695       }
 2696 
 2697       /* C99 */{
 2698          FILE *x = n_UNVOLATILE(sigfp);
 2699          int e = n_err_no, ise = ferror(x);
 2700 
 2701          sigfp = NULL;
 2702          Fclose(x);
 2703 
 2704          if(ise){
 2705             n_err(_("Errors while reading *signature* %s: %s\n"),
 2706                cpq, n_err_to_doc(e));
 2707             goto jerr;
 2708          }
 2709       }
 2710 
 2711       if(c != '\0' && c != '\n')
 2712          putc('\n', _coll_fp);
 2713    }
 2714 
 2715    {  char const *cp_obsolete = ok_vlook(NAIL_TAIL);
 2716 
 2717       if(cp_obsolete != NULL)
 2718          n_OBSOLETE(_("please use *message-inject-tail*, not *NAIL_TAIL*"));
 2719 
 2720    if((cp = ok_vlook(message_inject_tail)) != NULL ||
 2721          (cp = cp_obsolete) != NULL){
 2722       if(!a_coll_putesc(cp, TRU1, _coll_fp))
 2723          goto jerr;
 2724       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 2725             !a_coll_putesc(cp, TRU1, n_stdout))
 2726          goto jerr;
 2727    }
 2728    }
 2729 
 2730 jskiptails:
 2731    if(fflush(_coll_fp))
 2732       goto jerr;
 2733    rewind(_coll_fp);
 2734 
 2735 jleave:
 2736    if (linebuf != NULL)
 2737       free(linebuf);
 2738    sigprocmask(SIG_BLOCK, &nset, NULL);
 2739    n_pstate &= ~n_PS_COMPOSE_MODE;
 2740    safe_signal(SIGINT, _coll_saveint);
 2741    safe_signal(SIGHUP, _coll_savehup);
 2742    sigprocmask(SIG_SETMASK, &oset, NULL);
 2743    NYD_LEAVE;
 2744    return _coll_fp;
 2745 
 2746 jerr:
 2747    hold_all_sigs();
 2748 
 2749    if(coap != NULL && coap != (struct a_coll_ocs_arg*)-1){
 2750       if(!(flags & a_COAP_NOSIGTERM))
 2751          n_psignal(coap->coa_stdout, SIGTERM);
 2752       n_go_splice_hack_remove_after_jump();
 2753       coap = NULL;
 2754    }
 2755    if(ifs_saved != NULL){
 2756       ok_vset(ifs, ifs_saved);
 2757       ifs_saved = NULL;
 2758    }
 2759    if(sigfp != NULL){
 2760       Fclose(n_UNVOLATILE(sigfp));
 2761       sigfp = NULL;
 2762    }
 2763    if(_coll_fp != NULL){
 2764       Fclose(_coll_fp);
 2765       _coll_fp = NULL;
 2766    }
 2767 
 2768    rele_all_sigs();
 2769 
 2770    assert(checkaddr_err != NULL);
 2771    /* TODO We don't save in $DEAD upon error because msg not readily composed?
 2772     * TODO But this no good, it should go ZOMBIE / DRAFT / POSTPONED or what! */
 2773    if(*checkaddr_err != 0){
 2774       if(*checkaddr_err == 111)
 2775          n_err(_("Compose mode splice hook failure\n"));
 2776       else
 2777          n_err(_("Some addressees were classified as \"hard error\"\n"));
 2778    }else if(_coll_hadintr == 0){
 2779       *checkaddr_err = TRU1; /* TODO ugly: "sendout_error" now.. */
 2780       n_err(_("Failed to prepare composed message\n"));
 2781    }
 2782    goto jleave;
 2783 
 2784 #undef a_HARDERR
 2785 }
 2786 
 2787 /* s-it-mode */