"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ 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  * SPDX-License-Identifier: BSD-3-Clause
    8  */
    9 /*
   10  * Copyright (c) 1980, 1993
   11  *      The Regents of the University of California.  All rights reserved.
   12  *
   13  * Redistribution and use in source and binary forms, with or without
   14  * modification, are permitted provided that the following conditions
   15  * are met:
   16  * 1. Redistributions of source code must retain the above copyright
   17  *    notice, this list of conditions and the following disclaimer.
   18  * 2. Redistributions in binary form must reproduce the above copyright
   19  *    notice, this list of conditions and the following disclaimer in the
   20  *    documentation and/or other materials provided with the distribution.
   21  * 3. Neither the name of the University nor the names of its contributors
   22  *    may be used to endorse or promote products derived from this software
   23  *    without specific prior written permission.
   24  *
   25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   35  * SUCH DAMAGE.
   36  */
   37 #undef n_FILE
   38 #define n_FILE collect
   39 
   40 #ifndef HAVE_AMALGAMATION
   41 # include "nail.h"
   42 #endif
   43 
   44 struct a_coll_fmt_ctx{ /* xxx This is temporary until v15 has objects */
   45    char const *cfc_fmt;
   46    FILE *cfc_fp;
   47    struct message *cfc_mp;
   48    char *cfc_cumul;
   49    char *cfc_addr;
   50    char *cfc_real;
   51    char *cfc_full;
   52    char *cfc_date;
   53    char const *cfc_msgid;  /* Or NULL */
   54 };
   55 
   56 struct a_coll_ocs_arg{
   57    sighandler_type coa_opipe;
   58    sighandler_type coa_oint;
   59    FILE *coa_stdin;  /* The descriptor (pipe(2)+Fdopen()) we read from */
   60    FILE *coa_stdout; /* The Popen()ed pipe through which we write to the hook */
   61    int coa_pipe[2];  /* ..backing .coa_stdin */
   62    si8_t *coa_senderr; /* Set to 1 on failure */
   63    char coa_cmd[n_VFIELD_SIZE(0)];
   64 };
   65 
   66 /* The following hookiness with global variables is so that on receipt of an
   67  * interrupt signal, the partial message can be salted away on *DEAD* */
   68 
   69 static sighandler_type  _coll_saveint;    /* Previous SIGINT value */
   70 static sighandler_type  _coll_savehup;    /* Previous SIGHUP value */
   71 static FILE             *_coll_fp;        /* File for saving away */
   72 static int volatile     _coll_hadintr;    /* Have seen one SIGINT so far */
   73 static sigjmp_buf       _coll_jmp;        /* To get back to work */
   74 static sigjmp_buf       _coll_abort;      /* To end collection with error */
   75 static char const *a_coll_ocs__macname;   /* *on-compose-splice* */
   76 
   77 /* Handle `~:', `~_' and some hooks; hp may be NULL */
   78 static void       _execute_command(struct header *hp, char const *linebuf,
   79                      size_t linesize);
   80 
   81 /* Return errno */
   82 static si32_t a_coll_include_file(char const *name, bool_t indent,
   83                bool_t writestat);
   84 
   85 /* Execute cmd and insert its standard output into fp, return errno */
   86 static si32_t a_coll_insert_cmd(FILE *fp, char const *cmd);
   87 
   88 /* ~p command */
   89 static void       print_collf(FILE *collf, struct header *hp);
   90 
   91 /* Write a file, ex-like if f set */
   92 static si32_t a_coll_write(char const *name, FILE *fp, int f);
   93 
   94 /* *message-inject-head* */
   95 static bool_t a_coll_message_inject_head(FILE *fp);
   96 
   97 /* With bells and whistles */
   98 static bool_t a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd);
   99 
  100 /* *{forward,quote}-inject-{head,tail}*.
  101  * fmt may be NULL or the empty string, in which case no output is produced */
  102 static bool_t a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp);
  103 
  104 /* Parse off the message header from fp and store relevant fields in hp,
  105  * replace _coll_fp with a shiny new version without any header.
  106  * Takes care for closing of fp and _coll_fp as necessary */
  107 static bool_t a_coll_makeheader(FILE *fp, struct header *hp,
  108                si8_t *checkaddr_err, bool_t do_delayed_due_t);
  109 
  110 /* Edit the message being collected on fp.
  111  * If c=='|' pipecmd must be set and is passed through to n_run_editor().
  112  * On successful return, make the edit file the new temp file; return errno */
  113 static si32_t a_coll_edit(int c, struct header *hp, char const *pipecmd);
  114 
  115 /* Pipe the message through the command.  Old message is on stdin of command,
  116  * new message collected from stdout.  Shell must return 0 to accept new msg */
  117 static si32_t a_coll_pipe(char const *cmd);
  118 
  119 /* Interpolate the named messages into the current message, possibly doing
  120  * indent stuff.  The flag argument is one of the command escapes: [mMfFuU].
  121  * Return errno */
  122 static si32_t a_coll_forward(char const *ms, FILE *fp, int f);
  123 
  124 /* On interrupt, come here to save the partial message in ~/dead.letter.
  125  * Then jump out of the collection loop */
  126 static void       _collint(int s);
  127 
  128 static void       collhup(int s);
  129 
  130 /* ~[AaIi], *message-inject-**: put value, expand \[nt] if *posix* */
  131 static bool_t a_coll_putesc(char const *s, bool_t addnl, FILE *stream);
  132 
  133 /* *on-compose-splice* driver and *on-compose-splice(-shell)?* finalizer */
  134 static int a_coll_ocs__mac(void);
  135 static void a_coll_ocs__finalize(void *vp);
  136 
  137 static void
  138 _execute_command(struct header *hp, char const *linebuf, size_t linesize){
  139    /* The problem arises if there are rfc822 message attachments and the
  140     * user uses `~:' to change the current file.  TODO Unfortunately we
  141     * TODO cannot simply keep a pointer to, or increment a reference count
  142     * TODO of the current `file' (mailbox that is) object, because the
  143     * TODO codebase doesn't deal with that at all; so, until some far
  144     * TODO later time, copy the name of the path, and warn the user if it
  145     * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
  146     * TODO copy the message attachments over to temporary files, but that
  147     * TODO would require more changes so that the user still can recognize
  148     * TODO in `~@' etc. that its a rfc822 message attachment; see below */
  149    struct n_sigman sm;
  150    struct attachment *ap;
  151    char * volatile mnbuf;
  152    NYD_ENTER;
  153 
  154    n_UNUSED(linesize);
  155    mnbuf = NULL;
  156 
  157    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT){
  158    case 0:
  159       break;
  160    default:
  161       n_pstate_err_no = n_ERR_INTR;
  162       n_pstate_ex_no = 1;
  163       goto jleave;
  164    }
  165 
  166    /* If the above todo is worked, remove or outsource to attachment.c! */
  167    if(hp != NULL && (ap = hp->h_attach) != NULL) do
  168       if(ap->a_msgno){
  169          mnbuf = sstrdup(mailname);
  170          break;
  171       }
  172    while((ap = ap->a_flink) != NULL);
  173 
  174    n_go_command(n_GO_INPUT_CTX_COMPOSE, linebuf);
  175 
  176    n_sigman_cleanup_ping(&sm);
  177 jleave:
  178    if(mnbuf != NULL){
  179       if(strcmp(mnbuf, mailname))
  180          n_err(_("Mailbox changed: it is likely that existing "
  181             "rfc822 attachments became invalid!\n"));
  182       n_free(mnbuf);
  183    }
  184    NYD_LEAVE;
  185    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  186 }
  187 
  188 static si32_t
  189 a_coll_include_file(char const *name, bool_t indent, bool_t writestat){
  190    FILE *fbuf;
  191    char const *heredb, *indb;
  192    size_t linesize, heredl, indl, cnt, linelen;
  193    char *linebuf;
  194    si64_t lc, cc;
  195    si32_t rv;
  196    NYD_ENTER;
  197 
  198    rv = n_ERR_NONE;
  199    lc = cc = 0;
  200    linebuf = NULL; /* TODO line pool */
  201    linesize = 0;
  202    heredb = NULL;
  203    heredl = 0;
  204 
  205    /* The -M case is special */
  206    if(name == (char*)-1){
  207       fbuf = n_stdin;
  208       name = n_hy;
  209    }else if(name[0] == '-' &&
  210          (name[1] == '\0' || blankspacechar(name[1]))){
  211       fbuf = n_stdin;
  212       if(name[1] == '\0'){
  213          if(!(n_psonce & n_PSO_INTERACTIVE)){
  214             n_err(_("~< -: HERE-delimiter required in non-interactive mode\n"));
  215             rv = n_ERR_INVAL;
  216             goto jleave;
  217          }
  218       }else{
  219          for(heredb = &name[2]; *heredb != '\0' && blankspacechar(*heredb);
  220                ++heredb)
  221             ;
  222          if((heredl = strlen(heredb)) == 0){
  223 jdelim_empty:
  224             n_err(_("~< - HERE-delimiter: delimiter must not be empty\n"));
  225             rv = n_ERR_INVAL;
  226             goto jleave;
  227          }
  228 
  229          if(*heredb == '\''){
  230             for(indb = ++heredb; *indb != '\0' && *indb != '\''; ++indb)
  231                ;
  232             if(*indb == '\0'){
  233                n_err(_("~< - HERE-delimiter: missing trailing quote\n"));
  234                rv = n_ERR_INVAL;
  235                goto jleave;
  236             }else if(indb[1] != '\0'){
  237                n_err(_("~< - HERE-delimiter: trailing characters after "
  238                   "quote\n"));
  239                rv = n_ERR_INVAL;
  240                goto jleave;
  241             }
  242             if((heredl = PTR2SIZE(indb - heredb)) == 0)
  243                goto jdelim_empty;
  244             heredb = savestrbuf(heredb, heredl);
  245          }
  246       }
  247       name = n_hy;
  248    }else if((fbuf = Fopen(name, "r")) == NULL){
  249       n_perr(name, rv = n_err_no);
  250       goto jleave;
  251    }
  252 
  253    indl = indent ? strlen(indb = ok_vlook(indentprefix)) : 0;
  254 
  255    if(fbuf != n_stdin)
  256       cnt = fsize(fbuf);
  257    while(fgetline(&linebuf, &linesize, (fbuf == n_stdin ? NULL : &cnt),
  258          &linelen, fbuf, 0) != NULL){
  259       if(heredl > 0 && heredl == linelen - 1 &&
  260             !memcmp(heredb, linebuf, heredl)){
  261          heredb = NULL;
  262          break;
  263       }
  264 
  265       if(indl > 0){
  266          if(fwrite(indb, sizeof *indb, indl, _coll_fp) != indl){
  267             rv = n_err_no;
  268             goto jleave;
  269          }
  270          cc += indl;
  271       }
  272 
  273       if(fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen){
  274          rv = n_err_no;
  275          goto jleave;
  276       }
  277       cc += linelen;
  278       ++lc;
  279    }
  280    if(fflush(_coll_fp)){
  281       rv = n_err_no;
  282       goto jleave;
  283    }
  284 
  285    if(heredb != NULL)
  286       rv = n_ERR_NOTOBACCO;
  287 jleave:
  288    if(linebuf != NULL)
  289       n_free(linebuf);
  290    if(fbuf != NULL){
  291       if(fbuf != n_stdin)
  292          Fclose(fbuf);
  293       else if(heredl > 0)
  294          clearerr(n_stdin);
  295    }
  296 
  297    if(writestat)
  298       fprintf(n_stdout, "%s%s %" PRId64 "/%" PRId64 "\n",
  299          n_shexp_quote_cp(name, FAL0), (rv ? " " n_ERROR : n_empty), lc, cc);
  300    NYD_LEAVE;
  301    return rv;
  302 }
  303 
  304 static si32_t
  305 a_coll_insert_cmd(FILE *fp, char const *cmd){
  306    FILE *ibuf;
  307    si64_t lc, cc;
  308    si32_t rv;
  309    NYD_ENTER;
  310 
  311    rv = n_ERR_NONE;
  312    lc = cc = 0;
  313 
  314    if((ibuf = Popen(cmd, "r", ok_vlook(SHELL), NULL, 0)) != NULL){
  315       int c;
  316 
  317       while((c = getc(ibuf)) != EOF){ /* XXX bytewise, yuck! */
  318          if(putc(c, fp) == EOF){
  319             rv = n_err_no;
  320             break;
  321          }
  322          ++cc;
  323          if(c == '\n')
  324             ++lc;
  325       }
  326       if(!feof(ibuf) || ferror(ibuf)){
  327          if(rv == n_ERR_NONE)
  328             rv = n_ERR_IO;
  329       }
  330       if(!Pclose(ibuf, TRU1)){
  331          if(rv == n_ERR_NONE)
  332             rv = n_ERR_IO;
  333       }
  334    }else
  335       n_perr(cmd, rv = n_err_no);
  336 
  337    fprintf(n_stdout, "CMD%s %" PRId64 "/%" PRId64 "\n",
  338       (rv == n_ERR_NONE ? n_empty : " " n_ERROR), lc, cc);
  339    NYD_LEAVE;
  340    return rv;
  341 }
  342 
  343 static void
  344 print_collf(FILE *cf, struct header *hp)
  345 {
  346    char *lbuf;
  347    FILE *obuf;
  348    size_t cnt, linesize, linelen;
  349    NYD_ENTER;
  350 
  351    fflush_rewind(cf);
  352    cnt = (size_t)fsize(cf);
  353 
  354    if((obuf = Ftmp(NULL, "collfp", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
  355       n_perr(_("Can't create temporary file for `~p' command"), 0);
  356       goto jleave;
  357    }
  358 
  359    hold_all_sigs();
  360 
  361    fprintf(obuf, _("-------\nMessage contains:\n")); /* xxx SEARCH112 */
  362    n_puthead(TRU1, hp, obuf,
  363       (GIDENT | GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GNL | GFILES |
  364        GCOMMA), SEND_TODISP, CONV_NONE, NULL, NULL);
  365 
  366    lbuf = NULL;
  367    linesize = 0;
  368    while(fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
  369       prout(lbuf, linelen, obuf);
  370    if(lbuf != NULL)
  371       n_free(lbuf);
  372 
  373    if(hp->h_attach != NULL){
  374       fputs(_("-------\nAttachments:\n"), obuf);
  375       n_attachment_list_print(hp->h_attach, obuf);
  376    }
  377 
  378    rele_all_sigs();
  379 
  380    page_or_print(obuf, 0);
  381 
  382    Fclose(obuf);
  383 jleave:
  384    NYD_LEAVE;
  385 }
  386 
  387 static si32_t
  388 a_coll_write(char const *name, FILE *fp, int f)
  389 {
  390    FILE *of;
  391    int c;
  392    si64_t lc, cc;
  393    si32_t rv;
  394    NYD_ENTER;
  395 
  396    rv = n_ERR_NONE;
  397 
  398    if(f) {
  399       fprintf(n_stdout, "%s ", n_shexp_quote_cp(name, FAL0));
  400       fflush(n_stdout);
  401    }
  402 
  403    if ((of = Fopen(name, "a")) == NULL) {
  404       n_perr(name, rv = n_err_no);
  405       goto jerr;
  406    }
  407 
  408    lc = cc = 0;
  409    while ((c = getc(fp)) != EOF) {
  410       ++cc;
  411       if (c == '\n')
  412          ++lc;
  413       if (putc(c, of) == EOF) {
  414          n_perr(name, rv = n_err_no);
  415          goto jerr;
  416       }
  417    }
  418    fprintf(n_stdout, "%" PRId64 "/%" PRId64 "\n", lc, cc);
  419 
  420 jleave:
  421    if(of != NULL)
  422       Fclose(of);
  423    fflush(n_stdout);
  424    NYD_LEAVE;
  425    return rv;
  426 jerr:
  427    putc('-', n_stdout);
  428    putc('\n', n_stdout);
  429    goto jleave;
  430 }
  431 
  432 static bool_t
  433 a_coll_message_inject_head(FILE *fp){
  434    bool_t rv;
  435    char const *cp, *cp_obsolete;
  436    NYD2_ENTER;
  437 
  438    cp_obsolete = ok_vlook(NAIL_HEAD);
  439    if(cp_obsolete != NULL)
  440       n_OBSOLETE(_("please use *message-inject-head*, not *NAIL_HEAD*"));
  441 
  442    if(((cp = ok_vlook(message_inject_head)) != NULL ||
  443          (cp = cp_obsolete) != NULL) && !a_coll_putesc(cp, TRU1, fp))
  444       rv = FAL0;
  445    else
  446       rv = TRU1;
  447    NYD2_LEAVE;
  448    return rv;
  449 }
  450 
  451 static bool_t
  452 a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd){
  453    struct a_coll_fmt_ctx cfc;
  454    char const *cp;
  455    struct n_ignore const *quoteitp;
  456    enum sendaction action;
  457    bool_t rv;
  458    NYD_ENTER;
  459 
  460    rv = FAL0;
  461 
  462    if(isfwd || (cp = ok_vlook(quote)) != NULL){
  463       quoteitp = n_IGNORE_ALL;
  464       action = SEND_QUOTE;
  465 
  466       if(isfwd){
  467          char const *cp_v15compat;
  468 
  469          if((cp_v15compat = ok_vlook(fwdheading)) != NULL)
  470             n_OBSOLETE(_("please use *forward-inject-head* instead of "
  471                "*fwdheading*"));
  472          if((cp = ok_vlook(forward_inject_head)) == NULL &&
  473                (cp = cp_v15compat) == NULL)
  474             cp = n_FORWARD_INJECT_HEAD;
  475          quoteitp = n_IGNORE_FWD;
  476       }else{
  477          if(!strcmp(cp, "noheading")){
  478             cp = NULL;
  479          }else if(!strcmp(cp, "headers")){
  480             quoteitp = n_IGNORE_TYPE;
  481             cp = NULL;
  482          }else if(!strcmp(cp, "allheaders")){
  483             quoteitp = NULL;
  484             action = SEND_QUOTE_ALL;
  485             cp = NULL;
  486          }else if((cp = ok_vlook(quote_inject_head)) == NULL)
  487             cp = n_QUOTE_INJECT_HEAD;
  488       }
  489       /* We we pass through our formatter? */
  490       if((cfc.cfc_fmt = cp) != NULL){
  491          /* TODO In v15 [-textual_-]sender_info() should only create a list
  492           * TODO of matching header objects, and the formatter should simply
  493           * TODO iterate over this list and call OBJ->to_ui_str(FLAGS) or so.
  494           * TODO For now fully initialize this thing once (grrrr!!) */
  495          cfc.cfc_fp = fp;
  496          cfc.cfc_mp = mp;
  497          n_header_textual_sender_info(cfc.cfc_mp = mp, &cfc.cfc_cumul,
  498             &cfc.cfc_addr, &cfc.cfc_real, &cfc.cfc_full, NULL);
  499          cfc.cfc_date = n_header_textual_date_info(mp, NULL);
  500          /* C99 */{
  501             struct name *np;
  502 
  503             if((cp = hfield1("message-id", mp)) != NULL &&
  504                   (np = lextract(cp, GREF)) != NULL)
  505                cp = np->n_name;
  506             else
  507                cp = NULL;
  508          }
  509          cfc.cfc_msgid = cp;
  510 
  511          if(!a_coll__fmt_inj(&cfc) || fflush(fp))
  512             goto jleave;
  513       }
  514 
  515       if(sendmp(mp, fp, quoteitp, (isfwd ? NULL : ok_vlook(indentprefix)),
  516             action, NULL) < 0)
  517          goto jleave;
  518 
  519       if(isfwd){
  520          if((cp = ok_vlook(forward_inject_tail)) == NULL)
  521              cp = n_FORWARD_INJECT_TAIL;
  522       }else if(cp != NULL && (cp = ok_vlook(quote_inject_tail)) == NULL)
  523           cp = n_QUOTE_INJECT_TAIL;
  524       if((cfc.cfc_fmt = cp) != NULL && (!a_coll__fmt_inj(&cfc) || fflush(fp)))
  525          goto jleave;
  526    }
  527 
  528    rv = TRU1;
  529 jleave:
  530    NYD_LEAVE;
  531    return rv;
  532 }
  533 
  534 static bool_t
  535 a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp){
  536    struct quoteflt qf;
  537    struct n_string s_b, *sp;
  538    char c;
  539    char const *fmt;
  540    NYD_ENTER;
  541 
  542    if((fmt = cfcp->cfc_fmt) == NULL || *fmt == '\0')
  543       goto jleave;
  544 
  545    sp = n_string_book(n_string_creat_auto(&s_b), 127);
  546 
  547    while((c = *fmt++) != '\0'){
  548       if(c != '%' || (c = *fmt++) == '%'){
  549 jwrite_char:
  550          sp = n_string_push_c(sp, c);
  551       }else switch(c){
  552       case 'a':
  553          sp = n_string_push_cp(sp, cfcp->cfc_addr);
  554          break;
  555       case 'd':
  556          sp = n_string_push_cp(sp, cfcp->cfc_date);
  557          break;
  558       case 'f':
  559          sp = n_string_push_cp(sp, cfcp->cfc_full);
  560          break;
  561       case 'i':
  562          if(cfcp->cfc_msgid != NULL)
  563             sp = n_string_push_cp(sp, cfcp->cfc_msgid);
  564          break;
  565       case 'n':
  566          sp = n_string_push_cp(sp, cfcp->cfc_cumul);
  567          break;
  568       case 'r':
  569          sp = n_string_push_cp(sp, cfcp->cfc_real);
  570          break;
  571       case '\0':
  572          --fmt;
  573          c = '%';
  574          goto jwrite_char;
  575       default:
  576          n_err(_("*{forward,quote}-inject-{head,tail}*: "
  577             "unknown format: %c (in: %s)\n"),
  578             c, n_shexp_quote_cp(cfcp->cfc_fmt, FAL0));
  579          goto jwrite_char;
  580       }
  581    }
  582 
  583    quoteflt_init(&qf, NULL, FAL0);
  584    quoteflt_reset(&qf, cfcp->cfc_fp);
  585    if(quoteflt_push(&qf, sp->s_dat, sp->s_len) < 0 || quoteflt_flush(&qf) < 0)
  586       cfcp = NULL;
  587    quoteflt_destroy(&qf);
  588 
  589    /*n_string_gut(sp);*/
  590 jleave:
  591    NYD_LEAVE;
  592    return (cfcp != NULL);
  593 }
  594 
  595 static bool_t
  596 a_coll_makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err,
  597    bool_t do_delayed_due_t)
  598 {
  599    FILE *nf;
  600    int c;
  601    bool_t rv;
  602    NYD_ENTER;
  603 
  604    rv = FAL0;
  605 
  606    if ((nf = Ftmp(NULL, "colhead", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
  607       n_perr(_("temporary mail edit file"), 0);
  608       goto jleave;
  609    }
  610 
  611    n_header_extract(((do_delayed_due_t
  612             ? n_HEADER_EXTRACT_FULL | n_HEADER_EXTRACT_PREFILL_RECEIVERS
  613             : n_HEADER_EXTRACT_EXTENDED) |
  614          n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS), fp, hp, checkaddr_err);
  615    if (checkaddr_err != NULL && *checkaddr_err != 0)
  616       goto jleave;
  617 
  618    /* In template mode some things have been delayed until the template has
  619     * been read */
  620    if(do_delayed_due_t){
  621       char const *cp;
  622 
  623       if((cp = ok_vlook(on_compose_enter)) != NULL){
  624          setup_from_and_sender(hp);
  625          temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
  626             hp);
  627       }
  628 
  629       if(!a_coll_message_inject_head(nf))
  630          goto jleave;
  631    }
  632 
  633    while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
  634       putc(c, nf);
  635 
  636    if (fp != _coll_fp)
  637       Fclose(_coll_fp);
  638    Fclose(fp);
  639    _coll_fp = nf;
  640    nf = NULL;
  641 
  642    if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL)
  643       goto jleave;
  644    rv = TRU1;
  645 jleave:
  646    if(nf != NULL)
  647       Fclose(nf);
  648    NYD_LEAVE;
  649    return rv;
  650 }
  651 
  652 static si32_t
  653 a_coll_edit(int c, struct header *hp, char const *pipecmd) /* TODO errret */
  654 {
  655    struct n_sigman sm;
  656    FILE *nf;
  657    sighandler_type volatile sigint;
  658    struct name *saved_in_reply_to;
  659    bool_t saved_filrec;
  660    si32_t volatile rv;
  661    NYD_ENTER;
  662 
  663    rv = n_ERR_NONE;
  664    n_UNINIT(sigint, SIG_ERR);
  665    saved_filrec = ok_blook(add_file_recipients);
  666 
  667    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
  668    case 0:
  669       sigint = safe_signal(SIGINT, SIG_IGN);
  670       break;
  671    default:
  672       rv = n_ERR_INTR;
  673       goto jleave;
  674    }
  675 
  676    if(!saved_filrec)
  677       ok_bset(add_file_recipients);
  678 
  679    saved_in_reply_to = NULL;
  680    if(hp != NULL){
  681       struct name *np;
  682 
  683       if((np = hp->h_in_reply_to) == NULL)
  684          hp->h_in_reply_to = np = n_header_setup_in_reply_to(hp);
  685       if(np != NULL)
  686          saved_in_reply_to = ndup(np, np->n_type);
  687    }
  688 
  689    rewind(_coll_fp);
  690    nf = n_run_editor(_coll_fp, (off_t)-1, c, FAL0, hp, NULL, SEND_MBOX, sigint,
  691          pipecmd);
  692    if(nf != NULL){
  693       if(hp != NULL){
  694          /* Overtaking of nf->_coll_fp is done by a_coll_makeheader()! */
  695          if(!a_coll_makeheader(nf, hp, NULL, FAL0))
  696             rv = n_ERR_INVAL;
  697          /* Break the thread if In-Reply-To: has been modified */
  698          if(hp->h_in_reply_to == NULL || (saved_in_reply_to != NULL &&
  699                asccasecmp(hp->h_in_reply_to->n_fullname,
  700                   saved_in_reply_to->n_fullname)))
  701                hp->h_ref = NULL;
  702       }else{
  703          fseek(nf, 0L, SEEK_END);
  704          Fclose(_coll_fp);
  705          _coll_fp = nf;
  706       }
  707    }else
  708       rv = n_ERR_CHILD;
  709 
  710    n_sigman_cleanup_ping(&sm);
  711 jleave:
  712    if(!saved_filrec)
  713       ok_bclear(add_file_recipients);
  714    safe_signal(SIGINT, sigint);
  715    NYD_LEAVE;
  716    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  717    return rv;
  718 }
  719 
  720 static si32_t
  721 a_coll_pipe(char const *cmd)
  722 {
  723    int ws;
  724    FILE *nf;
  725    sighandler_type sigint;
  726    si32_t rv;
  727    NYD_ENTER;
  728 
  729    rv = n_ERR_NONE;
  730    sigint = safe_signal(SIGINT, SIG_IGN);
  731 
  732    if ((nf = Ftmp(NULL, "colpipe", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
  733 jperr:
  734       n_perr(_("temporary mail edit file"), rv = n_err_no);
  735       goto jout;
  736    }
  737 
  738    /* stdin = current message.  stdout = new message */
  739    if(fflush(_coll_fp) == EOF)
  740       goto jperr;
  741    rewind(_coll_fp);
  742    if (n_child_run(ok_vlook(SHELL), 0, fileno(_coll_fp), fileno(nf), "-c",
  743          cmd, NULL, NULL, &ws) < 0 || WEXITSTATUS(ws) != 0) {
  744       Fclose(nf);
  745       rv = n_ERR_CHILD;
  746       goto jout;
  747    }
  748 
  749    if (fsize(nf) == 0) {
  750       n_err(_("No bytes from %s !?\n"), n_shexp_quote_cp(cmd, FAL0));
  751       Fclose(nf);
  752       rv = n_ERR_NODATA;
  753       goto jout;
  754    }
  755 
  756    /* Take new files */
  757    fseek(nf, 0L, SEEK_END);
  758    Fclose(_coll_fp);
  759    _coll_fp = nf;
  760 jout:
  761    safe_signal(SIGINT, sigint);
  762    NYD_LEAVE;
  763    return rv;
  764 }
  765 
  766 static si32_t
  767 a_coll_forward(char const *ms, FILE *fp, int f)
  768 {
  769    int *msgvec, rv = 0;
  770    struct n_ignore const *itp;
  771    char const *tabst;
  772    enum sendaction action;
  773    NYD_ENTER;
  774 
  775    if ((rv = n_getmsglist(ms, n_msgvec, 0, NULL)) < 0) {
  776       rv = n_ERR_NOENT; /* XXX not really, should be handled there! */
  777       goto jleave;
  778    }
  779    if (rv == 0) {
  780       *n_msgvec = first(0, MMNORM);
  781       if (*n_msgvec == 0) {
  782          n_err(_("No appropriate messages\n"));
  783          rv = n_ERR_NOENT;
  784          goto jleave;
  785       }
  786       rv = 1;
  787    }
  788    msgvec = n_autorec_calloc(rv +1, sizeof *msgvec);
  789    while(rv-- > 0)
  790       msgvec[rv] = n_msgvec[rv];
  791    rv = 0;
  792 
  793    if (f == 'f' || f == 'F' || f == 'u')
  794       tabst = NULL;
  795    else
  796       tabst = ok_vlook(indentprefix);
  797    if (f == 'u' || f == 'U')
  798       itp = n_IGNORE_ALL;
  799    else
  800       itp = upperchar(f) ? NULL : n_IGNORE_TYPE;
  801    action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
  802 
  803    fprintf(n_stdout, A_("Interpolating:"));
  804    srelax_hold();
  805    for(; *msgvec != 0; ++msgvec){
  806       struct message *mp;
  807 
  808       mp = &message[*msgvec - 1];
  809       touch(mp);
  810 
  811       fprintf(n_stdout, " %d", *msgvec);
  812       fflush(n_stdout);
  813       if(f == 'Q'){
  814          if(!a_coll_quote_message(fp, mp, FAL0)){
  815             rv = n_ERR_IO;
  816             break;
  817          }
  818       }else if(sendmp(mp, fp, itp, tabst, action, NULL) < 0){
  819          n_perr(_("forward: temporary mail file"), 0);
  820          rv = n_ERR_IO;
  821          break;
  822       }
  823       srelax();
  824    }
  825    srelax_rele();
  826    fprintf(n_stdout, "\n");
  827 jleave:
  828    NYD_LEAVE;
  829    return rv;
  830 }
  831 
  832 static void
  833 _collint(int s)
  834 {
  835    NYD_X; /* Signal handler */
  836 
  837    /* the control flow is subtle, because we can be called from ~q */
  838    if (_coll_hadintr == 0) {
  839       if (ok_blook(ignore)) {
  840          fputs("@\n", n_stdout);
  841          fflush(n_stdout);
  842          clearerr(n_stdin);
  843       } else
  844          _coll_hadintr = 1;
  845       siglongjmp(_coll_jmp, 1);
  846    }
  847    n_exit_status |= n_EXIT_SEND_ERROR;
  848    if (s != 0)
  849       savedeadletter(_coll_fp, TRU1);
  850    /* Aborting message, no need to fflush() .. */
  851    siglongjmp(_coll_abort, 1);
  852 }
  853 
  854 static void
  855 collhup(int s)
  856 {
  857    NYD_X; /* Signal handler */
  858    n_UNUSED(s);
  859 
  860    savedeadletter(_coll_fp, TRU1);
  861    /* Let's pretend nobody else wants to clean up, a true statement at
  862     * this time */
  863    exit(n_EXIT_ERR);
  864 }
  865 
  866 static bool_t
  867 a_coll_putesc(char const *s, bool_t addnl, FILE *stream){
  868    char c1, c2;
  869    bool_t isposix;
  870    NYD2_ENTER;
  871 
  872    isposix = ok_blook(posix);
  873 
  874    while((c1 = *s++) != '\0'){
  875       if(c1 == '\\' && ((c2 = *s) == 't' || c2 == 'n')){
  876          if(!isposix){
  877             isposix = TRU1; /* TODO v15 OBSOLETE! */
  878             n_err(_("Compose mode warning: expanding \\t or \\n in variable "
  879                   "without *posix*!"
  880                "\n  Support remains only for ~A,~a,~I,~i in *posix* mode!\n"));
  881          }
  882          ++s;
  883          c1 = (c2 == 't') ? '\t' : '\n';
  884       }
  885 
  886       if(putc(c1, stream) == EOF)
  887          goto jleave;
  888    }
  889 
  890    if(addnl && putc('\n', stream) == EOF)
  891       goto jleave;
  892 
  893 jleave:
  894    NYD2_LEAVE;
  895    return (c1 == '\0');
  896 }
  897 
  898 static int
  899 a_coll_ocs__mac(void){
  900    /* Executes in a fork(2)ed child  TODO if remains, global MASKs for those! */
  901    setvbuf(n_stdin, NULL, _IOLBF, 0);
  902    setvbuf(n_stdout, NULL, _IOLBF, 0);
  903    n_psonce &= ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT);
  904    n_pstate |= n_PS_COMPOSE_FORKHOOK;
  905    n_readctl_read_overlay = NULL; /* TODO need OnForkEvent! See c_readctl() */
  906    n_digmsg_read_overlay = NULL; /* TODO need OnForkEvent! See c_digmsg() */
  907    if(n_poption & n_PO_D_VV){
  908       char buf[128];
  909 
  910       snprintf(buf, sizeof buf, "[%d]%s", getpid(), ok_vlook(log_prefix));
  911       ok_vset(log_prefix, buf);
  912    }
  913    /* TODO If that uses `!' it will effectively SIG_IGN SIGINT, ...and such */
  914    temporary_compose_mode_hook_call(a_coll_ocs__macname, NULL, NULL);
  915    return 0;
  916 }
  917 
  918 static void
  919 a_coll_ocs__finalize(void *vp){
  920    /* Note we use this for destruction upon setup errors, thus */
  921    sighandler_type opipe;
  922    sighandler_type oint;
  923    struct a_coll_ocs_arg **coapp, *coap;
  924    NYD2_ENTER;
  925 
  926    temporary_compose_mode_hook_call((char*)-1, NULL, NULL);
  927 
  928    coap = *(coapp = vp);
  929    *coapp = (struct a_coll_ocs_arg*)-1;
  930 
  931    if(coap->coa_stdin != NULL)
  932       Fclose(coap->coa_stdin);
  933    else if(coap->coa_pipe[0] != -1)
  934       close(coap->coa_pipe[0]);
  935 
  936    if(coap->coa_stdout != NULL && !Pclose(coap->coa_stdout, TRU1))
  937       *coap->coa_senderr = 111;
  938    if(coap->coa_pipe[1] != -1)
  939       close(coap->coa_pipe[1]);
  940 
  941    opipe = coap->coa_opipe;
  942    oint = coap->coa_oint;
  943 
  944    n_lofi_free(coap);
  945 
  946    hold_all_sigs();
  947    safe_signal(SIGPIPE, opipe);
  948    safe_signal(SIGINT, oint);
  949    rele_all_sigs();
  950    NYD2_LEAVE;
  951 }
  952 
  953 FL void
  954 n_temporary_compose_hook_varset(void *arg){ /* TODO v15: drop */
  955    struct header *hp;
  956    char const *val;
  957    NYD2_ENTER;
  958 
  959    hp = arg;
  960 
  961    if((val = hp->h_subject) == NULL)
  962       val = n_empty;
  963    ok_vset(mailx_subject, val);
  964    if((val = detract(hp->h_from, GNAMEONLY)) == NULL)
  965       val = n_empty;
  966    ok_vset(mailx_from, val);
  967    if((val = detract(hp->h_sender, GNAMEONLY)) == NULL)
  968       val = n_empty;
  969    ok_vset(mailx_sender, val);
  970    if((val = detract(hp->h_to, GNAMEONLY)) == NULL)
  971       val = n_empty;
  972    ok_vset(mailx_to, val);
  973    if((val = detract(hp->h_cc, GNAMEONLY)) == NULL)
  974       val = n_empty;
  975    ok_vset(mailx_cc, val);
  976    if((val = detract(hp->h_bcc, GNAMEONLY)) == NULL)
  977       val = n_empty;
  978    ok_vset(mailx_bcc, val);
  979 
  980    if((val = hp->h_mailx_command) == NULL)
  981       val = n_empty;
  982    ok_vset(mailx_command, val);
  983 
  984    if((val = detract(hp->h_mailx_raw_to, GNAMEONLY)) == NULL)
  985       val = n_empty;
  986    ok_vset(mailx_raw_to, val);
  987    if((val = detract(hp->h_mailx_raw_cc, GNAMEONLY)) == NULL)
  988       val = n_empty;
  989    ok_vset(mailx_raw_cc, val);
  990    if((val = detract(hp->h_mailx_raw_bcc, GNAMEONLY)) == NULL)
  991       val = n_empty;
  992    ok_vset(mailx_raw_bcc, val);
  993 
  994    if((val = detract(hp->h_mailx_orig_from, GNAMEONLY)) == NULL)
  995       val = n_empty;
  996    ok_vset(mailx_orig_from, val);
  997    if((val = detract(hp->h_mailx_orig_to, GNAMEONLY)) == NULL)
  998       val = n_empty;
  999    ok_vset(mailx_orig_to, val);
 1000    if((val = detract(hp->h_mailx_orig_cc, GNAMEONLY)) == NULL)
 1001       val = n_empty;
 1002    ok_vset(mailx_orig_cc, val);
 1003    if((val = detract(hp->h_mailx_orig_bcc, GNAMEONLY)) == NULL)
 1004       val = n_empty;
 1005    ok_vset(mailx_orig_bcc, val);
 1006    NYD2_LEAVE;
 1007 }
 1008 
 1009 FL FILE *
 1010 n_collect(enum n_mailsend_flags msf, struct header *hp, struct message *mp,
 1011    char const *quotefile, si8_t *checkaddr_err)
 1012 {
 1013    struct n_dig_msg_ctx dmc;
 1014    struct n_string s, * volatile sp;
 1015    struct a_coll_ocs_arg *coap;
 1016    int c;
 1017    int volatile t, eofcnt, getfields;
 1018    char volatile escape;
 1019    enum{
 1020       a_NONE,
 1021       a_ERREXIT = 1u<<0,
 1022       a_IGNERR = 1u<<1,
 1023       a_COAP_NOSIGTERM = 1u<<8
 1024 #define a_HARDERR() ((flags & (a_ERREXIT | a_IGNERR)) == a_ERREXIT)
 1025    } volatile flags;
 1026    char *linebuf;
 1027    char const *cp, *cp_base, * volatile coapm, * volatile ifs_saved;
 1028    size_t i, linesize; /* TODO line pool */
 1029    long cnt;
 1030    sigset_t oset, nset;
 1031    FILE * volatile sigfp;
 1032    NYD_ENTER;
 1033 
 1034    n_DIG_MSG_COMPOSE_CREATE(&dmc, hp);
 1035    _coll_fp = NULL;
 1036 
 1037    sigfp = NULL;
 1038    linesize = 0;
 1039    linebuf = NULL;
 1040    flags = a_NONE;
 1041    eofcnt = 0;
 1042    ifs_saved = coapm = NULL;
 1043    coap = NULL;
 1044    sp = NULL;
 1045 
 1046    /* Start catching signals from here, but we still die on interrupts
 1047     * until we're in the main loop */
 1048    sigfillset(&nset);
 1049    sigprocmask(SIG_BLOCK, &nset, &oset);
 1050    if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
 1051       safe_signal(SIGINT, &_collint);
 1052    if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
 1053       safe_signal(SIGHUP, collhup);
 1054    if (sigsetjmp(_coll_abort, 1))
 1055       goto jerr;
 1056    if (sigsetjmp(_coll_jmp, 1))
 1057       goto jerr;
 1058    n_pstate |= n_PS_COMPOSE_MODE;
 1059    sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
 1060 
 1061    if ((_coll_fp = Ftmp(NULL, "collect", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
 1062          NULL) {
 1063       n_perr(_("collect: temporary mail file"), 0);
 1064       goto jerr;
 1065    }
 1066 
 1067    /* If we are going to prompt for a subject, refrain from printing a newline
 1068     * after the headers (since some people mind) */
 1069    getfields = 0;
 1070    if(!(n_poption & n_PO_t_FLAG)){
 1071       t = GTO | GSUBJECT | GCC | GNL;
 1072       if(ok_blook(fullnames))
 1073          t |= GCOMMA;
 1074 
 1075       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
 1076          if(hp->h_subject == NULL && ok_blook(asksub)/* *ask* auto warped! */)
 1077             t &= ~GNL, getfields |= GSUBJECT;
 1078 
 1079          if(hp->h_to == NULL)
 1080             t &= ~GNL, getfields |= GTO;
 1081 
 1082          if(!ok_blook(bsdcompat) && !ok_blook(askatend)){
 1083             if(ok_blook(askbcc))
 1084                t &= ~GNL, getfields |= GBCC;
 1085             if(ok_blook(askcc))
 1086                t &= ~GNL, getfields |= GCC;
 1087          }
 1088       }
 1089    }else{
 1090       n_UNINIT(t, 0);
 1091    }
 1092 
 1093    _coll_hadintr = 0;
 1094 
 1095    if (!sigsetjmp(_coll_jmp, 1)) {
 1096       /* Ask for some headers first, as necessary */
 1097       if (getfields)
 1098          grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, getfields, 1);
 1099 
 1100       /* Execute compose-enter; delayed for -t mode */
 1101       if(!(n_poption & n_PO_t_FLAG) &&
 1102             (cp = ok_vlook(on_compose_enter)) != NULL){
 1103          setup_from_and_sender(hp);
 1104          temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
 1105             hp);
 1106       }
 1107 
 1108       /* TODO Mm: nope since it may require turning this into a multipart one */
 1109       if(!(n_poption & (n_PO_Mm_FLAG | n_PO_t_FLAG))){
 1110          if(!a_coll_message_inject_head(_coll_fp))
 1111             goto jerr;
 1112 
 1113          /* Quote an original message */
 1114          if(mp != NULL && !a_coll_quote_message(_coll_fp, mp,
 1115                ((msf & n_MAILSEND_IS_FWD) != 0)))
 1116             goto jerr;
 1117       }
 1118 
 1119       if (quotefile != NULL) {
 1120          if((n_pstate_err_no = a_coll_include_file(quotefile, FAL0, FAL0)
 1121                ) != n_ERR_NONE)
 1122             goto jerr;
 1123       }
 1124 
 1125       if(n_psonce & n_PSO_INTERACTIVE){
 1126          if(!(n_pstate & n_PS_SOURCING)){
 1127             sp = n_string_creat_auto(&s);
 1128             sp = n_string_reserve(sp, 80);
 1129          }
 1130 
 1131          if(!(n_poption & n_PO_Mm_FLAG) && !(n_pstate & n_PS_ROBOT)){
 1132             /* Print what we have sofar also on the terminal (if useful) */
 1133             if((cp = ok_vlook(editalong)) == NULL){
 1134                if(msf & n_MAILSEND_HEADERS_PRINT)
 1135                   n_puthead(TRU1, hp, n_stdout, t, SEND_TODISP, CONV_NONE,
 1136                      NULL, NULL);
 1137 
 1138                rewind(_coll_fp);
 1139                while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
 1140                   putc(c, n_stdout);
 1141                if (fseek(_coll_fp, 0, SEEK_END))
 1142                   goto jerr;
 1143             }else{
 1144                if(a_coll_edit(((*cp == 'v') ? 'v' : 'e'), hp, NULL
 1145                      ) != n_ERR_NONE)
 1146                   goto jerr;
 1147                /* Print msg mandated by the Mail Reference Manual */
 1148 jcont:
 1149                if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT))
 1150                   fputs(_("(continue)\n"), n_stdout);
 1151             }
 1152             fflush(n_stdout);
 1153          }
 1154       }
 1155    } else {
 1156       /* Come here for printing the after-signal message.  Duplicate messages
 1157        * won't be printed because the write is aborted if we get a SIGTTOU */
 1158       if(_coll_hadintr && (n_psonce & n_PSO_INTERACTIVE) &&
 1159             !(n_pstate & n_PS_ROBOT))
 1160          n_err(_("\n(Interrupt -- one more to kill letter)\n"));
 1161    }
 1162 
 1163    /* If not under shell hook control */
 1164    if(coap == NULL){
 1165       /* We're done with -M or -m TODO because: we are too stupid yet, above */
 1166       if(n_poption & n_PO_Mm_FLAG)
 1167          goto jout;
 1168       /* No command escapes, interrupts not expected.  Simply copy STDIN */
 1169       if(!(n_psonce & n_PSO_INTERACTIVE) &&
 1170             !(n_poption & (n_PO_t_FLAG | n_PO_TILDE_FLAG))){
 1171          linebuf = n_realloc(linebuf, linesize = LINESIZE);
 1172          while ((i = fread(linebuf, sizeof *linebuf, linesize, n_stdin)) > 0) {
 1173             if (i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
 1174                goto jerr;
 1175          }
 1176          goto jout;
 1177       }
 1178    }
 1179 
 1180    /* The interactive collect loop */
 1181    if(coap == NULL)
 1182       escape = *ok_vlook(escape);
 1183    flags = ok_blook(errexit) ? a_ERREXIT : a_NONE;
 1184 
 1185    for(;;){
 1186       enum {a_HIST_NONE, a_HIST_ADD = 1u<<0, a_HIST_GABBY = 1u<<1} hist;
 1187 
 1188       /* C99 */{
 1189          enum n_go_input_flags gif;
 1190          bool_t histadd;
 1191 
 1192          /* TODO optimize: no need to evaluate that anew for each loop tick! */
 1193          gif = n_GO_INPUT_CTX_COMPOSE;
 1194          histadd = (sp != NULL);
 1195          if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_TILDE_FLAG)){
 1196             if(!(n_poption & n_PO_t_FLAG) || (n_psonce & n_PSO_t_FLAG_DONE))
 1197                gif |= n_GO_INPUT_NL_ESC;
 1198          }
 1199          cnt = n_go_input(gif, n_empty, &linebuf, &linesize, NULL, &histadd);
 1200          hist = histadd ? a_HIST_ADD | a_HIST_GABBY : a_HIST_NONE;
 1201       }
 1202 
 1203       if(cnt < 0){
 1204          if(coap != NULL)
 1205             break;
 1206          if((n_poption & n_PO_t_FLAG) && !(n_psonce & n_PSO_t_FLAG_DONE)){
 1207             fflush_rewind(_coll_fp);
 1208             n_psonce |= n_PSO_t_FLAG_DONE;
 1209             if(!a_coll_makeheader(_coll_fp, hp, checkaddr_err, TRU1))
 1210                goto jerr;
 1211             continue;
 1212          }else if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 1213                ok_blook(ignoreeof) && ++eofcnt < 4){
 1214             fprintf(n_stdout,
 1215                _("*ignoreeof* set, use `~.' to terminate letter\n"));
 1216             n_go_input_clearerr();
 1217             continue;
 1218          }
 1219          break;
 1220       }
 1221 
 1222       _coll_hadintr = 0;
 1223 
 1224       cp = linebuf;
 1225       if(cnt == 0)
 1226          goto jputnl;
 1227       else if(coap == NULL){
 1228          if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_TILDE_FLAG))
 1229             goto jputline;
 1230          else if(cp[0] == '.'){
 1231             if(cnt == 1 && (ok_blook(dot) ||
 1232                   (ok_blook(posix) && ok_blook(ignoreeof))))
 1233                break;
 1234          }
 1235       }
 1236       if(cp[0] != escape){
 1237 jputline:
 1238          if(fwrite(cp, sizeof *cp, cnt, _coll_fp) != (size_t)cnt)
 1239             goto jerr;
 1240          /* TODO n_PS_READLINE_NL is a terrible hack to ensure that _in_all_-
 1241           * TODO _code_paths_ a file without trailing newline isn't modified
 1242           * TODO to contain one; the "saw-newline" needs to be part of an
 1243           * TODO I/O input machinery object */
 1244 jputnl:
 1245          if(n_pstate & n_PS_READLINE_NL){
 1246             if(putc('\n', _coll_fp) == EOF)
 1247                goto jerr;
 1248          }
 1249          continue;
 1250       }
 1251 
 1252       c = *(cp_base = ++cp);
 1253       if(--cnt == 0)
 1254          goto jearg;
 1255 
 1256       /* Avoid history entry? */
 1257       while(spacechar(c)){
 1258          hist = a_HIST_NONE;
 1259          c = *(cp_base = ++cp);
 1260          if(--cnt == 0)
 1261             goto jearg;
 1262       }
 1263 
 1264       /* It may just be an escaped escaped character, do that quick */
 1265       if(c == escape)
 1266          goto jputline;
 1267 
 1268       /* Avoid hard *errexit*? */
 1269       flags &= ~a_IGNERR;
 1270       if(c == '-'){
 1271          flags ^= a_IGNERR;
 1272          c = *++cp;
 1273          if(--cnt == 0)
 1274             goto jearg;
 1275       }
 1276 
 1277       /* Trim input, also to gain a somewhat normalized history entry */
 1278       ++cp;
 1279       if(--cnt > 0){
 1280          struct str x;
 1281 
 1282          x.s = n_UNCONST(cp);
 1283          x.l = (size_t)cnt;
 1284          n_str_trim_ifs(&x, TRU1);
 1285          x.s[x.l] = '\0';
 1286          cp = x.s;
 1287          cnt = (int)/*XXX*/x.l;
 1288       }
 1289 
 1290       if(hist != a_HIST_NONE){
 1291          sp = n_string_assign_c(sp, escape);
 1292          if(flags & a_IGNERR)
 1293             sp = n_string_push_c(sp, '-');
 1294          sp = n_string_push_c(sp, c);
 1295          if(cnt > 0){
 1296             sp = n_string_push_c(sp, ' ');
 1297             sp = n_string_push_buf(sp, cp, cnt);
 1298          }
 1299       }
 1300 
 1301       /* Switch over all command escapes */
 1302       switch(c){
 1303       default:
 1304          if(1){
 1305             char buf[sizeof(n_UNIREPL)];
 1306 
 1307             if(asciichar(c))
 1308                buf[0] = c, buf[1] = '\0';
 1309             else if(n_psonce & n_PSO_UNICODE)
 1310                memcpy(buf, n_unirepl, sizeof n_unirepl);
 1311             else
 1312                buf[0] = '?', buf[1] = '\0';
 1313             n_err(_("Unknown command escape: `%c%s'\n"), escape, buf);
 1314          }else
 1315 jearg:
 1316             n_err(_("Invalid command escape usage: %s\n"),
 1317                n_shexp_quote_cp(linebuf, FAL0));
 1318          if(a_HARDERR())
 1319             goto jerr;
 1320          n_pstate_err_no = n_ERR_INVAL;
 1321          n_pstate_ex_no = 1;
 1322          continue;
 1323       case '!':
 1324          /* Shell escape, send the balance of line to sh -c */
 1325          if(cnt == 0 || coap != NULL)
 1326             goto jearg;
 1327          else{
 1328             char const *argv[2];
 1329 
 1330             argv[0] = cp;
 1331             argv[1] = NULL;
 1332             n_pstate_ex_no = c_shell(argv); /* TODO history norm.; errexit? */
 1333          }
 1334          goto jhistcont;
 1335       case '.':
 1336          /* Simulate end of file on input */
 1337          if(cnt != 0 || coap != NULL)
 1338             goto jearg;
 1339          goto jout; /* TODO does not enter history, thus */
 1340       case ':':
 1341       case '_':
 1342          /* Escape to command mode, but be nice! *//* TODO command expansion
 1343           * TODO should be handled here so that we have unique history! */
 1344          if(cnt == 0)
 1345             goto jearg;
 1346          _execute_command(hp, cp, cnt);
 1347          if(ok_blook(errexit))
 1348             flags |= a_ERREXIT;
 1349          else
 1350             flags &= ~a_ERREXIT;
 1351          if(n_pstate_ex_no != 0 && a_HARDERR())
 1352             goto jerr;
 1353          if(coap == NULL)
 1354             escape = *ok_vlook(escape);
 1355          hist &= ~a_HIST_GABBY;
 1356          break;
 1357       /* case '<': <> 'd' */
 1358       case '?':
 1359 #ifdef HAVE_UISTRINGS
 1360          fputs(_(
 1361 "COMMAND ESCAPES (to be placed after a newline) excerpt:\n"
 1362 "~.            Commit and send message\n"
 1363 "~: <command>  Execute an internal command\n"
 1364 "~< <file>     Insert <file> (\"~<! <command>\": insert shell command)\n"
 1365 "~@ [<files>]  Edit [Add] attachments (file[=in-charset[#out-charset]])\n"
 1366 "~c <users>    Add users to Cc: list (`~b': to Bcc:)\n"
 1367 "~e, ~v        Edit message via $EDITOR / $VISUAL\n"
 1368             ), n_stdout);
 1369          fputs(_(
 1370 "~F <msglist>  Read in with headers, do not *indentprefix* lines\n"
 1371 "~f <msglist>  Like `~F', but honour `headerpick' configuration\n"
 1372 "~H            Edit From:, Reply-To: and Sender:\n"
 1373 "~h            Prompt for Subject:, To:, Cc: and Bcc:\n"
 1374 "~i <variable> Insert a value and a newline (`~I': insert value)\n"
 1375 "~M <msglist>  Read in with headers, *indentprefix* (`~m': use `headerpick')\n"
 1376 "~p            Show current message compose buffer\n"
 1377 "~Q <msglist>  Read in using normal *quote* algorithm\n"
 1378             ), n_stdout);
 1379          fputs(_(
 1380 "~r <file>     Insert <file> (`~R': *indentprefix* lines)\n"
 1381 "              <file> may also be <- [HERE-DELIMITER]>\n"
 1382 "~s <subject>  Set Subject:\n"
 1383 "~t <users>    Add users to To: list\n"
 1384 "~u <msglist>  Read in without headers (`~U': *indentprefix* lines)\n"
 1385 "~w <file>     Write message onto file\n"
 1386 "~x            Abort composition, discard message (`~q': save in $DEAD)\n"
 1387 "~| <command>  Pipe message through shell filter (`~||': with headers)\n"
 1388             ), n_stdout);
 1389 #endif /* HAVE_UISTRINGS */
 1390          if(cnt != 0)
 1391             goto jearg;
 1392          n_pstate_err_no = n_ERR_NONE;
 1393          n_pstate_ex_no = 0;
 1394          break;
 1395       case '@':{
 1396          struct attachment *aplist;
 1397 
 1398          /* Edit the attachment list */
 1399          aplist = hp->h_attach;
 1400          hp->h_attach = NULL;
 1401          if(cnt != 0)
 1402             hp->h_attach = n_attachment_append_list(aplist, cp);
 1403          else
 1404             hp->h_attach = n_attachment_list_edit(aplist,
 1405                   n_GO_INPUT_CTX_COMPOSE);
 1406          n_pstate_err_no = n_ERR_NONE; /* XXX ~@ does NOT handle $!/$?! */
 1407          n_pstate_ex_no = 0; /* XXX */
 1408          }break;
 1409       case '^':
 1410          if(!n_dig_msg_circumflex(&dmc, n_stdout, cp)){
 1411             if(ferror(_coll_fp))
 1412                goto jerr;
 1413             goto jearg;
 1414          }
 1415          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1416          n_pstate_ex_no = 0; /* XXX */
 1417          hist &= ~a_HIST_GABBY;
 1418          break;
 1419       /* case '_': <> ':' */
 1420       case '|':
 1421          /* Pipe message through command. Collect output as new message */
 1422          if(cnt == 0)
 1423             goto jearg;
 1424          /* Is this request to do a "stream equivalent" to 'e' and 'v'? */
 1425          if(*cp == '|'){
 1426             ++cp;
 1427             goto jev_go;
 1428          }
 1429          if((n_pstate_err_no = a_coll_pipe(cp)) == n_ERR_NONE)
 1430             n_pstate_ex_no = 0;
 1431          else if(ferror(_coll_fp))
 1432             goto jerr;
 1433          else if(a_HARDERR())
 1434             goto jerr;
 1435          else
 1436             n_pstate_ex_no = 1;
 1437          hist &= ~a_HIST_GABBY;
 1438          goto jhistcont;
 1439       case 'A':
 1440       case 'a':
 1441          /* Insert the contents of a sign variable */
 1442          if(cnt != 0)
 1443             goto jearg;
 1444          cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
 1445          goto jIi_putesc;
 1446       case 'b':
 1447          /* Add stuff to blind carbon copies list TODO join 'c' */
 1448          if(cnt == 0)
 1449             goto jearg;
 1450          else{
 1451             struct name *np;
 1452             si8_t soe;
 1453 
 1454             soe = 0;
 1455             if((np = checkaddrs(lextract(cp, GBCC | GFULL), EACM_NORMAL, &soe)
 1456                   ) != NULL)
 1457                hp->h_bcc = cat(hp->h_bcc, np);
 1458             if(soe == 0){
 1459                n_pstate_err_no = n_ERR_NONE;
 1460                n_pstate_ex_no = 0;
 1461             }else{
 1462                n_pstate_ex_no = 1;
 1463                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 1464             }
 1465          }
 1466          hist &= ~a_HIST_GABBY;
 1467          break;
 1468       case 'c':
 1469          /* Add to the CC list TODO join 'b' */
 1470          if(cnt == 0)
 1471             goto jearg;
 1472          else{
 1473             struct name *np;
 1474             si8_t soe;
 1475 
 1476             soe = 0;
 1477             if((np = checkaddrs(lextract(cp, GCC | GFULL), EACM_NORMAL, &soe)
 1478                   ) != NULL)
 1479                hp->h_cc = cat(hp->h_cc, np);
 1480             if(soe == 0){
 1481                n_pstate_err_no = n_ERR_NONE;
 1482                n_pstate_ex_no = 0;
 1483             }else{
 1484                n_pstate_ex_no = 1;
 1485                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 1486             }
 1487          }
 1488          hist &= ~a_HIST_GABBY;
 1489          break;
 1490       case 'd':
 1491          if(cnt != 0)
 1492             goto jearg;
 1493          cp = n_getdeadletter();
 1494          if(0){
 1495       case '<':
 1496       case 'R':
 1497       case 'r':
 1498             /* Invoke a file: Search for the file name, then open it and copy
 1499              * the contents to _coll_fp */
 1500             if(cnt == 0){
 1501                n_err(_("Interpolate what file?\n"));
 1502                if(a_HARDERR())
 1503                   goto jerr;
 1504                n_pstate_err_no = n_ERR_NOENT;
 1505                n_pstate_ex_no = 1;
 1506                break;
 1507             }
 1508             if(*cp == '!' && c == '<'){
 1509                /* TODO hist. normalization */
 1510                if((n_pstate_err_no = a_coll_insert_cmd(_coll_fp, ++cp)
 1511                      ) != n_ERR_NONE){
 1512                   if(ferror(_coll_fp))
 1513                      goto jerr;
 1514                   if(a_HARDERR())
 1515                      goto jerr;
 1516                   n_pstate_ex_no = 1;
 1517                   break;
 1518                }
 1519                goto jhistcont;
 1520             }
 1521             /* Note this also expands things like
 1522              *    !:vput vexpr delim random 0
 1523              *    !< - $delim */
 1524             if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO | FEXP_NSHELL)
 1525                   ) == NULL){
 1526                if(a_HARDERR())
 1527                   goto jerr;
 1528                n_pstate_err_no = n_ERR_INVAL;
 1529                n_pstate_ex_no = 1;
 1530                break;
 1531             }
 1532          }
 1533          /* XXX race, and why not test everywhere, then? */
 1534          if(n_is_dir(cp, FAL0)){
 1535             n_err(_("%s: is a directory\n"), n_shexp_quote_cp(cp, FAL0));
 1536             if(a_HARDERR())
 1537                goto jerr;
 1538             n_pstate_err_no = n_ERR_ISDIR;
 1539             n_pstate_ex_no = 1;
 1540             break;
 1541          }
 1542          if((n_pstate_err_no = a_coll_include_file(cp, (c == 'R'), TRU1)
 1543                ) != n_ERR_NONE){
 1544             if(ferror(_coll_fp))
 1545                goto jerr;
 1546             if(a_HARDERR())
 1547                goto jerr;
 1548             n_pstate_ex_no = 1;
 1549             break;
 1550          }
 1551          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1552          n_pstate_ex_no = 0; /* XXX */
 1553          break;
 1554       case 'e':
 1555       case 'v':
 1556          /* Edit the current message.  'e' -> use EDITOR, 'v' -> use VISUAL */
 1557          if(cnt != 0 || coap != NULL)
 1558             goto jearg;
 1559 jev_go:
 1560          if((n_pstate_err_no = a_coll_edit(c,
 1561                 ((c == '|' || ok_blook(editheaders)) ? hp : NULL), cp)
 1562                ) == n_ERR_NONE)
 1563             n_pstate_ex_no = 0;
 1564          else if(ferror(_coll_fp))
 1565             goto jerr;
 1566          else if(a_HARDERR())
 1567             goto jerr;
 1568          else
 1569             n_pstate_ex_no = 1;
 1570          goto jhistcont;
 1571       case 'F':
 1572       case 'f':
 1573       case 'M':
 1574       case 'm':
 1575       case 'Q':
 1576       case 'U':
 1577       case 'u':
 1578          /* Interpolate the named messages, if we are in receiving mail mode.
 1579           * Does the standard list processing garbage.  If ~f is given, we
 1580           * don't shift over */
 1581          if((n_pstate_err_no = a_coll_forward(cp, _coll_fp, c)) == n_ERR_NONE)
 1582             n_pstate_ex_no = 0;
 1583          else if(ferror(_coll_fp))
 1584             goto jerr;
 1585          else if(a_HARDERR())
 1586             goto jerr;
 1587          else
 1588             n_pstate_ex_no = 1;
 1589          break;
 1590       case 'H':
 1591          /* Grab extra headers */
 1592          if(cnt != 0)
 1593             goto jearg;
 1594          do
 1595             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, GEXTRA, 0);
 1596          while(check_from_and_sender(hp->h_from, hp->h_sender) == NULL);
 1597          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1598          n_pstate_ex_no = 0; /* XXX */
 1599          break;
 1600       case 'h':
 1601          /* Grab a bunch of headers */
 1602          if(cnt != 0)
 1603             goto jearg;
 1604          do
 1605             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp,
 1606               (GTO | GSUBJECT | GCC | GBCC),
 1607               (ok_blook(bsdcompat) && ok_blook(bsdorder)));
 1608          while(hp->h_to == NULL);
 1609          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1610          n_pstate_ex_no = 0; /* XXX */
 1611          break;
 1612       case 'I':
 1613       case 'i':
 1614          /* Insert a variable into the file */
 1615          if(cnt == 0)
 1616             goto jearg;
 1617          cp = n_var_vlook(cp, TRU1);
 1618 jIi_putesc:
 1619          if(cp == NULL || *cp == '\0')
 1620             break;
 1621          if(!a_coll_putesc(cp, (c != 'I'), _coll_fp))
 1622             goto jerr;
 1623          if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 1624                !a_coll_putesc(cp, (c != 'I'), n_stdout))
 1625             goto jerr;
 1626          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1627          n_pstate_ex_no = 0; /* XXX */
 1628          break;
 1629       /* case 'M': <> 'F' */
 1630       /* case 'm': <> 'f' */
 1631       case 'p':
 1632          /* Print current state of the message without altering anything */
 1633          if(cnt != 0)
 1634             goto jearg;
 1635          print_collf(_coll_fp, hp); /* XXX pstate_err_no ++ */
 1636          if(ferror(_coll_fp))
 1637             goto jerr;
 1638          n_pstate_err_no = n_ERR_NONE; /* XXX */
 1639          n_pstate_ex_no = 0; /* XXX */
 1640          break;
 1641       /* case 'Q': <> 'F' */
 1642       case 'q':
 1643       case 'x':
 1644          /* Force a quit, act like an interrupt had happened */
 1645          if(cnt != 0)
 1646             goto jearg;
 1647          /* If we are running a splice hook, assume it quits on its own now,
 1648           * otherwise we (no true out-of-band IPC to signal this state, XXX sic)
 1649           * have to SIGTERM it in order to stop this wild beast */
 1650          flags |= a_COAP_NOSIGTERM;
 1651          ++_coll_hadintr;
 1652          _collint((c == 'x') ? 0 : SIGINT);
 1653          exit(n_EXIT_ERR);
 1654          /*NOTREACHED*/
 1655       /* case 'R': <> 'd' */
 1656       /* case 'r': <> 'd' */
 1657       case 's':
 1658          /* Set the Subject list */
 1659          if(cnt == 0)
 1660             goto jearg;
 1661          /* Subject:; take care for Debian #419840 and strip any \r and \n */
 1662          if(n_anyof_cp("\n\r", hp->h_subject = savestr(cp))){
 1663             char *xp;
 1664 
 1665             n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n"));
 1666             for(xp = hp->h_subject; *xp != '\0'; ++xp)
 1667                if(*xp == '\n' || *xp == '\r')
 1668                   *xp = ' ';
 1669             n_pstate_err_no = n_ERR_INVAL;
 1670             n_pstate_ex_no = 1;
 1671          }else{
 1672             n_pstate_err_no = n_ERR_NONE;
 1673             n_pstate_ex_no = 0;
 1674          }
 1675          break;
 1676       case 't':
 1677          /* Add to the To: list TODO join 'b', 'c' */
 1678          if(cnt == 0)
 1679             goto jearg;
 1680          else{
 1681             struct name *np;
 1682             si8_t soe;
 1683 
 1684             soe = 0;
 1685             if((np = checkaddrs(lextract(cp, GTO | GFULL), EACM_NORMAL, &soe)
 1686                   ) != NULL)
 1687                hp->h_to = cat(hp->h_to, np);
 1688             if(soe == 0){
 1689                n_pstate_err_no = n_ERR_NONE;
 1690                n_pstate_ex_no = 0;
 1691             }else{
 1692                n_pstate_ex_no = 1;
 1693                n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
 1694             }
 1695          }
 1696          hist &= ~a_HIST_GABBY;
 1697          break;
 1698       /* case 'U': <> 'F' */
 1699       /* case 'u': <> 'f' */
 1700       /* case 'v': <> 'e' */
 1701       case 'w':
 1702          /* Write the message on a file */
 1703          if(cnt == 0)
 1704             goto jearg;
 1705          if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
 1706             n_err(_("Write what file!?\n"));
 1707             if(a_HARDERR())
 1708                goto jerr;
 1709             n_pstate_err_no = n_ERR_INVAL;
 1710             n_pstate_ex_no = 1;
 1711             break;
 1712          }
 1713          rewind(_coll_fp);
 1714          if((n_pstate_err_no = a_coll_write(cp, _coll_fp, 1)) == n_ERR_NONE)
 1715             n_pstate_ex_no = 0;
 1716          else if(ferror(_coll_fp))
 1717             goto jerr;
 1718          else if(a_HARDERR())
 1719             goto jerr;
 1720          else
 1721             n_pstate_ex_no = 1;
 1722          break;
 1723       /* case 'x': <> 'q' */
 1724       }
 1725 
 1726       /* Finally place an entry in history as applicable */
 1727       if(0){
 1728 jhistcont:
 1729          c = '\1';
 1730       }else
 1731          c = '\0';
 1732       if(hist & a_HIST_ADD){
 1733          /* Do not add *escape* to the history in order to allow history search
 1734           * to be handled generically in the MLE regardless of actual *escape*
 1735           * settings etc. */
 1736          n_tty_addhist(&n_string_cp(sp)[1], (n_GO_INPUT_CTX_COMPOSE |
 1737             (hist & a_HIST_GABBY ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE)));
 1738       }
 1739       if(c != '\0')
 1740          goto jcont;
 1741    }
 1742 
 1743 jout:
 1744    /* Do we have *on-compose-splice-shell*, or *on-compose-splice*?
 1745     * TODO Usual f...ed up state of signals and terminal etc. */
 1746    if(coap == NULL && (cp = ok_vlook(on_compose_splice_shell)) != NULL) Jocs:{
 1747       union {int (*ptf)(void); char const *sh;} u;
 1748       char const *cmd;
 1749 
 1750       /* Reset *escape* and more to their defaults.  On change update manual! */
 1751       if(ifs_saved == NULL)
 1752          ifs_saved = savestr(ok_vlook(ifs));
 1753       escape = n_ESCAPE[0];
 1754       ok_vclear(ifs);
 1755 
 1756       if(coapm != NULL){
 1757          /* XXX Due Popen() fflush(NULL) in PTF mode, ensure nothing to flush */
 1758          /*if(!n_real_seek(_coll_fp, 0, SEEK_END))
 1759           *  goto jerr;*/
 1760          u.ptf = &a_coll_ocs__mac;
 1761          cmd = (char*)-1;
 1762          a_coll_ocs__macname = cp = coapm;
 1763       }else{
 1764          u.sh = ok_vlook(SHELL);
 1765          cmd = cp;
 1766       }
 1767 
 1768       i = strlen(cp) +1;
 1769       coap = n_lofi_alloc(n_VSTRUCT_SIZEOF(struct a_coll_ocs_arg, coa_cmd
 1770             ) + i);
 1771       coap->coa_pipe[0] = coap->coa_pipe[1] = -1;
 1772       coap->coa_stdin = coap->coa_stdout = NULL;
 1773       coap->coa_senderr = checkaddr_err;
 1774       memcpy(coap->coa_cmd, cp, i);
 1775 
 1776       hold_all_sigs();
 1777       coap->coa_opipe = safe_signal(SIGPIPE, SIG_IGN);
 1778       coap->coa_oint = safe_signal(SIGINT, SIG_IGN);
 1779       rele_all_sigs();
 1780 
 1781       if(pipe_cloexec(coap->coa_pipe) != -1 &&
 1782             (coap->coa_stdin = Fdopen(coap->coa_pipe[0], "r", FAL0)) != NULL &&
 1783             (coap->coa_stdout = Popen(cmd, "W", u.sh, NULL, coap->coa_pipe[1])
 1784              ) != NULL){
 1785          close(coap->coa_pipe[1]);
 1786          coap->coa_pipe[1] = -1;
 1787 
 1788          temporary_compose_mode_hook_call(NULL, NULL, NULL);
 1789          n_go_splice_hack(coap->coa_cmd, coap->coa_stdin, coap->coa_stdout,
 1790             (n_psonce & ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT)),
 1791             &a_coll_ocs__finalize, &coap);
 1792          /* Hook version protocol for ~^: update manual upon change! */
 1793          fputs(n_DIG_MSG_PLUMBING_VERSION "\n", n_stdout/*coap->coa_stdout*/);
 1794          goto jcont;
 1795       }
 1796 
 1797       c = n_err_no;
 1798       a_coll_ocs__finalize(coap);
 1799       n_perr(_("Cannot invoke *on-compose-splice(-shell)?*"), c);
 1800       goto jerr;
 1801    }
 1802    if(*checkaddr_err != 0){
 1803       *checkaddr_err = 0;
 1804       goto jerr;
 1805    }
 1806    if(coapm == NULL && (coapm = ok_vlook(on_compose_splice)) != NULL)
 1807       goto Jocs;
 1808    if(coap != NULL){
 1809       ok_vset(ifs, ifs_saved);
 1810       ifs_saved = NULL;
 1811    }
 1812 
 1813    /* Final chance to edit headers, if not already above */
 1814    if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
 1815       if(ok_blook(bsdcompat) || ok_blook(askatend)){
 1816          enum gfield gf;
 1817 
 1818          gf = GNONE;
 1819          if(ok_blook(askcc))
 1820             gf |= GCC;
 1821          if(ok_blook(askbcc))
 1822             gf |= GBCC;
 1823          if(gf != 0)
 1824             grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, gf, 1);
 1825       }
 1826       if(ok_blook(askattach))
 1827          hp->h_attach = n_attachment_list_edit(hp->h_attach,
 1828                n_GO_INPUT_CTX_COMPOSE);
 1829    }
 1830 
 1831    /* Execute compose-leave */
 1832    if((cp = ok_vlook(on_compose_leave)) != NULL){
 1833       setup_from_and_sender(hp);
 1834       temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
 1835          hp);
 1836    }
 1837 
 1838    /* Add automatic receivers */
 1839    if ((cp = ok_vlook(autocc)) != NULL && *cp != '\0')
 1840       hp->h_cc = cat(hp->h_cc, checkaddrs(lextract(cp, GCC |
 1841             (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
 1842             EACM_NORMAL, checkaddr_err));
 1843    if ((cp = ok_vlook(autobcc)) != NULL && *cp != '\0')
 1844       hp->h_bcc = cat(hp->h_bcc, checkaddrs(lextract(cp, GBCC |
 1845             (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
 1846             EACM_NORMAL, checkaddr_err));
 1847    if (*checkaddr_err != 0)
 1848       goto jerr;
 1849 
 1850    if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 1851          ok_blook(asksend)){
 1852       bool_t b;
 1853 
 1854       ifs_saved = coapm = NULL;
 1855       coap = NULL;
 1856 
 1857       fprintf(n_stdout, _("-------\nEnvelope contains:\n")); /* xxx SEARCH112 */
 1858       if(!n_puthead(TRU1, hp, n_stdout,
 1859             GIDENT | GREF_IRT  | GSUBJECT | GTO | GCC | GBCC | GBCC_IS_FCC |
 1860             GCOMMA, SEND_TODISP, CONV_NONE, NULL, NULL))
 1861          goto jerr;
 1862 
 1863 jreasksend:
 1864       if(n_go_input(n_GO_INPUT_CTX_COMPOSE | n_GO_INPUT_NL_ESC,
 1865             _("Send this message [yes/no, empty: recompose]? "),
 1866             &linebuf, &linesize, NULL, NULL) < 0){
 1867          if(!n_go_input_is_eof())
 1868             goto jerr;
 1869          cp = n_1;
 1870       }
 1871 
 1872       if((b = n_boolify(linebuf, UIZ_MAX, TRUM1)) < FAL0)
 1873          goto jreasksend;
 1874       if(b == TRU2)
 1875          goto jcont;
 1876       if(!b)
 1877          goto jerr;
 1878    }
 1879 
 1880    /* TODO Cannot do since it may require turning this into a multipart one */
 1881    if(n_poption & n_PO_Mm_FLAG)
 1882       goto jskiptails;
 1883 
 1884    /* Place signature? */
 1885    if((cp = ok_vlook(signature)) != NULL && *cp != '\0'){ /* TODO OBSOLETE */
 1886       char const *cpq;
 1887 
 1888       n_OBSOLETE(_("please use *on-compose-{leave,splice}* and/or "
 1889          "*message-inject-tail*, not *signature*"));
 1890 
 1891       if((cpq = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
 1892          n_err(_("*signature* expands to invalid file: %s\n"),
 1893             n_shexp_quote_cp(cp, FAL0));
 1894          goto jerr;
 1895       }
 1896       cpq = n_shexp_quote_cp(cp = cpq, FAL0);
 1897 
 1898       if((sigfp = Fopen(cp, "r")) == NULL){
 1899          n_err(_("Can't open *signature* %s: %s\n"),
 1900             cpq, n_err_to_doc(n_err_no));
 1901          goto jerr;
 1902       }
 1903 
 1904       if(linebuf == NULL)
 1905          linebuf = n_alloc(linesize = LINESIZE);
 1906       c = '\0';
 1907       while((i = fread(linebuf, sizeof *linebuf, linesize, n_UNVOLATILE(sigfp)))
 1908             > 0){
 1909          c = linebuf[i - 1];
 1910          if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
 1911             goto jerr;
 1912       }
 1913 
 1914       /* C99 */{
 1915          FILE *x = n_UNVOLATILE(sigfp);
 1916          int e = n_err_no, ise = ferror(x);
 1917 
 1918          sigfp = NULL;
 1919          Fclose(x);
 1920 
 1921          if(ise){
 1922             n_err(_("Errors while reading *signature* %s: %s\n"),
 1923                cpq, n_err_to_doc(e));
 1924             goto jerr;
 1925          }
 1926       }
 1927 
 1928       if(c != '\0' && c != '\n')
 1929          putc('\n', _coll_fp);
 1930    }
 1931 
 1932    {  char const *cp_obsolete = ok_vlook(NAIL_TAIL);
 1933 
 1934       if(cp_obsolete != NULL)
 1935          n_OBSOLETE(_("please use *message-inject-tail*, not *NAIL_TAIL*"));
 1936 
 1937    if((cp = ok_vlook(message_inject_tail)) != NULL ||
 1938          (cp = cp_obsolete) != NULL){
 1939       if(!a_coll_putesc(cp, TRU1, _coll_fp))
 1940          goto jerr;
 1941       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
 1942             !a_coll_putesc(cp, TRU1, n_stdout))
 1943          goto jerr;
 1944    }
 1945    }
 1946 
 1947 jskiptails:
 1948    if(fflush(_coll_fp))
 1949       goto jerr;
 1950    rewind(_coll_fp);
 1951 
 1952    if(mp != NULL && ok_blook(quote_as_attachment)){
 1953       struct attachment *ap;
 1954 
 1955       ap = n_autorec_calloc(1, sizeof *ap);
 1956       if((ap->a_flink = hp->h_attach) != NULL)
 1957          hp->h_attach->a_blink = ap;
 1958       hp->h_attach = ap;
 1959       ap->a_msgno = (int)PTR2SIZE(mp - message + 1);
 1960       ap->a_content_description = _("Original message content");
 1961    }
 1962 
 1963 jleave:
 1964    if (linebuf != NULL)
 1965       n_free(linebuf);
 1966    sigprocmask(SIG_BLOCK, &nset, NULL);
 1967    n_DIG_MSG_COMPOSE_GUT(&dmc);
 1968    n_pstate &= ~n_PS_COMPOSE_MODE;
 1969    safe_signal(SIGINT, _coll_saveint);
 1970    safe_signal(SIGHUP, _coll_savehup);
 1971    sigprocmask(SIG_SETMASK, &oset, NULL);
 1972    NYD_LEAVE;
 1973    return _coll_fp;
 1974 
 1975 jerr:
 1976    hold_all_sigs();
 1977 
 1978    if(coap != NULL && coap != (struct a_coll_ocs_arg*)-1){
 1979       if(!(flags & a_COAP_NOSIGTERM))
 1980          n_psignal(coap->coa_stdout, SIGTERM);
 1981       n_go_splice_hack_remove_after_jump();
 1982       coap = NULL;
 1983    }
 1984    if(ifs_saved != NULL){
 1985       ok_vset(ifs, ifs_saved);
 1986       ifs_saved = NULL;
 1987    }
 1988    if(sigfp != NULL){
 1989       Fclose(n_UNVOLATILE(sigfp));
 1990       sigfp = NULL;
 1991    }
 1992    if(_coll_fp != NULL){
 1993       Fclose(_coll_fp);
 1994       _coll_fp = NULL;
 1995    }
 1996 
 1997    rele_all_sigs();
 1998 
 1999    assert(checkaddr_err != NULL);
 2000    /* TODO We don't save in $DEAD upon error because msg not readily composed?
 2001     * TODO But this no good, it should go ZOMBIE / DRAFT / POSTPONED or what! */
 2002    if(*checkaddr_err != 0){
 2003       if(*checkaddr_err == 111)
 2004          n_err(_("Compose mode splice hook failure\n"));
 2005       else
 2006          n_err(_("Some addressees were classified as \"hard error\"\n"));
 2007    }else if(_coll_hadintr == 0){
 2008       *checkaddr_err = TRU1; /* TODO ugly: "sendout_error" now.. */
 2009       n_err(_("Failed to prepare composed message\n"));
 2010    }
 2011    goto jleave;
 2012 
 2013 #undef a_HARDERR
 2014 }
 2015 
 2016 /* s-it-mode */