"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/head.c" (16 Feb 2018, 86041 Bytes) of package /linux/misc/s-nail-14.9.7.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 "head.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.6_vs_14.9.7.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Routines for processing and detecting headlines.
    3  *@ TODO Mostly a hackery, we need RFC compliant parsers instead.
    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 head
   38 
   39 #ifndef HAVE_AMALGAMATION
   40 # include "nail.h"
   41 #endif
   42 
   43 #include <pwd.h>
   44 
   45 struct cmatch_data {
   46    size_t      tlen;    /* Length of .tdata */
   47    char const  *tdata;  /* Template date - see _cmatch_data[] */
   48 };
   49 
   50 /* Template characters for cmatch_data.tdata:
   51  * 'A'   An upper case char
   52  * 'a'   A lower case char
   53  * ' '   A space
   54  * '0'   A digit
   55  * 'O'   An optional digit or space
   56  * ':'   A colon
   57  * '+'  Either a plus or a minus sign */
   58 static struct cmatch_data const  _cmatch_data[] = {
   59    { 24, "Aaa Aaa O0 00:00:00 0000" },       /* BSD/ISO C90 ctime */
   60    { 28, "Aaa Aaa O0 00:00:00 AAA 0000" },   /* BSD tmz */
   61    { 21, "Aaa Aaa O0 00:00 0000" },          /* SysV ctime */
   62    { 25, "Aaa Aaa O0 00:00 AAA 0000" },      /* SysV tmz */
   63    /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
   64     * in the wild (by UW-imap) */
   65    { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
   66    /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
   67     * zone strings; note that 1. is strictly speaking not correct as some
   68     * letters are not used, and 2. is not because only "UT" is defined */
   69 #define __reuse      "Aaa Aaa O0 00:00:00 0000 AAA"
   70    { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
   71    { 0, NULL }
   72 };
   73 #define a_HEAD_DATE_MINLEN 21
   74 
   75 /* Skip over "word" as found in From_ line */
   76 static char const *        _from__skipword(char const *wp);
   77 
   78 /* Match the date string against the date template (tp), return if match.
   79  * See _cmatch_data[] for template character description */
   80 static int                 _cmatch(size_t len, char const *date,
   81                               char const *tp);
   82 
   83 /* Check whether date is a valid 'From_' date.
   84  * (Rather ctime(3) generated dates, according to RFC 4155) */
   85 static int                 _is_date(char const *date);
   86 
   87 /* JulianDayNumber converter(s) */
   88 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
   89 #if 0
   90 static void a_head_jdn_to_gregorian(size_t jdn,
   91                ui32_t *yp, ui32_t *mp, ui32_t *dp);
   92 #endif
   93 
   94 /* Convert the domain part of a skinned address to IDNA.
   95  * If an error occurs before Unicode information is available, revert the IDNA
   96  * error to a normal CHAR one so that the error message doesn't talk Unicode */
   97 #ifdef HAVE_IDNA
   98 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
   99 #endif
  100 
  101 /* Classify and check a (possibly skinned) header body according to RFC
  102  * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
  103  * also a file or a pipe command, so check that first, then.
  104  * Otherwise perform content checking and isolate the domain part (for IDNA).
  105  * issingle_hack has the same meaning as for n_addrspec_with_guts() */
  106 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned,
  107                bool_t issingle_hack);
  108 
  109 /* Return the next header field found in the given message.
  110  * Return >= 0 if something found, < 0 elsewise.
  111  * "colon" is set to point to the colon in the header.
  112  * Must deal with \ continuations & other such fraud */
  113 static long a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem,
  114             char **colon);
  115 
  116 static int                 msgidnextc(char const **cp, int *status);
  117 
  118 /* Count the occurances of c in str */
  119 static int                 charcount(char *str, int c);
  120 
  121 static char const *        nexttoken(char const *cp);
  122 
  123 static char const *
  124 _from__skipword(char const *wp)
  125 {
  126    char c = 0;
  127    NYD2_ENTER;
  128 
  129    if (wp != NULL) {
  130       while ((c = *wp++) != '\0' && !blankchar(c)) {
  131          if (c == '"') {
  132             while ((c = *wp++) != '\0' && c != '"')
  133                ;
  134             if (c != '"')
  135                --wp;
  136          }
  137       }
  138       for (; blankchar(c); c = *wp++)
  139          ;
  140    }
  141    NYD2_LEAVE;
  142    return (c == 0 ? NULL : wp - 1);
  143 }
  144 
  145 static int
  146 _cmatch(size_t len, char const *date, char const *tp)
  147 {
  148    int ret = 0;
  149    NYD2_ENTER;
  150 
  151    while (len--) {
  152       char c = date[len];
  153       switch (tp[len]) {
  154       case 'a':
  155          if (!lowerchar(c))
  156             goto jleave;
  157          break;
  158       case 'A':
  159          if (!upperchar(c))
  160             goto jleave;
  161          break;
  162       case ' ':
  163          if (c != ' ')
  164             goto jleave;
  165          break;
  166       case '0':
  167          if (!digitchar(c))
  168             goto jleave;
  169          break;
  170       case 'O':
  171          if (c != ' ' && !digitchar(c))
  172             goto jleave;
  173          break;
  174       case ':':
  175          if (c != ':')
  176             goto jleave;
  177          break;
  178       case '+':
  179          if (c != '+' && c != '-')
  180             goto jleave;
  181          break;
  182       }
  183    }
  184    ret = 1;
  185 jleave:
  186    NYD2_LEAVE;
  187    return ret;
  188 }
  189 
  190 static int
  191 _is_date(char const *date)
  192 {
  193    struct cmatch_data const *cmdp;
  194    size_t dl;
  195    int rv = 0;
  196    NYD2_ENTER;
  197 
  198    if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
  199       for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
  200          if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
  201             break;
  202    NYD2_LEAVE;
  203    return rv;
  204 }
  205 
  206 static size_t
  207 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
  208    /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
  209     * (via third hand, plus adjustments).
  210     * This algorithm is supposed to work for all dates in between 1582-10-15
  211     * (0001-01-01 but that not Gregorian) and 65535-12-31 */
  212    size_t jdn;
  213    NYD2_ENTER;
  214 
  215 #if 0
  216    if(y == 0)
  217       y = 1;
  218    if(m == 0)
  219       m = 1;
  220    if(d == 0)
  221       d = 1;
  222 #endif
  223 
  224    if(m > 2)
  225       m -= 3;
  226    else{
  227       m += 9;
  228       --y;
  229    }
  230    jdn = y;
  231    jdn /= 100;
  232    y -= 100 * jdn;
  233    y *= 1461;
  234    y >>= 2;
  235    jdn *= 146097;
  236    jdn >>= 2;
  237    jdn += y;
  238    jdn += d;
  239    jdn += 1721119;
  240    m *= 153;
  241    m += 2;
  242    m /= 5;
  243    jdn += m;
  244    NYD2_LEAVE;
  245    return jdn;
  246 }
  247 
  248 #if 0
  249 static void
  250 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
  251    /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
  252     * (via third hand, plus adjustments) */
  253    size_t y, x;
  254    NYD2_ENTER;
  255 
  256    jdn -= 1721119;
  257    jdn <<= 2;
  258    --jdn;
  259    y =   jdn / 146097;
  260          jdn %= 146097;
  261    jdn |= 3;
  262    y *= 100;
  263    y +=  jdn / 1461;
  264          jdn %= 1461;
  265    jdn += 4;
  266    jdn >>= 2;
  267    x = jdn;
  268    jdn <<= 2;
  269    jdn += x;
  270    jdn -= 3;
  271    x =   jdn / 153;  /* x -> month */
  272          jdn %= 153;
  273    jdn += 5;
  274    jdn /= 5; /* jdn -> day */
  275    if(x < 10)
  276       x += 3;
  277    else{
  278       x -= 9;
  279       ++y;
  280    }
  281 
  282    *yp = (ui32_t)(y & 0xFFFF);
  283    *mp = (ui32_t)(x & 0xFF);
  284    *dp = (ui32_t)(jdn & 0xFF);
  285    NYD2_LEAVE;
  286 }
  287 #endif /* 0 */
  288 
  289 #ifdef HAVE_IDNA
  290 static struct n_addrguts *
  291 a_head_idna_apply(struct n_addrguts *agp){
  292    struct n_string idna_ascii;
  293    NYD_ENTER;
  294 
  295    n_string_creat_auto(&idna_ascii);
  296 
  297    if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
  298          agp->ag_slen - agp->ag_sdom_start))
  299       agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
  300    else{
  301       /* Replace the domain part of .ag_skinned with IDNA version */
  302       n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
  303 
  304       agp->ag_skinned = n_string_cp(&idna_ascii);
  305       agp->ag_slen = idna_ascii.s_len;
  306       NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
  307          NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
  308    }
  309    NYD_LEAVE;
  310    return agp;
  311 }
  312 #endif /* HAVE_IDNA */
  313 
  314 static bool_t
  315 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned,
  316       bool_t issingle_hack)
  317 {
  318    char *addr, *p;
  319    union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
  320    enum{
  321       a_NONE,
  322       a_IDNA_ENABLE = 1u<<0,
  323       a_IDNA_APPLY = 1u<<1,
  324       a_REDO_NODE_AFTER_ADDR = 1u<<2,
  325       a_RESET_MASK = a_IDNA_ENABLE | a_IDNA_APPLY | a_REDO_NODE_AFTER_ADDR,
  326       a_IN_QUOTE = 1u<<8,
  327       a_IN_AT = 1u<<9,
  328       a_IN_DOMAIN = 1u<<10,
  329       a_DOMAIN_V6 = 1u<<11,
  330       a_DOMAIN_MASK = a_IN_DOMAIN | a_DOMAIN_V6
  331    } flags;
  332    NYD_ENTER;
  333 
  334    flags = a_NONE;
  335 #ifdef HAVE_IDNA
  336    if(!ok_blook(idna_disable))
  337       flags = a_IDNA_ENABLE;
  338 #endif
  339 
  340    if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
  341       NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
  342       goto jleave;
  343    }
  344 
  345    addr = agp->ag_skinned;
  346 
  347    /* If the field is not a recipient, it cannot be a file or a pipe */
  348    if (!skinned)
  349       goto jaddr_check;
  350 
  351    /* When changing any of the following adjust any RECIPIENTADDRSPEC;
  352     * grep the latter for the complete picture */
  353    if (*addr == '|') {
  354       agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
  355       goto jleave;
  356    }
  357    if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
  358          (addr[0] == '-' && addr[1] == '\0'))
  359       goto jisfile;
  360    if (memchr(addr, '@', agp->ag_slen) == NULL) {
  361       if (*addr == '+')
  362          goto jisfile;
  363       for (p = addr; (c.c = *p); ++p) {
  364          if (c.c == '!' || c.c == '%')
  365             break;
  366          if (c.c == '/') {
  367 jisfile:
  368             agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
  369             goto jleave;
  370          }
  371       }
  372    }
  373 
  374 jaddr_check:
  375    /* TODO This is false.  If super correct this should work on wide
  376     * TODO characters, just in case (some bytes of) the ASCII set is (are)
  377     * TODO shared; it may yet tear apart multibyte sequences, possibly.
  378     * TODO All this should interact with mime_enc_mustquote(), too!
  379     * TODO That is: once this is an object, we need to do this in a way
  380     * TODO that it is valid for the wire format (instead)! */
  381    /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
  382     * TODO Note this correlats with addrspec_with_guts() which is in front
  383     * TODO of us and encapsulates (what it thinks is, sigh) the address
  384     * TODO boundary.  ALL THIS should be one object that knows how to deal */
  385    flags &= a_RESET_MASK;
  386    for (p = addr; (c.c = *p++) != '\0';) {
  387       if (c.c == '"') {
  388          flags ^= a_IN_QUOTE;
  389       } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
  390 #ifdef HAVE_IDNA
  391          if ((flags & (a_IN_DOMAIN | a_IDNA_ENABLE)) ==
  392                (a_IN_DOMAIN | a_IDNA_ENABLE))
  393             flags |= a_IDNA_APPLY;
  394          else
  395 #endif
  396             break;
  397       } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
  398          if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
  399             break;
  400       } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
  401          /*EMPTY*/;
  402       } else if (c.c == '\\' && *p != '\0') {
  403          ++p;
  404       } else if (c.c == '@') {
  405          if(flags & a_IN_AT){
  406             NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
  407                c.u);
  408             goto jleave;
  409          }
  410          agp->ag_sdom_start = PTR2SIZE(p - addr);
  411          agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
  412          flags &= ~a_DOMAIN_MASK;
  413          flags |= (*p == '[') ? a_IN_AT | a_IN_DOMAIN | a_DOMAIN_V6
  414                : a_IN_AT | a_IN_DOMAIN;
  415          continue;
  416       }
  417       /* TODO This interferes with our alias handling, which allows :!
  418        * TODO Update manual on support (search the several ALIASCOLON)! */
  419       else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
  420             c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
  421             c.c == '\\' || c.c == ',' || blankchar(c.c))
  422          break;
  423       flags &= ~a_IN_AT;
  424    }
  425    if (c.c != '\0') {
  426       NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
  427       goto jleave;
  428    }
  429 
  430    /* If we do not think this is an address we may treat it as an alias name
  431     * if and only if the original input is identical to the skinned version */
  432    if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR) &&
  433          !strcmp(agp->ag_skinned, agp->ag_input)){
  434       /* TODO This may be an UUCP address */
  435       agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
  436       if(!n_alias_is_valid_name(agp->ag_input))
  437          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
  438    }else{
  439       /* If we seem to know that this is an address.  Ensure this is correct
  440        * according to RFC 5322 TODO the entire address parser should be like
  441        * TODO that for one, and then we should know whether structured or
  442        * TODO unstructured, and just parse correctly overall!
  443        * TODO In addition, this can be optimised a lot.
  444        * TODO And it is far from perfect: it should not forget whether no
  445        * TODO whitespace followed some snippet, and it was written hastily.
  446        * TODO It is even wrong sometimes.  Not only for strange cases */
  447       struct a_token{
  448          struct a_token *t_last;
  449          struct a_token *t_next;
  450          enum{
  451             a_T_TATOM = 1u<<0,
  452             a_T_TCOMM = 1u<<1,
  453             a_T_TQUOTE = 1u<<2,
  454             a_T_TADDR = 1u<<3,
  455             a_T_TMASK = (1u<<4) - 1,
  456 
  457             a_T_SPECIAL = 1u<<8     /* An atom actually needs to go TQUOTE */
  458          } t_f;
  459          ui8_t t__pad[4];
  460          size_t t_start;
  461          size_t t_end;
  462       } *thead, *tcurr, *tp;
  463 
  464       struct n_string ost, *ostp;
  465       char const *cp, *cp1st, *cpmax, *xp;
  466       void *lofi_snap;
  467 
  468       /* Name and domain must be non-empty */
  469       if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
  470 jeat:
  471          c.c = '@';
  472          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
  473          goto jleave;
  474       }
  475 
  476       cp = agp->ag_input;
  477 
  478       /* Nothing to do if there is only an address (in angle brackets) */
  479       /* TODO This is wrong since we allow invalid constructs in local-part
  480        * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
  481        * TODO "abcd"@abc.  Etc. */
  482       if(agp->ag_iaddr_start == 0){
  483          /* No @ seen? */
  484          if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
  485             goto jeat;
  486          if(agp->ag_iaddr_aend == agp->ag_ilen)
  487             goto jleave;
  488       }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
  489             agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
  490             cp[agp->ag_iaddr_aend] == '>'){
  491          /* No @ seen?  Possibly insert n_nodename() */
  492          if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
  493             cp = &agp->ag_input[agp->ag_iaddr_start];
  494             cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  495             goto jinsert_domain;
  496          }
  497          goto jleave;
  498       }
  499 
  500       /* It is not, so parse off all tokens, then resort and rejoin */
  501       lofi_snap = n_lofi_snap_create();
  502 
  503       cp1st = cp;
  504       if((c.ui32 = agp->ag_iaddr_start) > 0)
  505          --c.ui32;
  506       cpmax = &cp[c.ui32];
  507 
  508       thead = tcurr = NULL;
  509 jnode_redo:
  510       for(tp = NULL; cp < cpmax;){
  511          switch((c.c = *cp)){
  512          case '(':
  513             if(tp != NULL)
  514                tp->t_end = PTR2SIZE(cp - cp1st);
  515             tp = n_lofi_alloc(sizeof *tp);
  516             tp->t_next = NULL;
  517             if((tp->t_last = tcurr) != NULL)
  518                tcurr->t_next = tp;
  519             else
  520                thead = tp;
  521             tcurr = tp;
  522             tp->t_f = a_T_TCOMM;
  523             tp->t_start = PTR2SIZE(++cp - cp1st);
  524             xp = skip_comment(cp);
  525             tp->t_end = PTR2SIZE(xp - cp1st);
  526             cp = xp;
  527             if(tp->t_end > tp->t_start){
  528                if(xp[-1] == ')')
  529                   --tp->t_end;
  530                else{
  531                   /* No closing comment - strip trailing whitespace */
  532                   while(blankchar(*--xp))
  533                      if(--tp->t_end == tp->t_start)
  534                         break;
  535                }
  536             }
  537             tp = NULL;
  538             break;
  539 
  540          case '"':
  541             if(tp != NULL)
  542                tp->t_end = PTR2SIZE(cp - cp1st);
  543             tp = n_lofi_alloc(sizeof *tp);
  544             tp->t_next = NULL;
  545             if((tp->t_last = tcurr) != NULL)
  546                tcurr->t_next = tp;
  547             else
  548                thead = tp;
  549             tcurr = tp;
  550             tp->t_f = a_T_TQUOTE;
  551             tp->t_start = PTR2SIZE(++cp - cp1st);
  552             for(xp = cp; xp < cpmax; ++xp){
  553                if((c.c = *xp) == '"')
  554                   break;
  555                if(c.c == '\\' && xp[1] != '\0')
  556                   ++xp;
  557             }
  558             tp->t_end = PTR2SIZE(xp - cp1st);
  559             cp = &xp[1];
  560             if(tp->t_end > tp->t_start){
  561                /* No closing quote - strip trailing whitespace */
  562                if(*xp != '"'){
  563                   while(blankchar(*xp--))
  564                      if(--tp->t_end == tp->t_start)
  565                         break;
  566                }
  567             }
  568             tp = NULL;
  569             break;
  570 
  571          default:
  572             if(blankchar(c.c)){
  573                if(tp != NULL)
  574                   tp->t_end = PTR2SIZE(cp - cp1st);
  575                tp = NULL;
  576                ++cp;
  577                break;
  578             }
  579 
  580             if(tp == NULL){
  581                tp = n_lofi_alloc(sizeof *tp);
  582                tp->t_next = NULL;
  583                if((tp->t_last = tcurr) != NULL)
  584                   tcurr->t_next = tp;
  585                else
  586                   thead = tp;
  587                tcurr = tp;
  588                tp->t_f = a_T_TATOM;
  589                tp->t_start = PTR2SIZE(cp - cp1st);
  590             }
  591             ++cp;
  592 
  593             /* Reverse solidus transforms the following into a quoted-pair, and
  594              * therefore (must occur in comment or quoted-string only) the
  595              * entire atom into a quoted string */
  596             if(c.c == '\\'){
  597                tp->t_f |= a_T_SPECIAL;
  598                if(cp < cpmax)
  599                   ++cp;
  600                break;
  601             }
  602 
  603             /* Is this plain RFC 5322 "atext", or "specials"?
  604              * TODO Because we don't know structured/unstructured, nor anything
  605              * TODO else, we need to treat "dot-atom" as being identical to
  606              * TODO "specials".
  607              * However, if the 8th bit is set, this will be RFC 2047 converted
  608              * and the entire sequence is skipped */
  609             if(!(c.u & 0x80) && !alnumchar(c.c) &&
  610                   c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
  611                   c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
  612                   c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
  613                   c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
  614                   c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
  615                tp->t_f |= a_T_SPECIAL;
  616             break;
  617          }
  618       }
  619       if(tp != NULL)
  620          tp->t_end = PTR2SIZE(cp - cp1st);
  621 
  622       if(!(flags & a_REDO_NODE_AFTER_ADDR)){
  623          flags |= a_REDO_NODE_AFTER_ADDR;
  624 
  625          /* The local-part may be in quotes.. */
  626          if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
  627                tp->t_end == agp->ag_iaddr_start - 1){
  628             /* ..so backward extend it, including the starting quote */
  629             /* TODO This is false and the code below #if 0 away.  We would
  630              * TODO need to create a properly quoted local-part HERE AND NOW
  631              * TODO and REPLACE the original data with that version, but the
  632              * TODO current code cannot do that.  The node needs the data,
  633              * TODO not only offsets for that, for example.  If we had all that
  634              * TODO the code below could produce a really valid thing */
  635             if(tp->t_start > 0)
  636                --tp->t_start;
  637             if(tp->t_start > 0 &&
  638                   (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
  639                      agp->ag_input[tp->t_start - 1] == '\\')
  640                --tp->t_start;
  641             tp->t_f = a_T_TADDR | a_T_SPECIAL;
  642          }else{
  643             tp = n_lofi_alloc(sizeof *tp);
  644             tp->t_next = NULL;
  645             if((tp->t_last = tcurr) != NULL)
  646                tcurr->t_next = tp;
  647             else
  648                thead = tp;
  649             tcurr = tp;
  650             tp->t_f = a_T_TADDR;
  651             tp->t_start = agp->ag_iaddr_start;
  652             /* TODO Very special case because of our hacky non-object-based and
  653              * TODO non-compliant address parser.  Note */
  654             if(tp->t_last == NULL && tp->t_start > 0)
  655                tp->t_start = 0;
  656             if(agp->ag_input[tp->t_start] == '<')
  657                ++tp->t_start;
  658 
  659             /* TODO Very special check for whether we need to massage the
  660              * TODO local part.  This is wrong, but otherwise even more so */
  661 #if 0
  662             cp = &agp->ag_input[tp->t_start];
  663             cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  664             while(cp < cpmax){
  665                c.c = *cp++;
  666                if(!(c.u & 0x80) && !alnumchar(c.c) &&
  667                      c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
  668                      c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
  669                      c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
  670                      c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
  671                      c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
  672                   tp->t_f |= a_T_SPECIAL;
  673                   break;
  674                }
  675             }
  676 #endif
  677          }
  678          tp->t_end = agp->ag_iaddr_aend;
  679          assert(tp->t_start <= tp->t_end);
  680          tp = NULL;
  681 
  682          cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
  683          cpmax = &agp->ag_input[agp->ag_ilen];
  684          if(cp < cpmax)
  685             goto jnode_redo;
  686       }
  687 
  688       /* Nothing may follow the address, move it to the end */
  689       if(!(tcurr->t_f & a_T_TADDR)){
  690          for(tp = thead; tp != NULL; tp = tp->t_next){
  691             if(tp->t_f & a_T_TADDR){
  692                if(tp->t_last != NULL)
  693                   tp->t_last->t_next = tp->t_next;
  694                else
  695                   thead = tp->t_next;
  696                if(tp->t_next != NULL)
  697                   tp->t_next->t_last = tp->t_last;
  698 
  699                tcurr = tp;
  700                while(tp->t_next != NULL)
  701                   tp = tp->t_next;
  702                tp->t_next = tcurr;
  703                tcurr->t_last = tp;
  704                tcurr->t_next = NULL;
  705                break;
  706             }
  707          }
  708       }
  709 
  710       /* Make ranges contiguous: ensure a continuous range of atoms is converted
  711        * to a SPECIAL one if at least one of them requires it */
  712       for(tp = thead; tp != NULL; tp = tp->t_next){
  713          if(tp->t_f & a_T_SPECIAL){
  714             tcurr = tp;
  715             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
  716                tp->t_f |= a_T_SPECIAL;
  717             tp = tcurr;
  718             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
  719                tp->t_f |= a_T_SPECIAL;
  720             if(tp == NULL)
  721                break;
  722          }
  723       }
  724 
  725       /* And yes, we want quotes to extend as much as possible */
  726       for(tp = thead; tp != NULL; tp = tp->t_next){
  727          if(tp->t_f & a_T_TQUOTE){
  728             tcurr = tp;
  729             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
  730                tp->t_f |= a_T_SPECIAL;
  731             tp = tcurr;
  732             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
  733                tp->t_f |= a_T_SPECIAL;
  734             if(tp == NULL)
  735                break;
  736          }
  737       }
  738 
  739       /* Then rejoin */
  740       ostp = n_string_creat_auto(&ost);
  741       if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
  742          ostp = n_string_reserve(ostp, c.ui32 <<= 1);
  743 
  744       for(tcurr = thead; tcurr != NULL;){
  745          if(tcurr != thead)
  746             ostp = n_string_push_c(ostp, ' ');
  747          if(tcurr->t_f & a_T_TADDR){
  748             if(tcurr->t_last != NULL)
  749                ostp = n_string_push_c(ostp, '<');
  750             agp->ag_iaddr_start = ostp->s_len;
  751             /* Now it is terrible to say, but if that thing contained
  752              * quotes, then those may contain quoted-pairs! */
  753 #if 0
  754             if(!(tcurr->t_f & a_T_SPECIAL)){
  755 #endif
  756                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  757                      (tcurr->t_end - tcurr->t_start));
  758 #if 0
  759             }else{
  760                bool_t quot, esc;
  761 
  762                ostp = n_string_push_c(ostp, '"');
  763                quot = TRU1;
  764 
  765                cp = &cp1st[tcurr->t_start];
  766                cpmax = &cp1st[tcurr->t_end];
  767                for(esc = FAL0; cp < cpmax;){
  768                   if((c.c = *cp++) == '\\' && !esc){
  769                      if(cp < cpmax && (*cp == '"' || *cp == '\\'))
  770                         esc = TRU1;
  771                   }else{
  772                      if(esc || c.c == '"')
  773                         ostp = n_string_push_c(ostp, '\\');
  774                      else if(c.c == '@'){
  775                         ostp = n_string_push_c(ostp, '"');
  776                         quot = FAL0;
  777                      }
  778                      ostp = n_string_push_c(ostp, c.c);
  779                      esc = FAL0;
  780                   }
  781                }
  782             }
  783 #endif
  784             agp->ag_iaddr_aend = ostp->s_len;
  785 
  786             if(tcurr->t_last != NULL)
  787                ostp = n_string_push_c(ostp, '>');
  788             tcurr = tcurr->t_next;
  789          }else if(tcurr->t_f & a_T_TCOMM){
  790             ostp = n_string_push_c(ostp, '(');
  791             ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  792                   (tcurr->t_end - tcurr->t_start));
  793             while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
  794                tcurr = tp;
  795                ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
  796                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  797                      (tcurr->t_end - tcurr->t_start));
  798             }
  799             ostp = n_string_push_c(ostp, ')');
  800             tcurr = tcurr->t_next;
  801          }else if(tcurr->t_f & a_T_TQUOTE){
  802 jput_quote:
  803             ostp = n_string_push_c(ostp, '"');
  804             tp = tcurr;
  805             do/* while tcurr && TATOM||TQUOTE */{
  806                cp = &cp1st[tcurr->t_start];
  807                cpmax = &cp1st[tcurr->t_end];
  808                if(cp == cpmax)
  809                   continue;
  810 
  811                if(tcurr != tp)
  812                   ostp = n_string_push_c(ostp, ' ');
  813 
  814                if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
  815                   ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
  816                else{
  817                   bool_t esc;
  818 
  819                   for(esc = FAL0; cp < cpmax;){
  820                      if((c.c = *cp++) == '\\' && !esc){
  821                         if(cp < cpmax && (*cp == '"' || *cp == '\\'))
  822                            esc = TRU1;
  823                      }else{
  824                         if(esc || c.c == '"'){
  825 jput_quote_esc:
  826                            ostp = n_string_push_c(ostp, '\\');
  827                         }
  828                         ostp = n_string_push_c(ostp, c.c);
  829                         esc = FAL0;
  830                      }
  831                   }
  832                   if(esc){
  833                      c.c = '\\';
  834                      goto jput_quote_esc;
  835                   }
  836                }
  837             }while((tcurr = tcurr->t_next) != NULL &&
  838                (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
  839             ostp = n_string_push_c(ostp, '"');
  840          }else if(tcurr->t_f & a_T_SPECIAL)
  841             goto jput_quote;
  842          else{
  843             /* Can we use a fast join mode? */
  844             for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
  845                if(!(tcurr->t_f & a_T_TATOM))
  846                   break;
  847                if(tcurr != tp)
  848                   ostp = n_string_push_c(ostp, ' ');
  849                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  850                      (tcurr->t_end - tcurr->t_start));
  851             }
  852          }
  853       }
  854 
  855       n_lofi_snap_unroll(lofi_snap);
  856 
  857       agp->ag_input = n_string_cp(ostp);
  858       agp->ag_ilen = ostp->s_len;
  859       /*ostp = n_string_drop_ownership(ostp);*/
  860 
  861       /* Name and domain must be non-empty, the second */
  862       cp = &agp->ag_input[agp->ag_iaddr_start];
  863       cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  864       if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
  865          c.c = '@';
  866          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
  867          goto jleave;
  868       }
  869 
  870       addr = agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
  871 
  872       /* TODO This parser is a mess.  We do not know whether this is truly
  873        * TODO valid, and all our checks are not truly RFC conforming.
  874        * TODO Do check the skinned thing by itself once more, in order
  875        * TODO to catch problems from reordering, e.g., this additional
  876        * TODO test catches a final address without AT..
  877        * TODO This is a plain copy+paste of the weird thing above, no care */
  878       agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
  879       flags &= a_RESET_MASK;
  880       for (p = addr; (c.c = *p++) != '\0';) {
  881          if(c.c == '"')
  882             flags ^= a_IN_QUOTE;
  883          else if (c.u < 040 || c.u >= 0177) {
  884 #ifdef HAVE_IDNA
  885                if(!(flags & a_IN_DOMAIN))
  886 #endif
  887                   break;
  888          } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
  889             if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
  890                break;
  891          } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
  892             /*EMPTY*/;
  893          } else if (c.c == '\\' && *p != '\0') {
  894             ++p;
  895          } else if (c.c == '@') {
  896             if(flags & a_IN_AT){
  897                NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
  898                   c.u);
  899                goto jleave;
  900             }
  901             flags |= a_IN_AT;
  902             agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
  903             flags &= ~a_DOMAIN_MASK;
  904             flags |= (*p == '[') ? a_IN_DOMAIN | a_DOMAIN_V6 : a_IN_DOMAIN;
  905             continue;
  906          } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
  907                c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
  908                c.c == '\\' || c.c == ',' || blankchar(c.c))
  909             break;
  910          flags &= ~a_IN_AT;
  911       }
  912       if(c.c != '\0')
  913          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
  914       else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
  915          /* This is not an address, but if we had seen angle brackets convert
  916           * it to a n_nodename() address if the name is a valid user */
  917 jinsert_domain:
  918          if(cp > &agp->ag_input[0] && cp[-1] == '<' &&
  919                cpmax <= &agp->ag_input[agp->ag_ilen] && cpmax[0] == '>' &&
  920                (!strcmp(addr, ok_vlook(LOGNAME)) || getpwnam(addr) != NULL)){
  921             /* XXX However, if hostname is set to the empty string this
  922              * XXX indicates that the used *mta* will perform the
  923              * XXX auto-expansion instead.  Not so with `addrcodec' though */
  924             agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR;
  925             if(!issingle_hack &&
  926                   (cp = ok_vlook(hostname)) != NULL && *cp == '\0')
  927                agp->ag_n_flags |= NAME_ADDRSPEC_WITHOUT_DOMAIN;
  928             else{
  929                c.ui32 = strlen(cp = n_nodename(TRU1));
  930                /* This is yet IDNA converted.. */
  931                ostp = n_string_creat_auto(&ost);
  932                ostp = n_string_assign_buf(ostp, agp->ag_input, agp->ag_ilen);
  933                ostp = n_string_insert_c(ostp, agp->ag_iaddr_aend++, '@');
  934                ostp = n_string_insert_buf(ostp, agp->ag_iaddr_aend, cp,
  935                      c.ui32);
  936                agp->ag_iaddr_aend += c.ui32;
  937                agp->ag_input = n_string_cp(ostp);
  938                agp->ag_ilen = ostp->s_len;
  939                /*ostp = n_string_drop_ownership(ostp);*/
  940 
  941                cp = &agp->ag_input[agp->ag_iaddr_start];
  942                cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  943                agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
  944             }
  945          }else
  946             NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
  947                '@');
  948       }
  949    }
  950 
  951 jleave:
  952 #ifdef HAVE_IDNA
  953    if(!(agp->ag_n_flags & NAME_ADDRSPEC_INVALID) && (flags & a_IDNA_APPLY))
  954       agp = a_head_idna_apply(agp);
  955 #endif
  956    NYD_LEAVE;
  957    return !(agp->ag_n_flags & NAME_ADDRSPEC_INVALID);
  958 }
  959 
  960 static long
  961 a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem, char **colon)
  962 {
  963    char *line2 = NULL, *cp, *cp2;
  964    size_t line2size = 0;
  965    int c, isenc;
  966    NYD2_ENTER;
  967 
  968    if (*linebuf == NULL)
  969       *linebuf = srealloc(*linebuf, *linesize = 1);
  970    **linebuf = '\0';
  971    for (;;) {
  972       if (--rem < 0) {
  973          rem = -1;
  974          break;
  975       }
  976       if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
  977          rem = -1;
  978          break;
  979       }
  980       for (cp = *linebuf; fieldnamechar(*cp); ++cp)
  981          ;
  982       if (cp > *linebuf)
  983          while (blankchar(*cp))
  984             ++cp;
  985       if (*cp != ':' || cp == *linebuf)
  986          continue;
  987 
  988       /* I guess we got a headline.  Handle wraparound */
  989       *colon = cp;
  990       cp = *linebuf + c;
  991       for (;;) {
  992          isenc = 0;
  993          while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
  994             ;
  995          cp++;
  996          if (rem <= 0)
  997             break;
  998          if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
  999             isenc |= 1;
 1000          ungetc(c = getc(f), f);
 1001          if (!blankchar(c))
 1002             break;
 1003          c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
 1004          if (c < 0)
 1005             break;
 1006          --rem;
 1007          for (cp2 = line2; blankchar(*cp2); ++cp2)
 1008             ;
 1009          c -= (int)PTR2SIZE(cp2 - line2);
 1010          if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
 1011             isenc |= 2;
 1012          if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
 1013             size_t diff = PTR2SIZE(cp - *linebuf),
 1014                colondiff = PTR2SIZE(*colon - *linebuf);
 1015             *linebuf = srealloc(*linebuf, *linesize += c + 2);
 1016             cp = &(*linebuf)[diff];
 1017             *colon = &(*linebuf)[colondiff];
 1018          }
 1019          if (isenc != 3)
 1020             *cp++ = ' ';
 1021          memcpy(cp, cp2, c);
 1022          cp += c;
 1023       }
 1024       *cp = '\0';
 1025 
 1026       if (line2 != NULL)
 1027          free(line2);
 1028       break;
 1029    }
 1030    NYD2_LEAVE;
 1031    return rem;
 1032 }
 1033 
 1034 static int
 1035 msgidnextc(char const **cp, int *status)
 1036 {
 1037    int c;
 1038    NYD2_ENTER;
 1039 
 1040    assert(cp != NULL);
 1041    assert(*cp != NULL);
 1042    assert(status != NULL);
 1043 
 1044    for (;;) {
 1045       if (*status & 01) {
 1046          if (**cp == '"') {
 1047             *status &= ~01;
 1048             (*cp)++;
 1049             continue;
 1050          }
 1051          if (**cp == '\\') {
 1052             (*cp)++;
 1053             if (**cp == '\0')
 1054                goto jeof;
 1055          }
 1056          goto jdfl;
 1057       }
 1058       switch (**cp) {
 1059       case '(':
 1060          *cp = skip_comment(&(*cp)[1]);
 1061          continue;
 1062       case '>':
 1063       case '\0':
 1064 jeof:
 1065          c = '\0';
 1066          goto jleave;
 1067       case '"':
 1068          (*cp)++;
 1069          *status |= 01;
 1070          continue;
 1071       case '@':
 1072          *status |= 02;
 1073          /*FALLTHRU*/
 1074       default:
 1075 jdfl:
 1076          c = *(*cp)++ & 0377;
 1077          c = (*status & 02) ? lowerconv(c) : c;
 1078          goto jleave;
 1079       }
 1080    }
 1081 jleave:
 1082    NYD2_LEAVE;
 1083    return c;
 1084 }
 1085 
 1086 static int
 1087 charcount(char *str, int c)
 1088 {
 1089    char *cp;
 1090    int i;
 1091    NYD2_ENTER;
 1092 
 1093    for (i = 0, cp = str; *cp; ++cp)
 1094       if (*cp == c)
 1095          ++i;
 1096    NYD2_LEAVE;
 1097    return i;
 1098 }
 1099 
 1100 static char const *
 1101 nexttoken(char const *cp)
 1102 {
 1103    NYD2_ENTER;
 1104    for (;;) {
 1105       if (*cp == '\0') {
 1106          cp = NULL;
 1107          break;
 1108       }
 1109 
 1110       if (*cp == '(') {
 1111          size_t nesting = 1;
 1112 
 1113          do switch (*++cp) {
 1114          case '(':
 1115             ++nesting;
 1116             break;
 1117          case ')':
 1118             --nesting;
 1119             break;
 1120          } while (nesting > 0 && *cp != '\0'); /* XXX error? */
 1121       } else if (blankchar(*cp) || *cp == ',')
 1122          ++cp;
 1123       else
 1124          break;
 1125    }
 1126    NYD2_LEAVE;
 1127    return cp;
 1128 }
 1129 
 1130 FL char const *
 1131 myaddrs(struct header *hp) /* TODO */
 1132 {
 1133    struct name *np;
 1134    char const *rv, *mta;
 1135    NYD_ENTER;
 1136 
 1137    if (hp != NULL && (np = hp->h_from) != NULL) {
 1138       if ((rv = np->n_fullname) != NULL)
 1139          goto jleave;
 1140       if ((rv = np->n_name) != NULL)
 1141          goto jleave;
 1142    }
 1143 
 1144    /* Verified once variable had been set */
 1145    if((rv = ok_vlook(from)) != NULL)
 1146       goto jleave;
 1147 
 1148    /* When invoking *sendmail* directly, it's its task to generate an otherwise
 1149     * undeterminable From: address.  However, if the user sets *hostname*,
 1150     * accept his desire */
 1151    if (ok_vlook(hostname) != NULL)
 1152       goto jnodename;
 1153    if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
 1154          /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
 1155          ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
 1156       goto jnodename;
 1157 jleave:
 1158    NYD_LEAVE;
 1159    return rv;
 1160 
 1161 jnodename:{
 1162       char *cp;
 1163       char const *hn, *ln;
 1164       size_t i;
 1165 
 1166       hn = n_nodename(TRU1);
 1167       ln = ok_vlook(LOGNAME);
 1168       i = strlen(ln) + strlen(hn) + 1 +1;
 1169       rv = cp = salloc(i);
 1170       sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
 1171    }
 1172    goto jleave;
 1173 }
 1174 
 1175 FL char const *
 1176 myorigin(struct header *hp) /* TODO */
 1177 {
 1178    char const *rv = NULL, *ccp;
 1179    struct name *np;
 1180    NYD_ENTER;
 1181 
 1182    if((ccp = myaddrs(hp)) != NULL &&
 1183          (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
 1184       if(np->n_flink == NULL)
 1185          rv = ccp;
 1186       /* Verified upon variable set time */
 1187       else if((ccp = ok_vlook(sender)) != NULL)
 1188          rv = ccp;
 1189       /* TODO why not else rv = n_poption_arg_r; ?? */
 1190    }
 1191    NYD_LEAVE;
 1192    return rv;
 1193 }
 1194 
 1195 FL bool_t
 1196 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
 1197 {
 1198    char date[n_FROM_DATEBUF];
 1199    bool_t rv;
 1200    NYD2_ENTER;
 1201 
 1202    if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
 1203          (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
 1204           !_is_date(date)))
 1205       rv = TRUM1;
 1206    NYD2_LEAVE;
 1207    return rv;
 1208 }
 1209 
 1210 FL int
 1211 extract_date_from_from_(char const *line, size_t linelen,
 1212    char datebuf[n_FROM_DATEBUF])
 1213 {
 1214    int rv;
 1215    char const *cp = line;
 1216    NYD_ENTER;
 1217 
 1218    rv = 1;
 1219 
 1220    /* "From " */
 1221    cp = _from__skipword(cp);
 1222    if (cp == NULL)
 1223       goto jerr;
 1224    /* "addr-spec " */
 1225    cp = _from__skipword(cp);
 1226    if (cp == NULL)
 1227       goto jerr;
 1228    if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
 1229          (cp[2] == 'y' || cp[2] == 'Y')){
 1230       cp = _from__skipword(cp);
 1231       if (cp == NULL)
 1232          goto jerr;
 1233    }
 1234    /* It seems there are invalid MBOX archives in the wild, compare
 1235     * . http://bugs.debian.org/624111
 1236     * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
 1237     * What they do is that they obfuscate the address to "name at host",
 1238     * and even "name at host dot dom dot dom.
 1239     * The [Aa][Tt] is also RFC 733, so be tolerant */
 1240    else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
 1241          cp[2] == ' '){
 1242       rv = -1;
 1243       cp += 3;
 1244 jat_dot:
 1245       cp = _from__skipword(cp);
 1246       if (cp == NULL)
 1247          goto jerr;
 1248       if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
 1249             (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
 1250          cp += 4;
 1251          goto jat_dot;
 1252       }
 1253    }
 1254 
 1255    linelen -= PTR2SIZE(cp - line);
 1256    if (linelen < a_HEAD_DATE_MINLEN)
 1257       goto jerr;
 1258    if (cp[linelen - 1] == '\n') {
 1259       --linelen;
 1260       /* (Rather IMAP/POP3 only) */
 1261       if (cp[linelen - 1] == '\r')
 1262          --linelen;
 1263       if (linelen < a_HEAD_DATE_MINLEN)
 1264          goto jerr;
 1265    }
 1266    if (linelen >= n_FROM_DATEBUF)
 1267       goto jerr;
 1268 
 1269 jleave:
 1270    memcpy(datebuf, cp, linelen);
 1271    datebuf[linelen] = '\0';
 1272    NYD_LEAVE;
 1273    return rv;
 1274 jerr:
 1275    cp = _("<Unknown date>");
 1276    linelen = strlen(cp);
 1277    if (linelen >= n_FROM_DATEBUF)
 1278       linelen = n_FROM_DATEBUF;
 1279    rv = 0;
 1280    goto jleave;
 1281 }
 1282 
 1283 FL void
 1284 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
 1285 {
 1286    /* See the prototype declaration for the hairy relationship of
 1287     * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
 1288    struct n_header_field **hftail;
 1289    struct header nh, *hq = &nh;
 1290    char *linebuf = NULL /* TODO line pool */, *colon;
 1291    size_t linesize = 0, seenfields = 0;
 1292    int c;
 1293    long lc;
 1294    char const *val, *cp;
 1295    NYD_ENTER;
 1296 
 1297    memset(hq, 0, sizeof *hq);
 1298    if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
 1299       hq->h_to = hp->h_to;
 1300       hq->h_cc = hp->h_cc;
 1301       hq->h_bcc = hp->h_bcc;
 1302    }
 1303    hftail = &hq->h_user_headers;
 1304 
 1305    for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
 1306       ;
 1307 
 1308    /* TODO yippieia, cat(check(lextract)) :-) */
 1309    rewind(fp);
 1310    while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
 1311       struct name *np;
 1312 
 1313       /* We explicitly allow EAF_NAME for some addressees since aliases are not
 1314        * yet expanded when we parse these! */
 1315       if ((val = thisfield(linebuf, "to")) != NULL) {
 1316          ++seenfields;
 1317          hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
 1318                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1319       } else if ((val = thisfield(linebuf, "cc")) != NULL) {
 1320          ++seenfields;
 1321          hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
 1322                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1323       } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
 1324          ++seenfields;
 1325          hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
 1326                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1327       } else if ((val = thisfield(linebuf, "from")) != NULL) {
 1328          if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
 1329             ++seenfields;
 1330             hq->h_from = cat(hq->h_from,
 1331                   checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
 1332                      EACM_STRICT, NULL));
 1333          }
 1334       } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
 1335          ++seenfields;
 1336          hq->h_reply_to = cat(hq->h_reply_to,
 1337                checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
 1338       } else if ((val = thisfield(linebuf, "sender")) != NULL) {
 1339          if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
 1340             ++seenfields;
 1341             hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
 1342                   checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
 1343                      EACM_STRICT, NULL));
 1344          } else
 1345             goto jebadhead;
 1346       } else if ((val = thisfield(linebuf, "subject")) != NULL ||
 1347             (val = thisfield(linebuf, "subj")) != NULL) {
 1348          ++seenfields;
 1349          for (cp = val; blankchar(*cp); ++cp)
 1350             ;
 1351          hq->h_subject = (hq->h_subject != NULL)
 1352                ? save2str(hq->h_subject, cp) : savestr(cp);
 1353       }
 1354       /* The remaining are mostly hacked in and thus TODO -- at least in
 1355        * TODO respect to their content checking */
 1356       else if((val = thisfield(linebuf, "message-id")) != NULL){
 1357          if(n_psonce & n_PSO_t_FLAG){
 1358             np = checkaddrs(lextract(val, GREF),
 1359                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1360                   NULL);
 1361             if (np == NULL || np->n_flink != NULL)
 1362                goto jebadhead;
 1363             ++seenfields;
 1364             hq->h_message_id = np;
 1365          }else
 1366             goto jebadhead;
 1367       }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
 1368          if(n_psonce & n_PSO_t_FLAG){
 1369             np = checkaddrs(lextract(val, GREF),
 1370                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1371                   NULL);
 1372             ++seenfields;
 1373             hq->h_in_reply_to = np;
 1374          }else
 1375             goto jebadhead;
 1376       }else if((val = thisfield(linebuf, "references")) != NULL){
 1377          if(n_psonce & n_PSO_t_FLAG){
 1378             ++seenfields;
 1379             /* TODO Limit number of references TODO better on parser side */
 1380             hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
 1381                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1382                   NULL));
 1383          }else
 1384             goto jebadhead;
 1385       }
 1386       /* and that is very hairy */
 1387       else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
 1388          if(n_psonce & n_PSO_t_FLAG){
 1389             ++seenfields;
 1390             hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
 1391                   /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
 1392                   checkaddr_err));
 1393          }else
 1394             goto jebadhead;
 1395       }
 1396       /* A free-form header; a_gethfield() did some verification already.. */
 1397       else{
 1398          struct n_header_field *hfp;
 1399          ui32_t nl, bl;
 1400          char const *nstart;
 1401 
 1402          for(nstart = cp = linebuf;; ++cp)
 1403             if(!fieldnamechar(*cp))
 1404                break;
 1405          nl = (ui32_t)PTR2SIZE(cp - nstart);
 1406 
 1407          while(blankchar(*cp))
 1408             ++cp;
 1409          if(*cp++ != ':'){
 1410 jebadhead:
 1411             n_err(_("Ignoring header field: %s\n"), linebuf);
 1412             continue;
 1413          }
 1414          while(blankchar(*cp))
 1415             ++cp;
 1416          bl = (ui32_t)strlen(cp) +1;
 1417 
 1418          ++seenfields;
 1419          *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
 1420                ) + nl +1 + bl);
 1421             hftail = &hfp->hf_next;
 1422          hfp->hf_next = NULL;
 1423          hfp->hf_nl = nl;
 1424          hfp->hf_bl = bl - 1;
 1425          memcpy(hfp->hf_dat, nstart, nl);
 1426             hfp->hf_dat[nl++] = '\0';
 1427             memcpy(hfp->hf_dat + nl, cp, bl);
 1428       }
 1429    }
 1430 
 1431    /* In case the blank line after the header has been edited out.  Otherwise,
 1432     * fetch the header separator */
 1433    if (linebuf != NULL) {
 1434       if (linebuf[0] != '\0') {
 1435          for (cp = linebuf; *(++cp) != '\0';)
 1436             ;
 1437          fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
 1438       } else {
 1439          if ((c = getc(fp)) != '\n' && c != EOF)
 1440             ungetc(c, fp);
 1441       }
 1442    }
 1443 
 1444    if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
 1445       hp->h_to = hq->h_to;
 1446       hp->h_cc = hq->h_cc;
 1447       hp->h_bcc = hq->h_bcc;
 1448       hp->h_from = hq->h_from;
 1449       hp->h_reply_to = hq->h_reply_to;
 1450       hp->h_sender = hq->h_sender;
 1451       if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
 1452             !(n_poption & n_PO_t_FLAG))
 1453          hp->h_subject = hq->h_subject;
 1454       hp->h_user_headers = hq->h_user_headers;
 1455 
 1456       if (n_psonce & n_PSO_t_FLAG) {
 1457          hp->h_ref = hq->h_ref;
 1458          hp->h_message_id = hq->h_message_id;
 1459          hp->h_in_reply_to = hq->h_in_reply_to;
 1460          hp->h_mft = hq->h_mft;
 1461 
 1462          /* And perform additional validity checks so that we don't bail later
 1463           * on TODO this is good and the place where this should occur,
 1464           * TODO unfortunately a lot of other places do again and blabla */
 1465          if (hp->h_from == NULL)
 1466             hp->h_from = n_poption_arg_r;
 1467          else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
 1468             hp->h_sender = lextract(ok_vlook(sender),
 1469                   GEXTRA | GFULL | GFULLEXTRA);
 1470       }
 1471    } else
 1472       n_err(_("Restoring deleted header lines\n"));
 1473 
 1474    if (linebuf != NULL)
 1475       free(linebuf);
 1476    NYD_LEAVE;
 1477 }
 1478 
 1479 FL char *
 1480 hfield_mult(char const *field, struct message *mp, int mult)
 1481 {
 1482    FILE *ibuf;
 1483    struct str hfs;
 1484    long lc;
 1485    size_t linesize = 0; /* TODO line pool */
 1486    char *linebuf = NULL, *colon;
 1487    char const *hfield;
 1488    NYD_ENTER;
 1489 
 1490    /* There are (spam) messages which have header bytes which are many KB when
 1491     * joined, so resize a single heap storage until we are done if we shall
 1492     * collect a field that may have multiple bodies; only otherwise use the
 1493     * string dope directly */
 1494    memset(&hfs, 0, sizeof hfs);
 1495 
 1496    if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 1497       goto jleave;
 1498    if ((lc = mp->m_lines - 1) < 0)
 1499       goto jleave;
 1500 
 1501    if ((mp->m_flag & MNOFROM) == 0 &&
 1502          readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 1503       goto jleave;
 1504    while (lc > 0) {
 1505       if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
 1506          break;
 1507       if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
 1508          if (mult)
 1509             n_str_add_buf(&hfs, hfield, strlen(hfield));
 1510          else {
 1511             hfs.s = savestr(hfield);
 1512             break;
 1513          }
 1514       }
 1515    }
 1516 
 1517 jleave:
 1518    if (linebuf != NULL)
 1519       free(linebuf);
 1520    if (mult && hfs.s != NULL) {
 1521       colon = savestrbuf(hfs.s, hfs.l);
 1522       free(hfs.s);
 1523       hfs.s = colon;
 1524    }
 1525    NYD_LEAVE;
 1526    return hfs.s;
 1527 }
 1528 
 1529 FL char const *
 1530 thisfield(char const *linebuf, char const *field)
 1531 {
 1532    char const *rv = NULL;
 1533    NYD2_ENTER;
 1534 
 1535    while (lowerconv(*linebuf) == lowerconv(*field)) {
 1536       ++linebuf;
 1537       ++field;
 1538    }
 1539    if (*field != '\0')
 1540       goto jleave;
 1541 
 1542    while (blankchar(*linebuf))
 1543       ++linebuf;
 1544    if (*linebuf++ != ':')
 1545       goto jleave;
 1546 
 1547    while (blankchar(*linebuf)) /* TODO header parser..  strip trailing WS?!? */
 1548       ++linebuf;
 1549    rv = linebuf;
 1550 jleave:
 1551    NYD2_LEAVE;
 1552    return rv;
 1553 }
 1554 
 1555 FL char *
 1556 nameof(struct message *mp, int reptype)
 1557 {
 1558    char *cp, *cp2;
 1559    NYD_ENTER;
 1560 
 1561    cp = skin(name1(mp, reptype));
 1562    if (reptype != 0 || charcount(cp, '!') < 2)
 1563       goto jleave;
 1564    cp2 = strrchr(cp, '!');
 1565    --cp2;
 1566    while (cp2 > cp && *cp2 != '!')
 1567       --cp2;
 1568    if (*cp2 == '!')
 1569       cp = cp2 + 1;
 1570 jleave:
 1571    NYD_LEAVE;
 1572    return cp;
 1573 }
 1574 
 1575 FL char const *
 1576 skip_comment(char const *cp)
 1577 {
 1578    size_t nesting;
 1579    NYD_ENTER;
 1580 
 1581    for (nesting = 1; nesting > 0 && *cp; ++cp) {
 1582       switch (*cp) {
 1583       case '\\':
 1584          if (cp[1])
 1585             ++cp;
 1586          break;
 1587       case '(':
 1588          ++nesting;
 1589          break;
 1590       case ')':
 1591          --nesting;
 1592          break;
 1593       }
 1594    }
 1595    NYD_LEAVE;
 1596    return cp;
 1597 }
 1598 
 1599 FL char const *
 1600 routeaddr(char const *name)
 1601 {
 1602    char const *np, *rp = NULL;
 1603    NYD_ENTER;
 1604 
 1605    for (np = name; *np; np++) {
 1606       switch (*np) {
 1607       case '(':
 1608          np = skip_comment(np + 1) - 1;
 1609          break;
 1610       case '"':
 1611          while (*np) {
 1612             if (*++np == '"')
 1613                break;
 1614             if (*np == '\\' && np[1])
 1615                np++;
 1616          }
 1617          break;
 1618       case '<':
 1619          rp = np;
 1620          break;
 1621       case '>':
 1622          goto jleave;
 1623       }
 1624    }
 1625    rp = NULL;
 1626 jleave:
 1627    NYD_LEAVE;
 1628    return rp;
 1629 }
 1630 
 1631 FL enum expand_addr_flags
 1632 expandaddr_to_eaf(void)
 1633 {
 1634    struct eafdesc {
 1635       char const  *eafd_name;
 1636       bool_t      eafd_is_target;
 1637       ui8_t       eafd_andoff;
 1638       ui8_t       eafd_or;
 1639    } const eafa[] = {
 1640       {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
 1641       {"fail", FAL0, EAF_NONE, EAF_FAIL},
 1642       {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
 1643       {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
 1644          {"file", TRU1, EAF_NONE, EAF_FILE},
 1645          {"pipe", TRU1, EAF_NONE, EAF_PIPE},
 1646          {"name", TRU1, EAF_NONE, EAF_NAME},
 1647          {"addr", TRU1, EAF_NONE, EAF_ADDR}
 1648    }, *eafp;
 1649 
 1650    char *buf;
 1651    enum expand_addr_flags rv;
 1652    char const *cp;
 1653    NYD2_ENTER;
 1654 
 1655    if ((cp = ok_vlook(expandaddr)) == NULL)
 1656       rv = EAF_RESTRICT_TARGETS;
 1657    else if (*cp == '\0')
 1658       rv = EAF_TARGET_MASK;
 1659    else {
 1660       rv = EAF_TARGET_MASK;
 1661 
 1662       for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
 1663          bool_t minus;
 1664 
 1665          if ((minus = (*cp == '-')) || *cp == '+')
 1666             ++cp;
 1667          for (eafp = eafa;; ++eafp) {
 1668             if (eafp == eafa + n_NELEM(eafa)) {
 1669                if (n_poption & n_PO_D_V)
 1670                   n_err(_("Unknown *expandaddr* value: %s\n"), cp);
 1671                break;
 1672             } else if (!asccasecmp(cp, eafp->eafd_name)) {
 1673                if (!minus) {
 1674                   rv &= ~eafp->eafd_andoff;
 1675                   rv |= eafp->eafd_or;
 1676                } else {
 1677                   if (eafp->eafd_is_target)
 1678                      rv &= ~eafp->eafd_or;
 1679                   else if (n_poption & n_PO_D_V)
 1680                      n_err(_("minus - prefix invalid for *expandaddr* value: "
 1681                         "%s\n"), --cp);
 1682                }
 1683                break;
 1684             } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
 1685                n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
 1686                rv &= ~EAF_NAME;
 1687                break;
 1688             }
 1689          }
 1690       }
 1691 
 1692       if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
 1693             (n_poption & n_PO_TILDE_FLAG)))
 1694          rv |= EAF_TARGET_MASK;
 1695       else if(n_poption & n_PO_D_V){
 1696          if(!(rv & EAF_TARGET_MASK))
 1697             n_err(_("*expandaddr* doesn't allow any addressees\n"));
 1698          else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
 1699             n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
 1700       }
 1701    }
 1702    NYD2_LEAVE;
 1703    return rv;
 1704 }
 1705 
 1706 FL si8_t
 1707 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
 1708 {
 1709    char cbuf[sizeof "'\\U12340'"];
 1710    char const *cs;
 1711    int f;
 1712    si8_t rv;
 1713    enum expand_addr_flags eaf;
 1714    NYD_ENTER;
 1715 
 1716    eaf = expandaddr_to_eaf();
 1717    f = np->n_flags;
 1718 
 1719    if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
 1720       if (eaf & EAF_FAILINVADDR)
 1721          rv = -rv;
 1722 
 1723       if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
 1724          ;
 1725       } else {
 1726          ui32_t c;
 1727          char const *fmt = "'\\x%02X'";
 1728          bool_t ok8bit = TRU1;
 1729 
 1730          if (f & NAME_ADDRSPEC_ERR_IDNA) {
 1731             cs = _("Invalid domain name: %s, character %s\n");
 1732             fmt = "'\\U%04X'";
 1733             ok8bit = FAL0;
 1734          } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
 1735             cs = _("%s contains invalid %s sequence\n");
 1736          else if (f & NAME_ADDRSPEC_ERR_NAME) {
 1737             cs = _("%s is an invalid alias name\n");
 1738          } else
 1739             cs = _("%s contains invalid byte %s\n");
 1740 
 1741          c = NAME_ADDRSPEC_ERR_GETWC(f);
 1742          snprintf(cbuf, sizeof cbuf,
 1743             (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
 1744          goto jprint;
 1745       }
 1746       goto jleave;
 1747    }
 1748 
 1749    /* *expandaddr* stuff */
 1750    if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
 1751       goto jleave;
 1752 
 1753    if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
 1754       if (eaf & EAF_FAIL)
 1755          rv = -rv;
 1756       cs = _("%s%s: file or pipe addressees not allowed here\n");
 1757       if (eacm & EACM_NOLOG)
 1758          goto jleave;
 1759       else
 1760          goto j0print;
 1761    }
 1762 
 1763    eaf |= (eacm & EAF_TARGET_MASK);
 1764    if (eacm & EACM_NONAME)
 1765       eaf &= ~EAF_NAME;
 1766 
 1767    if (eaf == EAF_NONE) {
 1768       rv = FAL0;
 1769       goto jleave;
 1770    }
 1771    if (eaf & EAF_FAIL)
 1772       rv = -rv;
 1773 
 1774    if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
 1775       cs = _("%s%s: *expandaddr* doesn't allow file target\n");
 1776       if (eacm & EACM_NOLOG)
 1777          goto jleave;
 1778    } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
 1779       cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
 1780       if (eacm & EACM_NOLOG)
 1781          goto jleave;
 1782    } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
 1783       cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
 1784       if (eacm & EACM_NOLOG)
 1785          goto jleave;
 1786    } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
 1787       cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
 1788       if (eacm & EACM_NOLOG)
 1789          goto jleave;
 1790    } else {
 1791       rv = FAL0;
 1792       goto jleave;
 1793    }
 1794 
 1795 j0print:
 1796    cbuf[0] = '\0';
 1797 jprint:
 1798    n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
 1799 jleave:
 1800    NYD_LEAVE;
 1801    return rv;
 1802 }
 1803 
 1804 FL char *
 1805 skin(char const *name)
 1806 {
 1807    struct n_addrguts ag;
 1808    char *rv;
 1809    NYD_ENTER;
 1810 
 1811    if(name != NULL){
 1812       /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
 1813       rv = ag.ag_skinned;
 1814       if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
 1815          rv = savestrbuf(rv, ag.ag_slen);
 1816    }else
 1817       rv = NULL;
 1818    NYD_LEAVE;
 1819    return rv;
 1820 }
 1821 
 1822 /* TODO addrspec_with_guts: RFC 5322
 1823  * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
 1824 FL char const *
 1825 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
 1826       bool_t issingle_hack){
 1827    char const *cp;
 1828    char *cp2, *bufend, *nbuf, c;
 1829    enum{
 1830       a_NONE,
 1831       a_GOTLT = 1<<0,
 1832       a_GOTADDR = 1<<1,
 1833       a_GOTSPACE = 1<<2,
 1834       a_LASTSP = 1<<3
 1835    } flags;
 1836    NYD_ENTER;
 1837 
 1838    memset(agp, 0, sizeof *agp);
 1839 
 1840    if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
 1841       agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
 1842       agp->ag_slen = 0;
 1843       NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
 1844       goto jleave;
 1845    }else if(!doskin){
 1846       /*agp->ag_iaddr_start = 0;*/
 1847       agp->ag_iaddr_aend = agp->ag_ilen;
 1848       agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
 1849       agp->ag_slen = agp->ag_ilen;
 1850       agp->ag_n_flags = NAME_SKINNED;
 1851       goto jcheck;
 1852    }
 1853 
 1854    flags = a_NONE;
 1855    nbuf = n_lofi_alloc(agp->ag_ilen +1);
 1856    /*agp->ag_iaddr_start = 0;*/
 1857    cp2 = bufend = nbuf;
 1858 
 1859    /* TODO This is complete crap and should use a token parser */
 1860    for(cp = name++; (c = *cp++) != '\0';){
 1861       switch (c) {
 1862       case '(':
 1863          cp = skip_comment(cp);
 1864          flags &= ~a_LASTSP;
 1865          break;
 1866       case '"':
 1867          /* Start of a "quoted-string".  Copy it in its entirety */
 1868          /* XXX RFC: quotes are "semantically invisible"
 1869           * XXX But it was explicitly added (Changelog.Heirloom,
 1870           * XXX [9.23] released 11/15/00, "Do not remove quotes
 1871           * XXX when skinning names"?  No more info.. */
 1872          *cp2++ = c;
 1873          while ((c = *cp) != '\0') { /* TODO improve */
 1874             ++cp;
 1875             if (c == '"') {
 1876                *cp2++ = c;
 1877                break;
 1878             }
 1879             if (c != '\\')
 1880                *cp2++ = c;
 1881             else if ((c = *cp) != '\0') {
 1882                *cp2++ = c;
 1883                ++cp;
 1884             }
 1885          }
 1886          flags &= ~a_LASTSP;
 1887          break;
 1888       case ' ':
 1889       case '\t':
 1890          if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
 1891             flags |= a_GOTSPACE;
 1892             agp->ag_iaddr_aend = PTR2SIZE(cp - name);
 1893          }
 1894          if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
 1895             cp += 3, *cp2++ = '@';
 1896          else if (cp[0] == '@' && blankchar(cp[1]))
 1897             cp += 2, *cp2++ = '@';
 1898          else
 1899             flags |= a_LASTSP;
 1900          break;
 1901       case '<':
 1902          agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
 1903          cp2 = bufend;
 1904          flags &= ~(a_GOTSPACE | a_LASTSP);
 1905          flags |= a_GOTLT | a_GOTADDR;
 1906          break;
 1907       case '>':
 1908          if(flags & a_GOTLT){
 1909             /* (_addrspec_check() verifies these later!) */
 1910             flags &= ~(a_GOTLT | a_LASTSP);
 1911             agp->ag_iaddr_aend = PTR2SIZE(cp - name);
 1912 
 1913             /* Skip over the entire remaining field */
 1914             while((c = *cp) != '\0' && c != ','){
 1915                ++cp;
 1916                if (c == '(')
 1917                   cp = skip_comment(cp);
 1918                else if (c == '"')
 1919                   while ((c = *cp) != '\0') {
 1920                      ++cp;
 1921                      if (c == '"')
 1922                         break;
 1923                      if (c == '\\' && *cp != '\0')
 1924                         ++cp;
 1925                   }
 1926             }
 1927             break;
 1928          }
 1929          /* FALLTHRU */
 1930       default:
 1931          if(flags & a_LASTSP){
 1932             flags &= ~a_LASTSP;
 1933             if(flags & a_GOTADDR)
 1934                *cp2++ = ' ';
 1935          }
 1936          *cp2++ = c;
 1937          /* This character is forbidden here, but it may nonetheless be
 1938           * present: ensure we turn this into something valid!  (E.g., if the
 1939           * next character would be a "..) */
 1940          if(c == '\\' && *cp != '\0')
 1941             *cp2++ = *cp++;
 1942          if(c == ',' && !issingle_hack){
 1943             if(!(flags & a_GOTLT)){
 1944                *cp2++ = ' ';
 1945                for(; blankchar(*cp); ++cp)
 1946                   ;
 1947                flags &= ~a_LASTSP;
 1948                bufend = cp2;
 1949             }
 1950          }else if(!(flags & a_GOTADDR)){
 1951             flags |= a_GOTADDR;
 1952             agp->ag_iaddr_start = PTR2SIZE(cp - name);
 1953          }
 1954       }
 1955    }
 1956    --name;
 1957    agp->ag_slen = PTR2SIZE(cp2 - nbuf);
 1958    if (agp->ag_iaddr_aend == 0)
 1959       agp->ag_iaddr_aend = agp->ag_ilen;
 1960    /* Misses > */
 1961    else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
 1962       cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
 1963       memcpy(cp2, agp->ag_input, agp->ag_ilen);
 1964       agp->ag_iaddr_aend = agp->ag_ilen;
 1965       cp2[agp->ag_ilen++] = '>';
 1966       cp2[agp->ag_ilen] = '\0';
 1967    }
 1968    agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
 1969    n_lofi_free(nbuf);
 1970    agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
 1971 jcheck:
 1972    if(a_head_addrspec_check(agp, doskin, issingle_hack) <= FAL0)
 1973       name = NULL;
 1974    else
 1975       name = agp->ag_input;
 1976 jleave:
 1977    NYD_LEAVE;
 1978    return name;
 1979 }
 1980 
 1981 FL char *
 1982 realname(char const *name)
 1983 {
 1984    char const *cp, *cq, *cstart = NULL, *cend = NULL;
 1985    char *rname, *rp;
 1986    struct str in, out;
 1987    int quoted, good, nogood;
 1988    NYD_ENTER;
 1989 
 1990    if ((cp = n_UNCONST(name)) == NULL)
 1991       goto jleave;
 1992    for (; *cp != '\0'; ++cp) {
 1993       switch (*cp) {
 1994       case '(':
 1995          if (cstart != NULL) {
 1996             /* More than one comment in address, doesn't make sense to display
 1997              * it without context.  Return the entire field */
 1998             cp = mime_fromaddr(name);
 1999             goto jleave;
 2000          }
 2001          cstart = cp++;
 2002          cp = skip_comment(cp);
 2003          cend = cp--;
 2004          if (cend <= cstart)
 2005             cend = cstart = NULL;
 2006          break;
 2007       case '"':
 2008          while (*cp) {
 2009             if (*++cp == '"')
 2010                break;
 2011             if (*cp == '\\' && cp[1])
 2012                ++cp;
 2013          }
 2014          break;
 2015       case '<':
 2016          if (cp > name) {
 2017             cstart = name;
 2018             cend = cp;
 2019          }
 2020          break;
 2021       case ',':
 2022          /* More than one address. Just use the first one */
 2023          goto jbrk;
 2024       }
 2025    }
 2026 
 2027 jbrk:
 2028    if (cstart == NULL) {
 2029       if (*name == '<') {
 2030          /* If name contains only a route-addr, the surrounding angle brackets
 2031           * don't serve any useful purpose when displaying, so remove */
 2032          cp = prstr(skin(name));
 2033       } else
 2034          cp = mime_fromaddr(name);
 2035       goto jleave;
 2036    }
 2037 
 2038    /* Strip quotes. Note that quotes that appear within a MIME encoded word are
 2039     * not stripped. The idea is to strip only syntactical relevant things (but
 2040     * this is not necessarily the most sensible way in practice) */
 2041    rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
 2042    quoted = 0;
 2043    for (cp = cstart; cp < cend; ++cp) {
 2044       if (*cp == '(' && !quoted) {
 2045          cq = skip_comment(++cp);
 2046          if (PTRCMP(--cq, >, cend))
 2047             cq = cend;
 2048          while (cp < cq) {
 2049             if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
 2050                ++cp;
 2051             *rp++ = *cp++;
 2052          }
 2053       } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
 2054          *rp++ = *++cp;
 2055       else if (*cp == '"') {
 2056          quoted = !quoted;
 2057          continue;
 2058       } else
 2059          *rp++ = *cp;
 2060    }
 2061    *rp = '\0';
 2062    in.s = rname;
 2063    in.l = rp - rname;
 2064    mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
 2065    ac_free(rname);
 2066    rname = savestr(out.s);
 2067    free(out.s);
 2068 
 2069    while (blankchar(*rname))
 2070       ++rname;
 2071    for (rp = rname; *rp != '\0'; ++rp)
 2072       ;
 2073    while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
 2074       *rp = '\0';
 2075    if (rp == rname) {
 2076       cp = mime_fromaddr(name);
 2077       goto jleave;
 2078    }
 2079 
 2080    /* mime_fromhdr() has converted all nonprintable characters to question
 2081     * marks now. These and blanks are considered uninteresting; if the
 2082     * displayed part of the real name contains more than 25% of them, it is
 2083     * probably better to display the plain email address instead */
 2084    good = 0;
 2085    nogood = 0;
 2086    for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
 2087       if (*rp == '?' || blankchar(*rp))
 2088          ++nogood;
 2089       else
 2090          ++good;
 2091    cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
 2092 jleave:
 2093    NYD_LEAVE;
 2094    return n_UNCONST(cp);
 2095 }
 2096 
 2097 FL char *
 2098 name1(struct message *mp, int reptype)
 2099 {
 2100    char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
 2101    size_t namesize, linesize = 0;
 2102    FILE *ibuf;
 2103    int f1st = 1;
 2104    NYD_ENTER;
 2105 
 2106    if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
 2107       goto jleave;
 2108    if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
 2109       goto jleave;
 2110 
 2111    namebuf = smalloc(namesize = 1);
 2112    namebuf[0] = 0;
 2113    if (mp->m_flag & MNOFROM)
 2114       goto jout;
 2115    if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 2116       goto jout;
 2117    if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 2118       goto jout;
 2119 
 2120 jnewname:
 2121    if (namesize <= linesize)
 2122       namebuf = srealloc(namebuf, namesize = linesize +1);
 2123    for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
 2124       ;
 2125    for (; blankchar(*cp); ++cp)
 2126       ;
 2127    for (cp2 = namebuf + strlen(namebuf);
 2128         *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
 2129       *cp2++ = *cp++;
 2130    *cp2 = '\0';
 2131 
 2132    if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 2133       goto jout;
 2134    if ((cp = strchr(linebuf, 'F')) == NULL)
 2135       goto jout;
 2136    if (strncmp(cp, "From", 4))
 2137       goto jout;
 2138    if (namesize <= linesize)
 2139       namebuf = srealloc(namebuf, namesize = linesize + 1);
 2140 
 2141    while ((cp = strchr(cp, 'r')) != NULL) {
 2142       if (!strncmp(cp, "remote", 6)) {
 2143          if ((cp = strchr(cp, 'f')) == NULL)
 2144             break;
 2145          if (strncmp(cp, "from", 4) != 0)
 2146             break;
 2147          if ((cp = strchr(cp, ' ')) == NULL)
 2148             break;
 2149          cp++;
 2150          if (f1st) {
 2151             strncpy(namebuf, cp, namesize);
 2152             f1st = 0;
 2153          } else {
 2154             cp2 = strrchr(namebuf, '!') + 1;
 2155             strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
 2156          }
 2157          namebuf[namesize - 2] = '!';
 2158          namebuf[namesize - 1] = '\0';
 2159          goto jnewname;
 2160       }
 2161       cp++;
 2162    }
 2163 jout:
 2164    if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
 2165          *cp == '\0')
 2166       cp = savestr(namebuf);
 2167 
 2168    if (linebuf != NULL)
 2169       free(linebuf);
 2170    free(namebuf);
 2171 jleave:
 2172    NYD_LEAVE;
 2173    return cp;
 2174 }
 2175 
 2176 FL char const *
 2177 subject_re_trim(char const *s){
 2178    struct{
 2179       ui8_t len;
 2180       char  dat[7];
 2181    }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
 2182       {3, "re:"},
 2183       {3, "aw:"}, {5, "antw:"}, /* de */
 2184       {3, "wg:"}, /* Seen too often in the wild */
 2185       {0, ""}
 2186    };
 2187 
 2188    bool_t any;
 2189    char *re_st, *re_st_x;
 2190    char const *orig_s;
 2191    size_t re_l;
 2192    NYD_ENTER;
 2193 
 2194    any = FAL0;
 2195    orig_s = s;
 2196    re_st = NULL;
 2197    n_UNINIT(re_l, 0);
 2198 
 2199    if((re_st_x = ok_vlook(reply_strings)) != NULL &&
 2200          (re_l = strlen(re_st_x)) > 0){
 2201       re_st = n_lofi_alloc(++re_l * 2);
 2202       memcpy(re_st, re_st_x, re_l);
 2203    }
 2204 
 2205 jouter:
 2206    while(*s != '\0'){
 2207       while(spacechar(*s))
 2208          ++s;
 2209 
 2210       for(pp = ignored; pp->len > 0; ++pp)
 2211          if(is_asccaseprefix(pp->dat, s)){
 2212             s += pp->len;
 2213             any = TRU1;
 2214             goto jouter;
 2215          }
 2216 
 2217       if(re_st != NULL){
 2218          char *cp;
 2219 
 2220          memcpy(re_st_x = &re_st[re_l], re_st, re_l);
 2221          while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
 2222             if(is_asccaseprefix(cp, s)){
 2223                s += strlen(cp);
 2224                any = TRU1;
 2225                goto jouter;
 2226             }
 2227       }
 2228       break;
 2229    }
 2230 
 2231    if(re_st != NULL)
 2232       n_lofi_free(re_st);
 2233    NYD_LEAVE;
 2234    return any ? s : orig_s;
 2235 }
 2236 
 2237 FL int
 2238 msgidcmp(char const *s1, char const *s2)
 2239 {
 2240    int q1 = 0, q2 = 0, c1, c2;
 2241    NYD_ENTER;
 2242 
 2243    while(*s1 == '<')
 2244       ++s1;
 2245    while(*s2 == '<')
 2246       ++s2;
 2247 
 2248    do {
 2249       c1 = msgidnextc(&s1, &q1);
 2250       c2 = msgidnextc(&s2, &q2);
 2251       if (c1 != c2)
 2252          break;
 2253    } while (c1 && c2);
 2254    NYD_LEAVE;
 2255    return c1 - c2;
 2256 }
 2257 
 2258 FL char const *
 2259 fakefrom(struct message *mp)
 2260 {
 2261    char const *name;
 2262    NYD_ENTER;
 2263 
 2264    if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
 2265          ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
 2266       /* XXX MAILER-DAEMON is what an old MBOX manual page says.
 2267        * RFC 4155 however requires a RFC 5322 (2822) conforming
 2268        * "addr-spec", but we simply can't provide that */
 2269       name = "MAILER-DAEMON";
 2270    NYD_LEAVE;
 2271    return name;
 2272 }
 2273 
 2274 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
 2275 FL time_t
 2276 unixtime(char const *fromline)
 2277 {
 2278    char const *fp, *xp;
 2279    time_t t, t2;
 2280    si32_t i, year, month, day, hour, minute, second, tzdiff;
 2281    struct tm *tmptr;
 2282    NYD2_ENTER;
 2283 
 2284    for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
 2285       ;
 2286    fp -= 24;
 2287    if (PTR2SIZE(fp - fromline) < 7)
 2288       goto jinvalid;
 2289    if (fp[3] != ' ')
 2290       goto jinvalid;
 2291    for (i = 0;;) {
 2292       if (!strncmp(fp + 4, n_month_names[i], 3))
 2293          break;
 2294       if (n_month_names[++i][0] == '\0')
 2295          goto jinvalid;
 2296    }
 2297    month = i + 1;
 2298    if (fp[7] != ' ')
 2299       goto jinvalid;
 2300    n_idec_si32_cp(&day, &fp[8], 10, &xp);
 2301    if (*xp != ' ' || xp != fp + 10)
 2302       goto jinvalid;
 2303    n_idec_si32_cp(&hour, &fp[11], 10, &xp);
 2304    if (*xp != ':' || xp != fp + 13)
 2305       goto jinvalid;
 2306    n_idec_si32_cp(&minute, &fp[14], 10, &xp);
 2307    if (*xp != ':' || xp != fp + 16)
 2308       goto jinvalid;
 2309    n_idec_si32_cp(&second, &fp[17], 10, &xp);
 2310    if (*xp != ' ' || xp != fp + 19)
 2311       goto jinvalid;
 2312    n_idec_si32_cp(&year, &fp[20], 10, &xp);
 2313    if (xp != fp + 24)
 2314       goto jinvalid;
 2315    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
 2316       goto jinvalid;
 2317    if((t2 = mktime(gmtime(&t))) == (time_t)-1)
 2318       goto jinvalid;
 2319    tzdiff = t - t2;
 2320    if((tmptr = localtime(&t)) == NULL)
 2321       goto jinvalid;
 2322    if (tmptr->tm_isdst > 0)
 2323       tzdiff += 3600; /* TODO simply adding an hour for ISDST is .. buuh */
 2324    t -= tzdiff;
 2325 jleave:
 2326    NYD2_LEAVE;
 2327    return t;
 2328 jinvalid:
 2329    t = n_time_epoch();
 2330    goto jleave;
 2331 }
 2332 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
 2333 
 2334 FL time_t
 2335 rfctime(char const *date) /* TODO n_idec_ return tests */
 2336 {
 2337    char const *cp, *x;
 2338    time_t t;
 2339    si32_t i, year, month, day, hour, minute, second;
 2340    NYD2_ENTER;
 2341 
 2342    cp = date;
 2343 
 2344    if ((cp = nexttoken(cp)) == NULL)
 2345       goto jinvalid;
 2346    if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
 2347          cp[3] == ',') {
 2348       if ((cp = nexttoken(&cp[4])) == NULL)
 2349          goto jinvalid;
 2350    }
 2351    n_idec_si32_cp(&day, cp, 10, &x);
 2352    if ((cp = nexttoken(x)) == NULL)
 2353       goto jinvalid;
 2354    for (i = 0;;) {
 2355       if (!strncmp(cp, n_month_names[i], 3))
 2356          break;
 2357       if (n_month_names[++i][0] == '\0')
 2358          goto jinvalid;
 2359    }
 2360    month = i + 1;
 2361    if ((cp = nexttoken(&cp[3])) == NULL)
 2362       goto jinvalid;
 2363    /* RFC 5322, 4.3:
 2364     *  Where a two or three digit year occurs in a date, the year is to be
 2365     *  interpreted as follows: If a two digit year is encountered whose
 2366     *  value is between 00 and 49, the year is interpreted by adding 2000,
 2367     *  ending up with a value between 2000 and 2049.  If a two digit year
 2368     *  is encountered with a value between 50 and 99, or any three digit
 2369     *  year is encountered, the year is interpreted by adding 1900 */
 2370    n_idec_si32_cp(&year, cp, 10, &x);
 2371    i = (int)PTR2SIZE(x - cp);
 2372    if (i == 2 && year >= 0 && year <= 49)
 2373       year += 2000;
 2374    else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
 2375       year += 1900;
 2376    if ((cp = nexttoken(x)) == NULL)
 2377       goto jinvalid;
 2378    n_idec_si32_cp(&hour, cp, 10, &x);
 2379    if (*x != ':')
 2380       goto jinvalid;
 2381    cp = &x[1];
 2382    n_idec_si32_cp(&minute, cp, 10, &x);
 2383    if (*x == ':') {
 2384       cp = &x[1];
 2385       n_idec_si32_cp(&second, cp, 10, &x);
 2386    } else
 2387       second = 0;
 2388 
 2389    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
 2390       goto jinvalid;
 2391    if ((cp = nexttoken(x)) != NULL) {
 2392       char buf[3];
 2393       int sign = 1;
 2394 
 2395       switch (*cp) {
 2396       case '+':
 2397          sign = -1;
 2398          /* FALLTHRU */
 2399       case '-':
 2400          ++cp;
 2401          break;
 2402       }
 2403       if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
 2404             digitchar(cp[3])) {
 2405          si64_t tadj;
 2406 
 2407          buf[2] = '\0';
 2408          buf[0] = cp[0];
 2409          buf[1] = cp[1];
 2410          n_idec_si32_cp(&i, buf, 10, NULL);
 2411          tadj = (si64_t)i * 3600; /* XXX */
 2412          buf[0] = cp[2];
 2413          buf[1] = cp[3];
 2414          n_idec_si32_cp(&i, buf, 10, NULL);
 2415          tadj += (si64_t)i * 60; /* XXX */
 2416          if (sign < 0)
 2417             tadj = -tadj;
 2418          t += (time_t)tadj;
 2419       }
 2420       /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
 2421        * TODO once again, Christos Zoulas and NetBSD Mail have done
 2422        * TODO a really good job already, but using strptime(3), which
 2423        * TODO is not portable.  Nonetheless, WE must improve, not
 2424        * TODO at last because we simply ignore obsolete timezones!!
 2425        * TODO See RFC 5322, 4.3! */
 2426    }
 2427 jleave:
 2428    NYD2_LEAVE;
 2429    return t;
 2430 jinvalid:
 2431    t = 0;
 2432    goto jleave;
 2433 }
 2434 
 2435 FL time_t
 2436 combinetime(int year, int month, int day, int hour, int minute, int second){
 2437    size_t const jdn_epoch = 2440588;
 2438    bool_t const y2038p = (sizeof(time_t) == 4);
 2439 
 2440    size_t jdn;
 2441    time_t t;
 2442    NYD2_ENTER;
 2443 
 2444    if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
 2445          UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
 2446          UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
 2447          day < 1 || day > 31 ||
 2448          month < 1 || month > 12 ||
 2449          year < 1970)
 2450       goto jerr;
 2451 
 2452    if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
 2453          (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
 2454       /* Be a coward regarding Y2038, many people (mostly myself, that is) do
 2455        * test by stepping second-wise around the flip.  Don't care otherwise */
 2456       if(!y2038p)
 2457          goto jerr;
 2458       if(year > 2038 || month > 1 || day > 19 ||
 2459             hour > 3 || minute > 14 || second > 7)
 2460          goto jerr;
 2461    }
 2462 
 2463    t = second;
 2464    t += minute * n_DATE_SECSMIN;
 2465    t += hour * n_DATE_SECSHOUR;
 2466 
 2467    jdn = a_head_gregorian_to_jdn(year, month, day);
 2468    jdn -= jdn_epoch;
 2469    t += (time_t)jdn * n_DATE_SECSDAY;
 2470 jleave:
 2471    NYD2_LEAVE;
 2472    return t;
 2473 jerr:
 2474    t = (time_t)-1;
 2475    goto jleave;
 2476 }
 2477 
 2478 FL void
 2479 substdate(struct message *m)
 2480 {
 2481    char const *cp;
 2482    NYD_ENTER;
 2483 
 2484    /* Determine the date to print in faked 'From ' lines. This is traditionally
 2485     * the date the message was written to the mail file. Try to determine this
 2486     * using RFC message header fields, or fall back to current time */
 2487    m->m_time = 0;
 2488    if ((cp = hfield1("received", m)) != NULL) {
 2489       while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
 2490          do
 2491             ++cp;
 2492          while (alnumchar(*cp));
 2493       }
 2494       if (cp && *++cp)
 2495          m->m_time = rfctime(cp);
 2496    }
 2497    if (m->m_time == 0 || m->m_time > time_current.tc_time) {
 2498       if ((cp = hfield1("date", m)) != NULL)
 2499          m->m_time = rfctime(cp);
 2500    }
 2501    if (m->m_time == 0 || m->m_time > time_current.tc_time)
 2502       m->m_time = time_current.tc_time;
 2503    NYD_LEAVE;
 2504 }
 2505 
 2506 FL void
 2507 setup_from_and_sender(struct header *hp)
 2508 {
 2509    char const *addr;
 2510    struct name *np;
 2511    NYD_ENTER;
 2512 
 2513    /* If -t parsed or composed From: then take it.  With -t we otherwise
 2514     * want -r to be honoured in favour of *from* in order to have
 2515     * a behaviour that is compatible with what users would expect from e.g.
 2516     * postfix(1) */
 2517    if ((np = hp->h_from) != NULL ||
 2518          ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
 2519       ;
 2520    } else if ((addr = myaddrs(hp)) != NULL)
 2521       np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
 2522    hp->h_from = np;
 2523 
 2524    if ((np = hp->h_sender) != NULL) {
 2525       ;
 2526    } else if ((addr = ok_vlook(sender)) != NULL)
 2527       np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
 2528    hp->h_sender = np;
 2529 
 2530    NYD_LEAVE;
 2531 }
 2532 
 2533 FL struct name const *
 2534 check_from_and_sender(struct name const *fromfield,
 2535    struct name const *senderfield)
 2536 {
 2537    struct name const *rv = NULL;
 2538    NYD_ENTER;
 2539 
 2540    if (senderfield != NULL) {
 2541       if (senderfield->n_flink != NULL) {
 2542          n_err(_("The Sender: field may contain only one address\n"));
 2543          goto jleave;
 2544       }
 2545       rv = senderfield;
 2546    }
 2547 
 2548    if (fromfield != NULL) {
 2549       if (fromfield->n_flink != NULL && senderfield == NULL) {
 2550          n_err(_("A Sender: is required when there are multiple "
 2551             "addresses in From:\n"));
 2552          goto jleave;
 2553       }
 2554       if (rv == NULL)
 2555          rv = fromfield;
 2556    }
 2557 
 2558    if (rv == NULL)
 2559       rv = (struct name*)0x1;
 2560 jleave:
 2561    NYD_LEAVE;
 2562    return rv;
 2563 }
 2564 
 2565 #ifdef HAVE_XSSL
 2566 FL char *
 2567 getsender(struct message *mp)
 2568 {
 2569    char *cp;
 2570    struct name *np;
 2571    NYD_ENTER;
 2572 
 2573    if ((cp = hfield1("from", mp)) == NULL ||
 2574          (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
 2575       cp = NULL;
 2576    else
 2577       cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
 2578    NYD_LEAVE;
 2579    return cp;
 2580 }
 2581 #endif
 2582 
 2583 FL int
 2584 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
 2585       int subjfirst)
 2586 {
 2587    /* TODO grab_headers: again, check counts etc. against RFC;
 2588     * TODO (now assumes check_from_and_sender() is called afterwards ++ */
 2589    int errs;
 2590    int volatile comma;
 2591    NYD_ENTER;
 2592 
 2593    errs = 0;
 2594    comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
 2595 
 2596    if (gflags & GTO)
 2597       hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
 2598    if (subjfirst && (gflags & GSUBJECT))
 2599       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
 2600    if (gflags & GCC)
 2601       hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
 2602    if (gflags & GBCC)
 2603       hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
 2604 
 2605    if (gflags & GEXTRA) {
 2606       if (hp->h_from == NULL)
 2607          hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
 2608       hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
 2609             GEXTRA | GFULL | GFULLEXTRA);
 2610       if (hp->h_reply_to == NULL) {
 2611          struct name *v15compat;
 2612 
 2613          if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
 2614             n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
 2615          hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
 2616          if(hp->h_reply_to == NULL) /* v15 */
 2617             hp->h_reply_to = v15compat;
 2618       }
 2619       hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
 2620             GEXTRA | GFULL);
 2621       if (hp->h_sender == NULL)
 2622          hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
 2623       hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
 2624             GEXTRA | GFULL);
 2625    }
 2626 
 2627    if (!subjfirst && (gflags & GSUBJECT))
 2628       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
 2629 
 2630    NYD_LEAVE;
 2631    return errs;
 2632 }
 2633 
 2634 FL bool_t
 2635 n_header_match(struct message *mp, struct search_expr const *sep){
 2636    struct str fiter, in, out;
 2637    char const *field;
 2638    long lc;
 2639    FILE *ibuf;
 2640    size_t *linesize;
 2641    char **linebuf, *colon;
 2642    enum {a_NONE, a_ALL, a_ITER, a_RE} match;
 2643    bool_t rv;
 2644    NYD_ENTER;
 2645 
 2646    rv = FAL0;
 2647    match = a_NONE;
 2648    linebuf = &termios_state.ts_linebuf; /* XXX line pool */
 2649    linesize = &termios_state.ts_linesize; /* XXX line pool */
 2650    n_UNINIT(fiter.l, 0);
 2651    n_UNINIT(fiter.s, NULL);
 2652 
 2653    if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 2654       goto jleave;
 2655    if((lc = mp->m_lines - 1) < 0)
 2656       goto jleave;
 2657 
 2658    if((mp->m_flag & MNOFROM) == 0 &&
 2659          readline_restart(ibuf, linebuf, linesize, 0) < 0)
 2660       goto jleave;
 2661 
 2662    /* */
 2663    if((field = sep->ss_field) != NULL){
 2664       if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
 2665          match = a_ALL;
 2666       else{
 2667          fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
 2668          match = a_ITER;
 2669       }
 2670 #ifdef HAVE_REGEX
 2671    }else if(sep->ss_fieldre != NULL){
 2672       match = a_RE;
 2673 #endif
 2674    }else
 2675       match = a_ALL;
 2676 
 2677    /* Iterate over all the headers */
 2678    while(lc > 0){
 2679       struct name *np;
 2680 
 2681       if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
 2682          break;
 2683 
 2684       /* Is this a header we are interested in? */
 2685       if(match == a_ITER){
 2686          char *itercp;
 2687 
 2688          memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
 2689          while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
 2690             /* It may be an abbreviation */
 2691             char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
 2692             size_t i;
 2693             char c1;
 2694 
 2695             if(field[0] != '\0' && field[1] == '\0'){
 2696                c1 = lowerconv(field[0]);
 2697                for(i = 0; i < n_NELEM(x); ++i){
 2698                   if(c1 == x[i][0]){
 2699                      field = x[i];
 2700                      break;
 2701                   }
 2702                }
 2703             }
 2704 
 2705             if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
 2706                break;
 2707          }
 2708          if(field == NULL)
 2709             continue;
 2710 #ifdef HAVE_REGEX
 2711       }else if(match == a_RE){
 2712          char *cp;
 2713          size_t i;
 2714 
 2715          i = PTR2SIZE(colon - *linebuf);
 2716          cp = n_lofi_alloc(i +1);
 2717          memcpy(cp, *linebuf, i);
 2718          cp[i] = '\0';
 2719          i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
 2720          n_lofi_free(cp);
 2721          if(!i)
 2722             continue;
 2723 #endif
 2724       }
 2725 
 2726       /* It could be a plain existence test */
 2727       if(sep->ss_field_exists){
 2728          rv = TRU1;
 2729          break;
 2730       }
 2731 
 2732       /* Need to check the body */
 2733       while(blankchar(*++colon))
 2734          ;
 2735       in.s = colon;
 2736 
 2737       /* Shall we split into address list and match as/the addresses only?
 2738        * TODO at some later time we should ignore and log efforts to search
 2739        * TODO a skinned address list if we know the header has none such */
 2740       if(sep->ss_skin){
 2741          if((np = lextract(in.s, GSKIN)) == NULL)
 2742             continue;
 2743          out.s = np->n_name;
 2744       }else{
 2745          np = NULL;
 2746          in.l = strlen(in.s);
 2747          mime_fromhdr(&in, &out, TD_ICONV);
 2748       }
 2749 
 2750 jnext_name:
 2751 #ifdef HAVE_REGEX
 2752       if(sep->ss_bodyre != NULL)
 2753          rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
 2754       else
 2755 #endif
 2756          rv = substr(out.s, sep->ss_body);
 2757 
 2758       if(np == NULL)
 2759          free(out.s);
 2760       if(rv)
 2761          break;
 2762       if(np != NULL && (np = np->n_flink) != NULL){
 2763          out.s = np->n_name;
 2764          goto jnext_name;
 2765       }
 2766    }
 2767 
 2768 jleave:
 2769    if(match == a_ITER)
 2770       n_lofi_free(fiter.s);
 2771    NYD_LEAVE;
 2772    return rv;
 2773 }
 2774 
 2775 FL char const *
 2776 n_header_is_standard(char const *name, size_t len){
 2777    static char const * const names[] = {
 2778       "Bcc", "Cc", "From",
 2779       "In-Reply-To", "Mail-Followup-To",
 2780       "Message-ID", "References", "Reply-To",
 2781       "Sender", "Subject", "To",
 2782       "Mailx-Command",
 2783       "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
 2784       "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
 2785       NULL
 2786    };
 2787    char const * const *rv;
 2788    NYD_ENTER;
 2789 
 2790    if(len == UIZ_MAX)
 2791       len = strlen(name);
 2792 
 2793    for(rv = names; *rv != NULL; ++rv)
 2794       if(!ascncasecmp(*rv, name, len))
 2795          break;
 2796    NYD_LEAVE;
 2797    return *rv;
 2798 }
 2799 
 2800 FL bool_t
 2801 n_header_add_custom(struct n_header_field **hflp, char const *dat,
 2802       bool_t heap){
 2803    size_t i;
 2804    ui32_t nl, bl;
 2805    char const *cp;
 2806    struct n_header_field *hfp;
 2807    NYD_ENTER;
 2808 
 2809    hfp = NULL;
 2810 
 2811    /* For (-C) convenience, allow leading WS */
 2812    while(blankchar(*dat))
 2813       ++dat;
 2814 
 2815    /* Isolate the header field from the body */
 2816    for(cp = dat;; ++cp){
 2817       if(fieldnamechar(*cp))
 2818          continue;
 2819       if(*cp == '\0'){
 2820          if(cp == dat)
 2821             goto jename;
 2822       }else if(*cp != ':' && !blankchar(*cp)){
 2823 jename:
 2824          cp = N_("Invalid custom header (not \"field: body\"): %s\n");
 2825          goto jerr;
 2826       }
 2827       break;
 2828    }
 2829    nl = (ui32_t)PTR2SIZE(cp - dat);
 2830    if(nl == 0)
 2831       goto jename;
 2832 
 2833    /* Verify the custom header does not use standard/managed field name */
 2834    if(n_header_is_standard(dat, nl) != NULL){
 2835       cp = N_("Custom headers cannot use standard header names: %s\n");
 2836       goto jerr;
 2837    }
 2838 
 2839    /* Skip on over to the body */
 2840    while(blankchar(*cp))
 2841       ++cp;
 2842    if(*cp++ != ':')
 2843       goto jename;
 2844    while(blankchar(*cp))
 2845       ++cp;
 2846    bl = (ui32_t)strlen(cp);
 2847    for(i = bl++; i-- != 0;)
 2848       if(cntrlchar(cp[i])){
 2849          cp = N_("Invalid custom header: contains control characters: %s\n");
 2850          goto jerr;
 2851       }
 2852 
 2853    i = n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl +1 + bl;
 2854    *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
 2855    hfp->hf_next = NULL;
 2856    hfp->hf_nl = nl;
 2857    hfp->hf_bl = bl - 1;
 2858    memcpy(hfp->hf_dat, dat, nl);
 2859       hfp->hf_dat[nl++] = '\0';
 2860       memcpy(hfp->hf_dat + nl, cp, bl);
 2861 jleave:
 2862    NYD_LEAVE;
 2863    return (hfp != NULL);
 2864 
 2865 jerr:
 2866    n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
 2867    goto jleave;
 2868 }
 2869 
 2870 /* s-it-mode */