"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/head.c" (25 Mar 2018, 86087 Bytes) of package /linux/misc/s-nail-14.9.10.tar.xz:


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

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ 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       assert(tcurr != NULL);
  690       if(tcurr != NULL && !(tcurr->t_f & a_T_TADDR)){
  691          for(tp = thead; tp != NULL; tp = tp->t_next){
  692             if(tp->t_f & a_T_TADDR){
  693                if(tp->t_last != NULL)
  694                   tp->t_last->t_next = tp->t_next;
  695                else
  696                   thead = tp->t_next;
  697                if(tp->t_next != NULL)
  698                   tp->t_next->t_last = tp->t_last;
  699 
  700                tcurr = tp;
  701                while(tp->t_next != NULL)
  702                   tp = tp->t_next;
  703                tp->t_next = tcurr;
  704                tcurr->t_last = tp;
  705                tcurr->t_next = NULL;
  706                break;
  707             }
  708          }
  709       }
  710 
  711       /* Make ranges contiguous: ensure a continuous range of atoms is converted
  712        * to a SPECIAL one if at least one of them requires it */
  713       for(tp = thead; tp != NULL; tp = tp->t_next){
  714          if(tp->t_f & a_T_SPECIAL){
  715             tcurr = tp;
  716             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
  717                tp->t_f |= a_T_SPECIAL;
  718             tp = tcurr;
  719             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
  720                tp->t_f |= a_T_SPECIAL;
  721             if(tp == NULL)
  722                break;
  723          }
  724       }
  725 
  726       /* And yes, we want quotes to extend as much as possible */
  727       for(tp = thead; tp != NULL; tp = tp->t_next){
  728          if(tp->t_f & a_T_TQUOTE){
  729             tcurr = tp;
  730             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
  731                tp->t_f |= a_T_SPECIAL;
  732             tp = tcurr;
  733             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
  734                tp->t_f |= a_T_SPECIAL;
  735             if(tp == NULL)
  736                break;
  737          }
  738       }
  739 
  740       /* Then rejoin */
  741       ostp = n_string_creat_auto(&ost);
  742       if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
  743          ostp = n_string_reserve(ostp, c.ui32 <<= 1);
  744 
  745       for(tcurr = thead; tcurr != NULL;){
  746          if(tcurr != thead)
  747             ostp = n_string_push_c(ostp, ' ');
  748          if(tcurr->t_f & a_T_TADDR){
  749             if(tcurr->t_last != NULL)
  750                ostp = n_string_push_c(ostp, '<');
  751             agp->ag_iaddr_start = ostp->s_len;
  752             /* Now it is terrible to say, but if that thing contained
  753              * quotes, then those may contain quoted-pairs! */
  754 #if 0
  755             if(!(tcurr->t_f & a_T_SPECIAL)){
  756 #endif
  757                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  758                      (tcurr->t_end - tcurr->t_start));
  759 #if 0
  760             }else{
  761                bool_t quot, esc;
  762 
  763                ostp = n_string_push_c(ostp, '"');
  764                quot = TRU1;
  765 
  766                cp = &cp1st[tcurr->t_start];
  767                cpmax = &cp1st[tcurr->t_end];
  768                for(esc = FAL0; cp < cpmax;){
  769                   if((c.c = *cp++) == '\\' && !esc){
  770                      if(cp < cpmax && (*cp == '"' || *cp == '\\'))
  771                         esc = TRU1;
  772                   }else{
  773                      if(esc || c.c == '"')
  774                         ostp = n_string_push_c(ostp, '\\');
  775                      else if(c.c == '@'){
  776                         ostp = n_string_push_c(ostp, '"');
  777                         quot = FAL0;
  778                      }
  779                      ostp = n_string_push_c(ostp, c.c);
  780                      esc = FAL0;
  781                   }
  782                }
  783             }
  784 #endif
  785             agp->ag_iaddr_aend = ostp->s_len;
  786 
  787             if(tcurr->t_last != NULL)
  788                ostp = n_string_push_c(ostp, '>');
  789             tcurr = tcurr->t_next;
  790          }else if(tcurr->t_f & a_T_TCOMM){
  791             ostp = n_string_push_c(ostp, '(');
  792             ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  793                   (tcurr->t_end - tcurr->t_start));
  794             while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
  795                tcurr = tp;
  796                ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
  797                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  798                      (tcurr->t_end - tcurr->t_start));
  799             }
  800             ostp = n_string_push_c(ostp, ')');
  801             tcurr = tcurr->t_next;
  802          }else if(tcurr->t_f & a_T_TQUOTE){
  803 jput_quote:
  804             ostp = n_string_push_c(ostp, '"');
  805             tp = tcurr;
  806             do/* while tcurr && TATOM||TQUOTE */{
  807                cp = &cp1st[tcurr->t_start];
  808                cpmax = &cp1st[tcurr->t_end];
  809                if(cp == cpmax)
  810                   continue;
  811 
  812                if(tcurr != tp)
  813                   ostp = n_string_push_c(ostp, ' ');
  814 
  815                if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
  816                   ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
  817                else{
  818                   bool_t esc;
  819 
  820                   for(esc = FAL0; cp < cpmax;){
  821                      if((c.c = *cp++) == '\\' && !esc){
  822                         if(cp < cpmax && (*cp == '"' || *cp == '\\'))
  823                            esc = TRU1;
  824                      }else{
  825                         if(esc || c.c == '"'){
  826 jput_quote_esc:
  827                            ostp = n_string_push_c(ostp, '\\');
  828                         }
  829                         ostp = n_string_push_c(ostp, c.c);
  830                         esc = FAL0;
  831                      }
  832                   }
  833                   if(esc){
  834                      c.c = '\\';
  835                      goto jput_quote_esc;
  836                   }
  837                }
  838             }while((tcurr = tcurr->t_next) != NULL &&
  839                (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
  840             ostp = n_string_push_c(ostp, '"');
  841          }else if(tcurr->t_f & a_T_SPECIAL)
  842             goto jput_quote;
  843          else{
  844             /* Can we use a fast join mode? */
  845             for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
  846                if(!(tcurr->t_f & a_T_TATOM))
  847                   break;
  848                if(tcurr != tp)
  849                   ostp = n_string_push_c(ostp, ' ');
  850                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
  851                      (tcurr->t_end - tcurr->t_start));
  852             }
  853          }
  854       }
  855 
  856       n_lofi_snap_unroll(lofi_snap);
  857 
  858       agp->ag_input = n_string_cp(ostp);
  859       agp->ag_ilen = ostp->s_len;
  860       /*ostp = n_string_drop_ownership(ostp);*/
  861 
  862       /* Name and domain must be non-empty, the second */
  863       cp = &agp->ag_input[agp->ag_iaddr_start];
  864       cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  865       if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
  866          c.c = '@';
  867          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
  868          goto jleave;
  869       }
  870 
  871       addr = agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
  872 
  873       /* TODO This parser is a mess.  We do not know whether this is truly
  874        * TODO valid, and all our checks are not truly RFC conforming.
  875        * TODO Do check the skinned thing by itself once more, in order
  876        * TODO to catch problems from reordering, e.g., this additional
  877        * TODO test catches a final address without AT..
  878        * TODO This is a plain copy+paste of the weird thing above, no care */
  879       agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
  880       flags &= a_RESET_MASK;
  881       for (p = addr; (c.c = *p++) != '\0';) {
  882          if(c.c == '"')
  883             flags ^= a_IN_QUOTE;
  884          else if (c.u < 040 || c.u >= 0177) {
  885 #ifdef HAVE_IDNA
  886                if(!(flags & a_IN_DOMAIN))
  887 #endif
  888                   break;
  889          } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
  890             if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
  891                break;
  892          } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
  893             /*EMPTY*/;
  894          } else if (c.c == '\\' && *p != '\0') {
  895             ++p;
  896          } else if (c.c == '@') {
  897             if(flags & a_IN_AT){
  898                NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
  899                   c.u);
  900                goto jleave;
  901             }
  902             flags |= a_IN_AT;
  903             agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
  904             flags &= ~a_DOMAIN_MASK;
  905             flags |= (*p == '[') ? a_IN_DOMAIN | a_DOMAIN_V6 : a_IN_DOMAIN;
  906             continue;
  907          } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
  908                c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
  909                c.c == '\\' || c.c == ',' || blankchar(c.c))
  910             break;
  911          flags &= ~a_IN_AT;
  912       }
  913       if(c.c != '\0')
  914          NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
  915       else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
  916          /* This is not an address, but if we had seen angle brackets convert
  917           * it to a n_nodename() address if the name is a valid user */
  918 jinsert_domain:
  919          if(cp > &agp->ag_input[0] && cp[-1] == '<' &&
  920                cpmax <= &agp->ag_input[agp->ag_ilen] && cpmax[0] == '>' &&
  921                (!strcmp(addr, ok_vlook(LOGNAME)) || getpwnam(addr) != NULL)){
  922             /* XXX However, if hostname is set to the empty string this
  923              * XXX indicates that the used *mta* will perform the
  924              * XXX auto-expansion instead.  Not so with `addrcodec' though */
  925             agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR;
  926             if(!issingle_hack &&
  927                   (cp = ok_vlook(hostname)) != NULL && *cp == '\0')
  928                agp->ag_n_flags |= NAME_ADDRSPEC_WITHOUT_DOMAIN;
  929             else{
  930                c.ui32 = strlen(cp = n_nodename(TRU1));
  931                /* This is yet IDNA converted.. */
  932                ostp = n_string_creat_auto(&ost);
  933                ostp = n_string_assign_buf(ostp, agp->ag_input, agp->ag_ilen);
  934                ostp = n_string_insert_c(ostp, agp->ag_iaddr_aend++, '@');
  935                ostp = n_string_insert_buf(ostp, agp->ag_iaddr_aend, cp,
  936                      c.ui32);
  937                agp->ag_iaddr_aend += c.ui32;
  938                agp->ag_input = n_string_cp(ostp);
  939                agp->ag_ilen = ostp->s_len;
  940                /*ostp = n_string_drop_ownership(ostp);*/
  941 
  942                cp = &agp->ag_input[agp->ag_iaddr_start];
  943                cpmax = &agp->ag_input[agp->ag_iaddr_aend];
  944                agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
  945             }
  946          }else
  947             NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
  948                '@');
  949       }
  950    }
  951 
  952 jleave:
  953 #ifdef HAVE_IDNA
  954    if(!(agp->ag_n_flags & NAME_ADDRSPEC_INVALID) && (flags & a_IDNA_APPLY))
  955       agp = a_head_idna_apply(agp);
  956 #endif
  957    NYD_LEAVE;
  958    return !(agp->ag_n_flags & NAME_ADDRSPEC_INVALID);
  959 }
  960 
  961 static long
  962 a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem, char **colon)
  963 {
  964    char *line2 = NULL, *cp, *cp2;
  965    size_t line2size = 0;
  966    int c, isenc;
  967    NYD2_ENTER;
  968 
  969    if (*linebuf == NULL)
  970       *linebuf = srealloc(*linebuf, *linesize = 1);
  971    **linebuf = '\0';
  972    for (;;) {
  973       if (--rem < 0) {
  974          rem = -1;
  975          break;
  976       }
  977       if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
  978          rem = -1;
  979          break;
  980       }
  981       for (cp = *linebuf; fieldnamechar(*cp); ++cp)
  982          ;
  983       if (cp > *linebuf)
  984          while (blankchar(*cp))
  985             ++cp;
  986       if (*cp != ':' || cp == *linebuf)
  987          continue;
  988 
  989       /* I guess we got a headline.  Handle wraparound */
  990       *colon = cp;
  991       cp = *linebuf + c;
  992       for (;;) {
  993          isenc = 0;
  994          while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
  995             ;
  996          cp++;
  997          if (rem <= 0)
  998             break;
  999          if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
 1000             isenc |= 1;
 1001          ungetc(c = getc(f), f);
 1002          if (!blankchar(c))
 1003             break;
 1004          c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
 1005          if (c < 0)
 1006             break;
 1007          --rem;
 1008          for (cp2 = line2; blankchar(*cp2); ++cp2)
 1009             ;
 1010          c -= (int)PTR2SIZE(cp2 - line2);
 1011          if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
 1012             isenc |= 2;
 1013          if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
 1014             size_t diff = PTR2SIZE(cp - *linebuf),
 1015                colondiff = PTR2SIZE(*colon - *linebuf);
 1016             *linebuf = srealloc(*linebuf, *linesize += c + 2);
 1017             cp = &(*linebuf)[diff];
 1018             *colon = &(*linebuf)[colondiff];
 1019          }
 1020          if (isenc != 3)
 1021             *cp++ = ' ';
 1022          memcpy(cp, cp2, c);
 1023          cp += c;
 1024       }
 1025       *cp = '\0';
 1026 
 1027       if (line2 != NULL)
 1028          free(line2);
 1029       break;
 1030    }
 1031    NYD2_LEAVE;
 1032    return rem;
 1033 }
 1034 
 1035 static int
 1036 msgidnextc(char const **cp, int *status)
 1037 {
 1038    int c;
 1039    NYD2_ENTER;
 1040 
 1041    assert(cp != NULL);
 1042    assert(*cp != NULL);
 1043    assert(status != NULL);
 1044 
 1045    for (;;) {
 1046       if (*status & 01) {
 1047          if (**cp == '"') {
 1048             *status &= ~01;
 1049             (*cp)++;
 1050             continue;
 1051          }
 1052          if (**cp == '\\') {
 1053             (*cp)++;
 1054             if (**cp == '\0')
 1055                goto jeof;
 1056          }
 1057          goto jdfl;
 1058       }
 1059       switch (**cp) {
 1060       case '(':
 1061          *cp = skip_comment(&(*cp)[1]);
 1062          continue;
 1063       case '>':
 1064       case '\0':
 1065 jeof:
 1066          c = '\0';
 1067          goto jleave;
 1068       case '"':
 1069          (*cp)++;
 1070          *status |= 01;
 1071          continue;
 1072       case '@':
 1073          *status |= 02;
 1074          /*FALLTHRU*/
 1075       default:
 1076 jdfl:
 1077          c = *(*cp)++ & 0377;
 1078          c = (*status & 02) ? lowerconv(c) : c;
 1079          goto jleave;
 1080       }
 1081    }
 1082 jleave:
 1083    NYD2_LEAVE;
 1084    return c;
 1085 }
 1086 
 1087 static int
 1088 charcount(char *str, int c)
 1089 {
 1090    char *cp;
 1091    int i;
 1092    NYD2_ENTER;
 1093 
 1094    for (i = 0, cp = str; *cp; ++cp)
 1095       if (*cp == c)
 1096          ++i;
 1097    NYD2_LEAVE;
 1098    return i;
 1099 }
 1100 
 1101 static char const *
 1102 nexttoken(char const *cp)
 1103 {
 1104    NYD2_ENTER;
 1105    for (;;) {
 1106       if (*cp == '\0') {
 1107          cp = NULL;
 1108          break;
 1109       }
 1110 
 1111       if (*cp == '(') {
 1112          size_t nesting = 1;
 1113 
 1114          do switch (*++cp) {
 1115          case '(':
 1116             ++nesting;
 1117             break;
 1118          case ')':
 1119             --nesting;
 1120             break;
 1121          } while (nesting > 0 && *cp != '\0'); /* XXX error? */
 1122       } else if (blankchar(*cp) || *cp == ',')
 1123          ++cp;
 1124       else
 1125          break;
 1126    }
 1127    NYD2_LEAVE;
 1128    return cp;
 1129 }
 1130 
 1131 FL char const *
 1132 myaddrs(struct header *hp) /* TODO */
 1133 {
 1134    struct name *np;
 1135    char const *rv, *mta;
 1136    NYD_ENTER;
 1137 
 1138    if (hp != NULL && (np = hp->h_from) != NULL) {
 1139       if ((rv = np->n_fullname) != NULL)
 1140          goto jleave;
 1141       if ((rv = np->n_name) != NULL)
 1142          goto jleave;
 1143    }
 1144 
 1145    /* Verified once variable had been set */
 1146    if((rv = ok_vlook(from)) != NULL)
 1147       goto jleave;
 1148 
 1149    /* When invoking *sendmail* directly, it's its task to generate an otherwise
 1150     * undeterminable From: address.  However, if the user sets *hostname*,
 1151     * accept his desire */
 1152    if (ok_vlook(hostname) != NULL)
 1153       goto jnodename;
 1154    if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
 1155          /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
 1156          ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
 1157       goto jnodename;
 1158 jleave:
 1159    NYD_LEAVE;
 1160    return rv;
 1161 
 1162 jnodename:{
 1163       char *cp;
 1164       char const *hn, *ln;
 1165       size_t i;
 1166 
 1167       hn = n_nodename(TRU1);
 1168       ln = ok_vlook(LOGNAME);
 1169       i = strlen(ln) + strlen(hn) + 1 +1;
 1170       rv = cp = salloc(i);
 1171       sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
 1172    }
 1173    goto jleave;
 1174 }
 1175 
 1176 FL char const *
 1177 myorigin(struct header *hp) /* TODO */
 1178 {
 1179    char const *rv = NULL, *ccp;
 1180    struct name *np;
 1181    NYD_ENTER;
 1182 
 1183    if((ccp = myaddrs(hp)) != NULL &&
 1184          (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
 1185       if(np->n_flink == NULL)
 1186          rv = ccp;
 1187       /* Verified upon variable set time */
 1188       else if((ccp = ok_vlook(sender)) != NULL)
 1189          rv = ccp;
 1190       /* TODO why not else rv = n_poption_arg_r; ?? */
 1191    }
 1192    NYD_LEAVE;
 1193    return rv;
 1194 }
 1195 
 1196 FL bool_t
 1197 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
 1198 {
 1199    char date[n_FROM_DATEBUF];
 1200    bool_t rv;
 1201    NYD2_ENTER;
 1202 
 1203    if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
 1204          (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
 1205           !_is_date(date)))
 1206       rv = TRUM1;
 1207    NYD2_LEAVE;
 1208    return rv;
 1209 }
 1210 
 1211 FL int
 1212 extract_date_from_from_(char const *line, size_t linelen,
 1213    char datebuf[n_FROM_DATEBUF])
 1214 {
 1215    int rv;
 1216    char const *cp = line;
 1217    NYD_ENTER;
 1218 
 1219    rv = 1;
 1220 
 1221    /* "From " */
 1222    cp = _from__skipword(cp);
 1223    if (cp == NULL)
 1224       goto jerr;
 1225    /* "addr-spec " */
 1226    cp = _from__skipword(cp);
 1227    if (cp == NULL)
 1228       goto jerr;
 1229    if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
 1230          (cp[2] == 'y' || cp[2] == 'Y')){
 1231       cp = _from__skipword(cp);
 1232       if (cp == NULL)
 1233          goto jerr;
 1234    }
 1235    /* It seems there are invalid MBOX archives in the wild, compare
 1236     * . http://bugs.debian.org/624111
 1237     * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
 1238     * What they do is that they obfuscate the address to "name at host",
 1239     * and even "name at host dot dom dot dom.
 1240     * The [Aa][Tt] is also RFC 733, so be tolerant */
 1241    else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
 1242          cp[2] == ' '){
 1243       rv = -1;
 1244       cp += 3;
 1245 jat_dot:
 1246       cp = _from__skipword(cp);
 1247       if (cp == NULL)
 1248          goto jerr;
 1249       if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
 1250             (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
 1251          cp += 4;
 1252          goto jat_dot;
 1253       }
 1254    }
 1255 
 1256    linelen -= PTR2SIZE(cp - line);
 1257    if (linelen < a_HEAD_DATE_MINLEN)
 1258       goto jerr;
 1259    if (cp[linelen - 1] == '\n') {
 1260       --linelen;
 1261       /* (Rather IMAP/POP3 only) */
 1262       if (cp[linelen - 1] == '\r')
 1263          --linelen;
 1264       if (linelen < a_HEAD_DATE_MINLEN)
 1265          goto jerr;
 1266    }
 1267    if (linelen >= n_FROM_DATEBUF)
 1268       goto jerr;
 1269 
 1270 jleave:
 1271    memcpy(datebuf, cp, linelen);
 1272    datebuf[linelen] = '\0';
 1273    NYD_LEAVE;
 1274    return rv;
 1275 jerr:
 1276    cp = _("<Unknown date>");
 1277    linelen = strlen(cp);
 1278    if (linelen >= n_FROM_DATEBUF)
 1279       linelen = n_FROM_DATEBUF;
 1280    rv = 0;
 1281    goto jleave;
 1282 }
 1283 
 1284 FL void
 1285 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
 1286 {
 1287    /* See the prototype declaration for the hairy relationship of
 1288     * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
 1289    struct n_header_field **hftail;
 1290    struct header nh, *hq = &nh;
 1291    char *linebuf = NULL /* TODO line pool */, *colon;
 1292    size_t linesize = 0, seenfields = 0;
 1293    int c;
 1294    long lc;
 1295    char const *val, *cp;
 1296    NYD_ENTER;
 1297 
 1298    memset(hq, 0, sizeof *hq);
 1299    if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
 1300       hq->h_to = hp->h_to;
 1301       hq->h_cc = hp->h_cc;
 1302       hq->h_bcc = hp->h_bcc;
 1303    }
 1304    hftail = &hq->h_user_headers;
 1305 
 1306    for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
 1307       ;
 1308 
 1309    /* TODO yippieia, cat(check(lextract)) :-) */
 1310    rewind(fp);
 1311    while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
 1312       struct name *np;
 1313 
 1314       /* We explicitly allow EAF_NAME for some addressees since aliases are not
 1315        * yet expanded when we parse these! */
 1316       if ((val = thisfield(linebuf, "to")) != NULL) {
 1317          ++seenfields;
 1318          hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
 1319                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1320       } else if ((val = thisfield(linebuf, "cc")) != NULL) {
 1321          ++seenfields;
 1322          hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
 1323                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1324       } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
 1325          ++seenfields;
 1326          hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
 1327                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
 1328       } else if ((val = thisfield(linebuf, "from")) != NULL) {
 1329          if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
 1330             ++seenfields;
 1331             hq->h_from = cat(hq->h_from,
 1332                   checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
 1333                      EACM_STRICT, NULL));
 1334          }
 1335       } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
 1336          ++seenfields;
 1337          hq->h_reply_to = cat(hq->h_reply_to,
 1338                checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
 1339       } else if ((val = thisfield(linebuf, "sender")) != NULL) {
 1340          if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
 1341             ++seenfields;
 1342             hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
 1343                   checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
 1344                      EACM_STRICT, NULL));
 1345          } else
 1346             goto jebadhead;
 1347       } else if ((val = thisfield(linebuf, "subject")) != NULL ||
 1348             (val = thisfield(linebuf, "subj")) != NULL) {
 1349          ++seenfields;
 1350          for (cp = val; blankchar(*cp); ++cp)
 1351             ;
 1352          hq->h_subject = (hq->h_subject != NULL)
 1353                ? save2str(hq->h_subject, cp) : savestr(cp);
 1354       }
 1355       /* The remaining are mostly hacked in and thus TODO -- at least in
 1356        * TODO respect to their content checking */
 1357       else if((val = thisfield(linebuf, "message-id")) != NULL){
 1358          if(n_psonce & n_PSO_t_FLAG){
 1359             np = checkaddrs(lextract(val, GREF),
 1360                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1361                   NULL);
 1362             if (np == NULL || np->n_flink != NULL)
 1363                goto jebadhead;
 1364             ++seenfields;
 1365             hq->h_message_id = np;
 1366          }else
 1367             goto jebadhead;
 1368       }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
 1369          if(n_psonce & n_PSO_t_FLAG){
 1370             np = checkaddrs(lextract(val, GREF),
 1371                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1372                   NULL);
 1373             ++seenfields;
 1374             hq->h_in_reply_to = np;
 1375          }else
 1376             goto jebadhead;
 1377       }else if((val = thisfield(linebuf, "references")) != NULL){
 1378          if(n_psonce & n_PSO_t_FLAG){
 1379             ++seenfields;
 1380             /* TODO Limit number of references TODO better on parser side */
 1381             hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
 1382                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
 1383                   NULL));
 1384          }else
 1385             goto jebadhead;
 1386       }
 1387       /* and that is very hairy */
 1388       else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
 1389          if(n_psonce & n_PSO_t_FLAG){
 1390             ++seenfields;
 1391             hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
 1392                   /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
 1393                   checkaddr_err));
 1394          }else
 1395             goto jebadhead;
 1396       }
 1397       /* A free-form header; a_gethfield() did some verification already.. */
 1398       else{
 1399          struct n_header_field *hfp;
 1400          ui32_t nl, bl;
 1401          char const *nstart;
 1402 
 1403          for(nstart = cp = linebuf;; ++cp)
 1404             if(!fieldnamechar(*cp))
 1405                break;
 1406          nl = (ui32_t)PTR2SIZE(cp - nstart);
 1407 
 1408          while(blankchar(*cp))
 1409             ++cp;
 1410          if(*cp++ != ':'){
 1411 jebadhead:
 1412             n_err(_("Ignoring header field: %s\n"), linebuf);
 1413             continue;
 1414          }
 1415          while(blankchar(*cp))
 1416             ++cp;
 1417          bl = (ui32_t)strlen(cp) +1;
 1418 
 1419          ++seenfields;
 1420          *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
 1421                ) + nl +1 + bl);
 1422             hftail = &hfp->hf_next;
 1423          hfp->hf_next = NULL;
 1424          hfp->hf_nl = nl;
 1425          hfp->hf_bl = bl - 1;
 1426          memcpy(hfp->hf_dat, nstart, nl);
 1427             hfp->hf_dat[nl++] = '\0';
 1428             memcpy(hfp->hf_dat + nl, cp, bl);
 1429       }
 1430    }
 1431 
 1432    /* In case the blank line after the header has been edited out.  Otherwise,
 1433     * fetch the header separator */
 1434    if (linebuf != NULL) {
 1435       if (linebuf[0] != '\0') {
 1436          for (cp = linebuf; *(++cp) != '\0';)
 1437             ;
 1438          fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
 1439       } else {
 1440          if ((c = getc(fp)) != '\n' && c != EOF)
 1441             ungetc(c, fp);
 1442       }
 1443    }
 1444 
 1445    if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
 1446       hp->h_to = hq->h_to;
 1447       hp->h_cc = hq->h_cc;
 1448       hp->h_bcc = hq->h_bcc;
 1449       hp->h_from = hq->h_from;
 1450       hp->h_reply_to = hq->h_reply_to;
 1451       hp->h_sender = hq->h_sender;
 1452       if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
 1453             !(n_poption & n_PO_t_FLAG))
 1454          hp->h_subject = hq->h_subject;
 1455       hp->h_user_headers = hq->h_user_headers;
 1456 
 1457       if (n_psonce & n_PSO_t_FLAG) {
 1458          hp->h_ref = hq->h_ref;
 1459          hp->h_message_id = hq->h_message_id;
 1460          hp->h_in_reply_to = hq->h_in_reply_to;
 1461          hp->h_mft = hq->h_mft;
 1462 
 1463          /* And perform additional validity checks so that we don't bail later
 1464           * on TODO this is good and the place where this should occur,
 1465           * TODO unfortunately a lot of other places do again and blabla */
 1466          if (hp->h_from == NULL)
 1467             hp->h_from = n_poption_arg_r;
 1468          else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
 1469             hp->h_sender = lextract(ok_vlook(sender),
 1470                   GEXTRA | GFULL | GFULLEXTRA);
 1471       }
 1472    } else
 1473       n_err(_("Restoring deleted header lines\n"));
 1474 
 1475    if (linebuf != NULL)
 1476       free(linebuf);
 1477    NYD_LEAVE;
 1478 }
 1479 
 1480 FL char *
 1481 hfield_mult(char const *field, struct message *mp, int mult)
 1482 {
 1483    FILE *ibuf;
 1484    struct str hfs;
 1485    long lc;
 1486    size_t linesize = 0; /* TODO line pool */
 1487    char *linebuf = NULL, *colon;
 1488    char const *hfield;
 1489    NYD_ENTER;
 1490 
 1491    /* There are (spam) messages which have header bytes which are many KB when
 1492     * joined, so resize a single heap storage until we are done if we shall
 1493     * collect a field that may have multiple bodies; only otherwise use the
 1494     * string dope directly */
 1495    memset(&hfs, 0, sizeof hfs);
 1496 
 1497    if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 1498       goto jleave;
 1499    if ((lc = mp->m_lines - 1) < 0)
 1500       goto jleave;
 1501 
 1502    if ((mp->m_flag & MNOFROM) == 0 &&
 1503          readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 1504       goto jleave;
 1505    while (lc > 0) {
 1506       if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
 1507          break;
 1508       if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
 1509          if (mult)
 1510             n_str_add_buf(&hfs, hfield, strlen(hfield));
 1511          else {
 1512             hfs.s = savestr(hfield);
 1513             break;
 1514          }
 1515       }
 1516    }
 1517 
 1518 jleave:
 1519    if (linebuf != NULL)
 1520       free(linebuf);
 1521    if (mult && hfs.s != NULL) {
 1522       colon = savestrbuf(hfs.s, hfs.l);
 1523       free(hfs.s);
 1524       hfs.s = colon;
 1525    }
 1526    NYD_LEAVE;
 1527    return hfs.s;
 1528 }
 1529 
 1530 FL char const *
 1531 thisfield(char const *linebuf, char const *field)
 1532 {
 1533    char const *rv = NULL;
 1534    NYD2_ENTER;
 1535 
 1536    while (lowerconv(*linebuf) == lowerconv(*field)) {
 1537       ++linebuf;
 1538       ++field;
 1539    }
 1540    if (*field != '\0')
 1541       goto jleave;
 1542 
 1543    while (blankchar(*linebuf))
 1544       ++linebuf;
 1545    if (*linebuf++ != ':')
 1546       goto jleave;
 1547 
 1548    while (blankchar(*linebuf)) /* TODO header parser..  strip trailing WS?!? */
 1549       ++linebuf;
 1550    rv = linebuf;
 1551 jleave:
 1552    NYD2_LEAVE;
 1553    return rv;
 1554 }
 1555 
 1556 FL char *
 1557 nameof(struct message *mp, int reptype)
 1558 {
 1559    char *cp, *cp2;
 1560    NYD_ENTER;
 1561 
 1562    cp = skin(name1(mp, reptype));
 1563    if (reptype != 0 || charcount(cp, '!') < 2)
 1564       goto jleave;
 1565    cp2 = strrchr(cp, '!');
 1566    --cp2;
 1567    while (cp2 > cp && *cp2 != '!')
 1568       --cp2;
 1569    if (*cp2 == '!')
 1570       cp = cp2 + 1;
 1571 jleave:
 1572    NYD_LEAVE;
 1573    return cp;
 1574 }
 1575 
 1576 FL char const *
 1577 skip_comment(char const *cp)
 1578 {
 1579    size_t nesting;
 1580    NYD_ENTER;
 1581 
 1582    for (nesting = 1; nesting > 0 && *cp; ++cp) {
 1583       switch (*cp) {
 1584       case '\\':
 1585          if (cp[1])
 1586             ++cp;
 1587          break;
 1588       case '(':
 1589          ++nesting;
 1590          break;
 1591       case ')':
 1592          --nesting;
 1593          break;
 1594       }
 1595    }
 1596    NYD_LEAVE;
 1597    return cp;
 1598 }
 1599 
 1600 FL char const *
 1601 routeaddr(char const *name)
 1602 {
 1603    char const *np, *rp = NULL;
 1604    NYD_ENTER;
 1605 
 1606    for (np = name; *np; np++) {
 1607       switch (*np) {
 1608       case '(':
 1609          np = skip_comment(np + 1) - 1;
 1610          break;
 1611       case '"':
 1612          while (*np) {
 1613             if (*++np == '"')
 1614                break;
 1615             if (*np == '\\' && np[1])
 1616                np++;
 1617          }
 1618          break;
 1619       case '<':
 1620          rp = np;
 1621          break;
 1622       case '>':
 1623          goto jleave;
 1624       }
 1625    }
 1626    rp = NULL;
 1627 jleave:
 1628    NYD_LEAVE;
 1629    return rp;
 1630 }
 1631 
 1632 FL enum expand_addr_flags
 1633 expandaddr_to_eaf(void)
 1634 {
 1635    struct eafdesc {
 1636       char const  *eafd_name;
 1637       bool_t      eafd_is_target;
 1638       ui8_t       eafd_andoff;
 1639       ui8_t       eafd_or;
 1640    } const eafa[] = {
 1641       {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
 1642       {"fail", FAL0, EAF_NONE, EAF_FAIL},
 1643       {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
 1644       {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
 1645          {"file", TRU1, EAF_NONE, EAF_FILE},
 1646          {"pipe", TRU1, EAF_NONE, EAF_PIPE},
 1647          {"name", TRU1, EAF_NONE, EAF_NAME},
 1648          {"addr", TRU1, EAF_NONE, EAF_ADDR}
 1649    }, *eafp;
 1650 
 1651    char *buf;
 1652    enum expand_addr_flags rv;
 1653    char const *cp;
 1654    NYD2_ENTER;
 1655 
 1656    if ((cp = ok_vlook(expandaddr)) == NULL)
 1657       rv = EAF_RESTRICT_TARGETS;
 1658    else if (*cp == '\0')
 1659       rv = EAF_TARGET_MASK;
 1660    else {
 1661       rv = EAF_TARGET_MASK;
 1662 
 1663       for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
 1664          bool_t minus;
 1665 
 1666          if ((minus = (*cp == '-')) || *cp == '+')
 1667             ++cp;
 1668          for (eafp = eafa;; ++eafp) {
 1669             if (eafp == eafa + n_NELEM(eafa)) {
 1670                if (n_poption & n_PO_D_V)
 1671                   n_err(_("Unknown *expandaddr* value: %s\n"), cp);
 1672                break;
 1673             } else if (!asccasecmp(cp, eafp->eafd_name)) {
 1674                if (!minus) {
 1675                   rv &= ~eafp->eafd_andoff;
 1676                   rv |= eafp->eafd_or;
 1677                } else {
 1678                   if (eafp->eafd_is_target)
 1679                      rv &= ~eafp->eafd_or;
 1680                   else if (n_poption & n_PO_D_V)
 1681                      n_err(_("minus - prefix invalid for *expandaddr* value: "
 1682                         "%s\n"), --cp);
 1683                }
 1684                break;
 1685             } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
 1686                n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
 1687                rv &= ~EAF_NAME;
 1688                break;
 1689             }
 1690          }
 1691       }
 1692 
 1693       if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
 1694             (n_poption & n_PO_TILDE_FLAG)))
 1695          rv |= EAF_TARGET_MASK;
 1696       else if(n_poption & n_PO_D_V){
 1697          if(!(rv & EAF_TARGET_MASK))
 1698             n_err(_("*expandaddr* doesn't allow any addressees\n"));
 1699          else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
 1700             n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
 1701       }
 1702    }
 1703    NYD2_LEAVE;
 1704    return rv;
 1705 }
 1706 
 1707 FL si8_t
 1708 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
 1709 {
 1710    char cbuf[sizeof "'\\U12340'"];
 1711    char const *cs;
 1712    int f;
 1713    si8_t rv;
 1714    enum expand_addr_flags eaf;
 1715    NYD_ENTER;
 1716 
 1717    eaf = expandaddr_to_eaf();
 1718    f = np->n_flags;
 1719 
 1720    if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
 1721       if (eaf & EAF_FAILINVADDR)
 1722          rv = -rv;
 1723 
 1724       if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
 1725          ;
 1726       } else {
 1727          ui32_t c;
 1728          char const *fmt = "'\\x%02X'";
 1729          bool_t ok8bit = TRU1;
 1730 
 1731          if (f & NAME_ADDRSPEC_ERR_IDNA) {
 1732             cs = _("Invalid domain name: %s, character %s\n");
 1733             fmt = "'\\U%04X'";
 1734             ok8bit = FAL0;
 1735          } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
 1736             cs = _("%s contains invalid %s sequence\n");
 1737          else if (f & NAME_ADDRSPEC_ERR_NAME) {
 1738             cs = _("%s is an invalid alias name\n");
 1739          } else
 1740             cs = _("%s contains invalid byte %s\n");
 1741 
 1742          c = NAME_ADDRSPEC_ERR_GETWC(f);
 1743          snprintf(cbuf, sizeof cbuf,
 1744             (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
 1745          goto jprint;
 1746       }
 1747       goto jleave;
 1748    }
 1749 
 1750    /* *expandaddr* stuff */
 1751    if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
 1752       goto jleave;
 1753 
 1754    if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
 1755       if (eaf & EAF_FAIL)
 1756          rv = -rv;
 1757       cs = _("%s%s: file or pipe addressees not allowed here\n");
 1758       if (eacm & EACM_NOLOG)
 1759          goto jleave;
 1760       else
 1761          goto j0print;
 1762    }
 1763 
 1764    eaf |= (eacm & EAF_TARGET_MASK);
 1765    if (eacm & EACM_NONAME)
 1766       eaf &= ~EAF_NAME;
 1767 
 1768    if (eaf == EAF_NONE) {
 1769       rv = FAL0;
 1770       goto jleave;
 1771    }
 1772    if (eaf & EAF_FAIL)
 1773       rv = -rv;
 1774 
 1775    if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
 1776       cs = _("%s%s: *expandaddr* doesn't allow file target\n");
 1777       if (eacm & EACM_NOLOG)
 1778          goto jleave;
 1779    } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
 1780       cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
 1781       if (eacm & EACM_NOLOG)
 1782          goto jleave;
 1783    } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
 1784       cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
 1785       if (eacm & EACM_NOLOG)
 1786          goto jleave;
 1787    } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
 1788       cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
 1789       if (eacm & EACM_NOLOG)
 1790          goto jleave;
 1791    } else {
 1792       rv = FAL0;
 1793       goto jleave;
 1794    }
 1795 
 1796 j0print:
 1797    cbuf[0] = '\0';
 1798 jprint:
 1799    n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
 1800 jleave:
 1801    NYD_LEAVE;
 1802    return rv;
 1803 }
 1804 
 1805 FL char *
 1806 skin(char const *name)
 1807 {
 1808    struct n_addrguts ag;
 1809    char *rv;
 1810    NYD_ENTER;
 1811 
 1812    if(name != NULL){
 1813       /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
 1814       rv = ag.ag_skinned;
 1815       if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
 1816          rv = savestrbuf(rv, ag.ag_slen);
 1817    }else
 1818       rv = NULL;
 1819    NYD_LEAVE;
 1820    return rv;
 1821 }
 1822 
 1823 /* TODO addrspec_with_guts: RFC 5322
 1824  * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
 1825 FL char const *
 1826 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
 1827       bool_t issingle_hack){
 1828    char const *cp;
 1829    char *cp2, *bufend, *nbuf, c;
 1830    enum{
 1831       a_NONE,
 1832       a_GOTLT = 1<<0,
 1833       a_GOTADDR = 1<<1,
 1834       a_GOTSPACE = 1<<2,
 1835       a_LASTSP = 1<<3
 1836    } flags;
 1837    NYD_ENTER;
 1838 
 1839    memset(agp, 0, sizeof *agp);
 1840 
 1841    if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
 1842       agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
 1843       agp->ag_slen = 0;
 1844       NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
 1845       goto jleave;
 1846    }else if(!doskin){
 1847       /*agp->ag_iaddr_start = 0;*/
 1848       agp->ag_iaddr_aend = agp->ag_ilen;
 1849       agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
 1850       agp->ag_slen = agp->ag_ilen;
 1851       agp->ag_n_flags = NAME_SKINNED;
 1852       goto jcheck;
 1853    }
 1854 
 1855    flags = a_NONE;
 1856    nbuf = n_lofi_alloc(agp->ag_ilen +1);
 1857    /*agp->ag_iaddr_start = 0;*/
 1858    cp2 = bufend = nbuf;
 1859 
 1860    /* TODO This is complete crap and should use a token parser */
 1861    for(cp = name++; (c = *cp++) != '\0';){
 1862       switch (c) {
 1863       case '(':
 1864          cp = skip_comment(cp);
 1865          flags &= ~a_LASTSP;
 1866          break;
 1867       case '"':
 1868          /* Start of a "quoted-string".  Copy it in its entirety */
 1869          /* XXX RFC: quotes are "semantically invisible"
 1870           * XXX But it was explicitly added (Changelog.Heirloom,
 1871           * XXX [9.23] released 11/15/00, "Do not remove quotes
 1872           * XXX when skinning names"?  No more info.. */
 1873          *cp2++ = c;
 1874          while ((c = *cp) != '\0') { /* TODO improve */
 1875             ++cp;
 1876             if (c == '"') {
 1877                *cp2++ = c;
 1878                break;
 1879             }
 1880             if (c != '\\')
 1881                *cp2++ = c;
 1882             else if ((c = *cp) != '\0') {
 1883                *cp2++ = c;
 1884                ++cp;
 1885             }
 1886          }
 1887          flags &= ~a_LASTSP;
 1888          break;
 1889       case ' ':
 1890       case '\t':
 1891          if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
 1892             flags |= a_GOTSPACE;
 1893             agp->ag_iaddr_aend = PTR2SIZE(cp - name);
 1894          }
 1895          if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
 1896             cp += 3, *cp2++ = '@';
 1897          else if (cp[0] == '@' && blankchar(cp[1]))
 1898             cp += 2, *cp2++ = '@';
 1899          else
 1900             flags |= a_LASTSP;
 1901          break;
 1902       case '<':
 1903          agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
 1904          cp2 = bufend;
 1905          flags &= ~(a_GOTSPACE | a_LASTSP);
 1906          flags |= a_GOTLT | a_GOTADDR;
 1907          break;
 1908       case '>':
 1909          if(flags & a_GOTLT){
 1910             /* (_addrspec_check() verifies these later!) */
 1911             flags &= ~(a_GOTLT | a_LASTSP);
 1912             agp->ag_iaddr_aend = PTR2SIZE(cp - name);
 1913 
 1914             /* Skip over the entire remaining field */
 1915             while((c = *cp) != '\0' && c != ','){
 1916                ++cp;
 1917                if (c == '(')
 1918                   cp = skip_comment(cp);
 1919                else if (c == '"')
 1920                   while ((c = *cp) != '\0') {
 1921                      ++cp;
 1922                      if (c == '"')
 1923                         break;
 1924                      if (c == '\\' && *cp != '\0')
 1925                         ++cp;
 1926                   }
 1927             }
 1928             break;
 1929          }
 1930          /* FALLTHRU */
 1931       default:
 1932          if(flags & a_LASTSP){
 1933             flags &= ~a_LASTSP;
 1934             if(flags & a_GOTADDR)
 1935                *cp2++ = ' ';
 1936          }
 1937          *cp2++ = c;
 1938          /* This character is forbidden here, but it may nonetheless be
 1939           * present: ensure we turn this into something valid!  (E.g., if the
 1940           * next character would be a "..) */
 1941          if(c == '\\' && *cp != '\0')
 1942             *cp2++ = *cp++;
 1943          if(c == ',' && !issingle_hack){
 1944             if(!(flags & a_GOTLT)){
 1945                *cp2++ = ' ';
 1946                for(; blankchar(*cp); ++cp)
 1947                   ;
 1948                flags &= ~a_LASTSP;
 1949                bufend = cp2;
 1950             }
 1951          }else if(!(flags & a_GOTADDR)){
 1952             flags |= a_GOTADDR;
 1953             agp->ag_iaddr_start = PTR2SIZE(cp - name);
 1954          }
 1955       }
 1956    }
 1957    --name;
 1958    agp->ag_slen = PTR2SIZE(cp2 - nbuf);
 1959    if (agp->ag_iaddr_aend == 0)
 1960       agp->ag_iaddr_aend = agp->ag_ilen;
 1961    /* Misses > */
 1962    else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
 1963       cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
 1964       memcpy(cp2, agp->ag_input, agp->ag_ilen);
 1965       agp->ag_iaddr_aend = agp->ag_ilen;
 1966       cp2[agp->ag_ilen++] = '>';
 1967       cp2[agp->ag_ilen] = '\0';
 1968    }
 1969    agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
 1970    n_lofi_free(nbuf);
 1971    agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
 1972 jcheck:
 1973    if(a_head_addrspec_check(agp, doskin, issingle_hack) <= FAL0)
 1974       name = NULL;
 1975    else
 1976       name = agp->ag_input;
 1977 jleave:
 1978    NYD_LEAVE;
 1979    return name;
 1980 }
 1981 
 1982 FL char *
 1983 realname(char const *name)
 1984 {
 1985    char const *cp, *cq, *cstart = NULL, *cend = NULL;
 1986    char *rname, *rp;
 1987    struct str in, out;
 1988    int quoted, good, nogood;
 1989    NYD_ENTER;
 1990 
 1991    if ((cp = n_UNCONST(name)) == NULL)
 1992       goto jleave;
 1993    for (; *cp != '\0'; ++cp) {
 1994       switch (*cp) {
 1995       case '(':
 1996          if (cstart != NULL) {
 1997             /* More than one comment in address, doesn't make sense to display
 1998              * it without context.  Return the entire field */
 1999             cp = mime_fromaddr(name);
 2000             goto jleave;
 2001          }
 2002          cstart = cp++;
 2003          cp = skip_comment(cp);
 2004          cend = cp--;
 2005          if (cend <= cstart)
 2006             cend = cstart = NULL;
 2007          break;
 2008       case '"':
 2009          while (*cp) {
 2010             if (*++cp == '"')
 2011                break;
 2012             if (*cp == '\\' && cp[1])
 2013                ++cp;
 2014          }
 2015          break;
 2016       case '<':
 2017          if (cp > name) {
 2018             cstart = name;
 2019             cend = cp;
 2020          }
 2021          break;
 2022       case ',':
 2023          /* More than one address. Just use the first one */
 2024          goto jbrk;
 2025       }
 2026    }
 2027 
 2028 jbrk:
 2029    if (cstart == NULL) {
 2030       if (*name == '<') {
 2031          /* If name contains only a route-addr, the surrounding angle brackets
 2032           * don't serve any useful purpose when displaying, so remove */
 2033          cp = prstr(skin(name));
 2034       } else
 2035          cp = mime_fromaddr(name);
 2036       goto jleave;
 2037    }
 2038 
 2039    /* Strip quotes. Note that quotes that appear within a MIME encoded word are
 2040     * not stripped. The idea is to strip only syntactical relevant things (but
 2041     * this is not necessarily the most sensible way in practice) */
 2042    rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
 2043    quoted = 0;
 2044    for (cp = cstart; cp < cend; ++cp) {
 2045       if (*cp == '(' && !quoted) {
 2046          cq = skip_comment(++cp);
 2047          if (PTRCMP(--cq, >, cend))
 2048             cq = cend;
 2049          while (cp < cq) {
 2050             if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
 2051                ++cp;
 2052             *rp++ = *cp++;
 2053          }
 2054       } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
 2055          *rp++ = *++cp;
 2056       else if (*cp == '"') {
 2057          quoted = !quoted;
 2058          continue;
 2059       } else
 2060          *rp++ = *cp;
 2061    }
 2062    *rp = '\0';
 2063    in.s = rname;
 2064    in.l = rp - rname;
 2065    mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
 2066    ac_free(rname);
 2067    rname = savestr(out.s);
 2068    free(out.s);
 2069 
 2070    while (blankchar(*rname))
 2071       ++rname;
 2072    for (rp = rname; *rp != '\0'; ++rp)
 2073       ;
 2074    while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
 2075       *rp = '\0';
 2076    if (rp == rname) {
 2077       cp = mime_fromaddr(name);
 2078       goto jleave;
 2079    }
 2080 
 2081    /* mime_fromhdr() has converted all nonprintable characters to question
 2082     * marks now. These and blanks are considered uninteresting; if the
 2083     * displayed part of the real name contains more than 25% of them, it is
 2084     * probably better to display the plain email address instead */
 2085    good = 0;
 2086    nogood = 0;
 2087    for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
 2088       if (*rp == '?' || blankchar(*rp))
 2089          ++nogood;
 2090       else
 2091          ++good;
 2092    cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
 2093 jleave:
 2094    NYD_LEAVE;
 2095    return n_UNCONST(cp);
 2096 }
 2097 
 2098 FL char *
 2099 name1(struct message *mp, int reptype)
 2100 {
 2101    char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
 2102    size_t namesize, linesize = 0;
 2103    FILE *ibuf;
 2104    int f1st = 1;
 2105    NYD_ENTER;
 2106 
 2107    if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
 2108       goto jleave;
 2109    if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
 2110       goto jleave;
 2111 
 2112    namebuf = smalloc(namesize = 1);
 2113    namebuf[0] = 0;
 2114    if (mp->m_flag & MNOFROM)
 2115       goto jout;
 2116    if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 2117       goto jout;
 2118    if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 2119       goto jout;
 2120 
 2121 jnewname:
 2122    if (namesize <= linesize)
 2123       namebuf = srealloc(namebuf, namesize = linesize +1);
 2124    for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
 2125       ;
 2126    for (; blankchar(*cp); ++cp)
 2127       ;
 2128    for (cp2 = namebuf + strlen(namebuf);
 2129         *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
 2130       *cp2++ = *cp++;
 2131    *cp2 = '\0';
 2132 
 2133    if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
 2134       goto jout;
 2135    if ((cp = strchr(linebuf, 'F')) == NULL)
 2136       goto jout;
 2137    if (strncmp(cp, "From", 4))
 2138       goto jout;
 2139    if (namesize <= linesize)
 2140       namebuf = srealloc(namebuf, namesize = linesize + 1);
 2141 
 2142    while ((cp = strchr(cp, 'r')) != NULL) {
 2143       if (!strncmp(cp, "remote", 6)) {
 2144          if ((cp = strchr(cp, 'f')) == NULL)
 2145             break;
 2146          if (strncmp(cp, "from", 4) != 0)
 2147             break;
 2148          if ((cp = strchr(cp, ' ')) == NULL)
 2149             break;
 2150          cp++;
 2151          if (f1st) {
 2152             strncpy(namebuf, cp, namesize);
 2153             f1st = 0;
 2154          } else {
 2155             cp2 = strrchr(namebuf, '!') + 1;
 2156             strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
 2157          }
 2158          namebuf[namesize - 2] = '!';
 2159          namebuf[namesize - 1] = '\0';
 2160          goto jnewname;
 2161       }
 2162       cp++;
 2163    }
 2164 jout:
 2165    if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
 2166          *cp == '\0')
 2167       cp = savestr(namebuf);
 2168 
 2169    if (linebuf != NULL)
 2170       free(linebuf);
 2171    free(namebuf);
 2172 jleave:
 2173    NYD_LEAVE;
 2174    return cp;
 2175 }
 2176 
 2177 FL char const *
 2178 subject_re_trim(char const *s){
 2179    struct{
 2180       ui8_t len;
 2181       char  dat[7];
 2182    }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
 2183       {3, "re:"},
 2184       {3, "aw:"}, {5, "antw:"}, /* de */
 2185       {3, "wg:"}, /* Seen too often in the wild */
 2186       {0, ""}
 2187    };
 2188 
 2189    bool_t any;
 2190    char *re_st, *re_st_x;
 2191    char const *orig_s;
 2192    size_t re_l;
 2193    NYD_ENTER;
 2194 
 2195    any = FAL0;
 2196    orig_s = s;
 2197    re_st = NULL;
 2198    n_UNINIT(re_l, 0);
 2199 
 2200    if((re_st_x = ok_vlook(reply_strings)) != NULL &&
 2201          (re_l = strlen(re_st_x)) > 0){
 2202       re_st = n_lofi_alloc(++re_l * 2);
 2203       memcpy(re_st, re_st_x, re_l);
 2204    }
 2205 
 2206 jouter:
 2207    while(*s != '\0'){
 2208       while(spacechar(*s))
 2209          ++s;
 2210 
 2211       for(pp = ignored; pp->len > 0; ++pp)
 2212          if(is_asccaseprefix(pp->dat, s)){
 2213             s += pp->len;
 2214             any = TRU1;
 2215             goto jouter;
 2216          }
 2217 
 2218       if(re_st != NULL){
 2219          char *cp;
 2220 
 2221          memcpy(re_st_x = &re_st[re_l], re_st, re_l);
 2222          while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
 2223             if(is_asccaseprefix(cp, s)){
 2224                s += strlen(cp);
 2225                any = TRU1;
 2226                goto jouter;
 2227             }
 2228       }
 2229       break;
 2230    }
 2231 
 2232    if(re_st != NULL)
 2233       n_lofi_free(re_st);
 2234    NYD_LEAVE;
 2235    return any ? s : orig_s;
 2236 }
 2237 
 2238 FL int
 2239 msgidcmp(char const *s1, char const *s2)
 2240 {
 2241    int q1 = 0, q2 = 0, c1, c2;
 2242    NYD_ENTER;
 2243 
 2244    while(*s1 == '<')
 2245       ++s1;
 2246    while(*s2 == '<')
 2247       ++s2;
 2248 
 2249    do {
 2250       c1 = msgidnextc(&s1, &q1);
 2251       c2 = msgidnextc(&s2, &q2);
 2252       if (c1 != c2)
 2253          break;
 2254    } while (c1 && c2);
 2255    NYD_LEAVE;
 2256    return c1 - c2;
 2257 }
 2258 
 2259 FL char const *
 2260 fakefrom(struct message *mp)
 2261 {
 2262    char const *name;
 2263    NYD_ENTER;
 2264 
 2265    if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
 2266          ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
 2267       /* XXX MAILER-DAEMON is what an old MBOX manual page says.
 2268        * RFC 4155 however requires a RFC 5322 (2822) conforming
 2269        * "addr-spec", but we simply can't provide that */
 2270       name = "MAILER-DAEMON";
 2271    NYD_LEAVE;
 2272    return name;
 2273 }
 2274 
 2275 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
 2276 FL time_t
 2277 unixtime(char const *fromline)
 2278 {
 2279    char const *fp, *xp;
 2280    time_t t, t2;
 2281    si32_t i, year, month, day, hour, minute, second, tzdiff;
 2282    struct tm *tmptr;
 2283    NYD2_ENTER;
 2284 
 2285    for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
 2286       ;
 2287    fp -= 24;
 2288    if (PTR2SIZE(fp - fromline) < 7)
 2289       goto jinvalid;
 2290    if (fp[3] != ' ')
 2291       goto jinvalid;
 2292    for (i = 0;;) {
 2293       if (!strncmp(fp + 4, n_month_names[i], 3))
 2294          break;
 2295       if (n_month_names[++i][0] == '\0')
 2296          goto jinvalid;
 2297    }
 2298    month = i + 1;
 2299    if (fp[7] != ' ')
 2300       goto jinvalid;
 2301    n_idec_si32_cp(&day, &fp[8], 10, &xp);
 2302    if (*xp != ' ' || xp != fp + 10)
 2303       goto jinvalid;
 2304    n_idec_si32_cp(&hour, &fp[11], 10, &xp);
 2305    if (*xp != ':' || xp != fp + 13)
 2306       goto jinvalid;
 2307    n_idec_si32_cp(&minute, &fp[14], 10, &xp);
 2308    if (*xp != ':' || xp != fp + 16)
 2309       goto jinvalid;
 2310    n_idec_si32_cp(&second, &fp[17], 10, &xp);
 2311    if (*xp != ' ' || xp != fp + 19)
 2312       goto jinvalid;
 2313    n_idec_si32_cp(&year, &fp[20], 10, &xp);
 2314    if (xp != fp + 24)
 2315       goto jinvalid;
 2316    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
 2317       goto jinvalid;
 2318    if((t2 = mktime(gmtime(&t))) == (time_t)-1)
 2319       goto jinvalid;
 2320    tzdiff = t - t2;
 2321    if((tmptr = localtime(&t)) == NULL)
 2322       goto jinvalid;
 2323    if (tmptr->tm_isdst > 0)
 2324       tzdiff += 3600; /* TODO simply adding an hour for ISDST is .. buuh */
 2325    t -= tzdiff;
 2326 jleave:
 2327    NYD2_LEAVE;
 2328    return t;
 2329 jinvalid:
 2330    t = n_time_epoch();
 2331    goto jleave;
 2332 }
 2333 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
 2334 
 2335 FL time_t
 2336 rfctime(char const *date) /* TODO n_idec_ return tests */
 2337 {
 2338    char const *cp, *x;
 2339    time_t t;
 2340    si32_t i, year, month, day, hour, minute, second;
 2341    NYD2_ENTER;
 2342 
 2343    cp = date;
 2344 
 2345    if ((cp = nexttoken(cp)) == NULL)
 2346       goto jinvalid;
 2347    if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
 2348          cp[3] == ',') {
 2349       if ((cp = nexttoken(&cp[4])) == NULL)
 2350          goto jinvalid;
 2351    }
 2352    n_idec_si32_cp(&day, cp, 10, &x);
 2353    if ((cp = nexttoken(x)) == NULL)
 2354       goto jinvalid;
 2355    for (i = 0;;) {
 2356       if (!strncmp(cp, n_month_names[i], 3))
 2357          break;
 2358       if (n_month_names[++i][0] == '\0')
 2359          goto jinvalid;
 2360    }
 2361    month = i + 1;
 2362    if ((cp = nexttoken(&cp[3])) == NULL)
 2363       goto jinvalid;
 2364    /* RFC 5322, 4.3:
 2365     *  Where a two or three digit year occurs in a date, the year is to be
 2366     *  interpreted as follows: If a two digit year is encountered whose
 2367     *  value is between 00 and 49, the year is interpreted by adding 2000,
 2368     *  ending up with a value between 2000 and 2049.  If a two digit year
 2369     *  is encountered with a value between 50 and 99, or any three digit
 2370     *  year is encountered, the year is interpreted by adding 1900 */
 2371    n_idec_si32_cp(&year, cp, 10, &x);
 2372    i = (int)PTR2SIZE(x - cp);
 2373    if (i == 2 && year >= 0 && year <= 49)
 2374       year += 2000;
 2375    else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
 2376       year += 1900;
 2377    if ((cp = nexttoken(x)) == NULL)
 2378       goto jinvalid;
 2379    n_idec_si32_cp(&hour, cp, 10, &x);
 2380    if (*x != ':')
 2381       goto jinvalid;
 2382    cp = &x[1];
 2383    n_idec_si32_cp(&minute, cp, 10, &x);
 2384    if (*x == ':') {
 2385       cp = &x[1];
 2386       n_idec_si32_cp(&second, cp, 10, &x);
 2387    } else
 2388       second = 0;
 2389 
 2390    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
 2391       goto jinvalid;
 2392    if ((cp = nexttoken(x)) != NULL) {
 2393       char buf[3];
 2394       int sign = 1;
 2395 
 2396       switch (*cp) {
 2397       case '+':
 2398          sign = -1;
 2399          /* FALLTHRU */
 2400       case '-':
 2401          ++cp;
 2402          break;
 2403       }
 2404       if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
 2405             digitchar(cp[3])) {
 2406          si64_t tadj;
 2407 
 2408          buf[2] = '\0';
 2409          buf[0] = cp[0];
 2410          buf[1] = cp[1];
 2411          n_idec_si32_cp(&i, buf, 10, NULL);
 2412          tadj = (si64_t)i * 3600; /* XXX */
 2413          buf[0] = cp[2];
 2414          buf[1] = cp[3];
 2415          n_idec_si32_cp(&i, buf, 10, NULL);
 2416          tadj += (si64_t)i * 60; /* XXX */
 2417          if (sign < 0)
 2418             tadj = -tadj;
 2419          t += (time_t)tadj;
 2420       }
 2421       /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
 2422        * TODO once again, Christos Zoulas and NetBSD Mail have done
 2423        * TODO a really good job already, but using strptime(3), which
 2424        * TODO is not portable.  Nonetheless, WE must improve, not
 2425        * TODO at last because we simply ignore obsolete timezones!!
 2426        * TODO See RFC 5322, 4.3! */
 2427    }
 2428 jleave:
 2429    NYD2_LEAVE;
 2430    return t;
 2431 jinvalid:
 2432    t = 0;
 2433    goto jleave;
 2434 }
 2435 
 2436 FL time_t
 2437 combinetime(int year, int month, int day, int hour, int minute, int second){
 2438    size_t const jdn_epoch = 2440588;
 2439    bool_t const y2038p = (sizeof(time_t) == 4);
 2440 
 2441    size_t jdn;
 2442    time_t t;
 2443    NYD2_ENTER;
 2444 
 2445    if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
 2446          UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
 2447          UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
 2448          day < 1 || day > 31 ||
 2449          month < 1 || month > 12 ||
 2450          year < 1970)
 2451       goto jerr;
 2452 
 2453    if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
 2454          (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
 2455       /* Be a coward regarding Y2038, many people (mostly myself, that is) do
 2456        * test by stepping second-wise around the flip.  Don't care otherwise */
 2457       if(!y2038p)
 2458          goto jerr;
 2459       if(year > 2038 || month > 1 || day > 19 ||
 2460             hour > 3 || minute > 14 || second > 7)
 2461          goto jerr;
 2462    }
 2463 
 2464    t = second;
 2465    t += minute * n_DATE_SECSMIN;
 2466    t += hour * n_DATE_SECSHOUR;
 2467 
 2468    jdn = a_head_gregorian_to_jdn(year, month, day);
 2469    jdn -= jdn_epoch;
 2470    t += (time_t)jdn * n_DATE_SECSDAY;
 2471 jleave:
 2472    NYD2_LEAVE;
 2473    return t;
 2474 jerr:
 2475    t = (time_t)-1;
 2476    goto jleave;
 2477 }
 2478 
 2479 FL void
 2480 substdate(struct message *m)
 2481 {
 2482    char const *cp;
 2483    NYD_ENTER;
 2484 
 2485    /* Determine the date to print in faked 'From ' lines. This is traditionally
 2486     * the date the message was written to the mail file. Try to determine this
 2487     * using RFC message header fields, or fall back to current time */
 2488    m->m_time = 0;
 2489    if ((cp = hfield1("received", m)) != NULL) {
 2490       while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
 2491          do
 2492             ++cp;
 2493          while (alnumchar(*cp));
 2494       }
 2495       if (cp && *++cp)
 2496          m->m_time = rfctime(cp);
 2497    }
 2498    if (m->m_time == 0 || m->m_time > time_current.tc_time) {
 2499       if ((cp = hfield1("date", m)) != NULL)
 2500          m->m_time = rfctime(cp);
 2501    }
 2502    if (m->m_time == 0 || m->m_time > time_current.tc_time)
 2503       m->m_time = time_current.tc_time;
 2504    NYD_LEAVE;
 2505 }
 2506 
 2507 FL void
 2508 setup_from_and_sender(struct header *hp)
 2509 {
 2510    char const *addr;
 2511    struct name *np;
 2512    NYD_ENTER;
 2513 
 2514    /* If -t parsed or composed From: then take it.  With -t we otherwise
 2515     * want -r to be honoured in favour of *from* in order to have
 2516     * a behaviour that is compatible with what users would expect from e.g.
 2517     * postfix(1) */
 2518    if ((np = hp->h_from) != NULL ||
 2519          ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
 2520       ;
 2521    } else if ((addr = myaddrs(hp)) != NULL)
 2522       np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
 2523    hp->h_from = np;
 2524 
 2525    if ((np = hp->h_sender) != NULL) {
 2526       ;
 2527    } else if ((addr = ok_vlook(sender)) != NULL)
 2528       np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
 2529    hp->h_sender = np;
 2530 
 2531    NYD_LEAVE;
 2532 }
 2533 
 2534 FL struct name const *
 2535 check_from_and_sender(struct name const *fromfield,
 2536    struct name const *senderfield)
 2537 {
 2538    struct name const *rv = NULL;
 2539    NYD_ENTER;
 2540 
 2541    if (senderfield != NULL) {
 2542       if (senderfield->n_flink != NULL) {
 2543          n_err(_("The Sender: field may contain only one address\n"));
 2544          goto jleave;
 2545       }
 2546       rv = senderfield;
 2547    }
 2548 
 2549    if (fromfield != NULL) {
 2550       if (fromfield->n_flink != NULL && senderfield == NULL) {
 2551          n_err(_("A Sender: is required when there are multiple "
 2552             "addresses in From:\n"));
 2553          goto jleave;
 2554       }
 2555       if (rv == NULL)
 2556          rv = fromfield;
 2557    }
 2558 
 2559    if (rv == NULL)
 2560       rv = (struct name*)0x1;
 2561 jleave:
 2562    NYD_LEAVE;
 2563    return rv;
 2564 }
 2565 
 2566 #ifdef HAVE_XSSL
 2567 FL char *
 2568 getsender(struct message *mp)
 2569 {
 2570    char *cp;
 2571    struct name *np;
 2572    NYD_ENTER;
 2573 
 2574    if ((cp = hfield1("from", mp)) == NULL ||
 2575          (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
 2576       cp = NULL;
 2577    else
 2578       cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
 2579    NYD_LEAVE;
 2580    return cp;
 2581 }
 2582 #endif
 2583 
 2584 FL int
 2585 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
 2586       int subjfirst)
 2587 {
 2588    /* TODO grab_headers: again, check counts etc. against RFC;
 2589     * TODO (now assumes check_from_and_sender() is called afterwards ++ */
 2590    int errs;
 2591    int volatile comma;
 2592    NYD_ENTER;
 2593 
 2594    errs = 0;
 2595    comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
 2596 
 2597    if (gflags & GTO)
 2598       hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
 2599    if (subjfirst && (gflags & GSUBJECT))
 2600       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
 2601    if (gflags & GCC)
 2602       hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
 2603    if (gflags & GBCC)
 2604       hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
 2605 
 2606    if (gflags & GEXTRA) {
 2607       if (hp->h_from == NULL)
 2608          hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
 2609       hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
 2610             GEXTRA | GFULL | GFULLEXTRA);
 2611       if (hp->h_reply_to == NULL) {
 2612          struct name *v15compat;
 2613 
 2614          if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
 2615             n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
 2616          hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
 2617          if(hp->h_reply_to == NULL) /* v15 */
 2618             hp->h_reply_to = v15compat;
 2619       }
 2620       hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
 2621             GEXTRA | GFULL);
 2622       if (hp->h_sender == NULL)
 2623          hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
 2624       hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
 2625             GEXTRA | GFULL);
 2626    }
 2627 
 2628    if (!subjfirst && (gflags & GSUBJECT))
 2629       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
 2630 
 2631    NYD_LEAVE;
 2632    return errs;
 2633 }
 2634 
 2635 FL bool_t
 2636 n_header_match(struct message *mp, struct search_expr const *sep){
 2637    struct str fiter, in, out;
 2638    char const *field;
 2639    long lc;
 2640    FILE *ibuf;
 2641    size_t *linesize;
 2642    char **linebuf, *colon;
 2643    enum {a_NONE, a_ALL, a_ITER, a_RE} match;
 2644    bool_t rv;
 2645    NYD_ENTER;
 2646 
 2647    rv = FAL0;
 2648    match = a_NONE;
 2649    linebuf = &termios_state.ts_linebuf; /* XXX line pool */
 2650    linesize = &termios_state.ts_linesize; /* XXX line pool */
 2651    n_UNINIT(fiter.l, 0);
 2652    n_UNINIT(fiter.s, NULL);
 2653 
 2654    if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
 2655       goto jleave;
 2656    if((lc = mp->m_lines - 1) < 0)
 2657       goto jleave;
 2658 
 2659    if((mp->m_flag & MNOFROM) == 0 &&
 2660          readline_restart(ibuf, linebuf, linesize, 0) < 0)
 2661       goto jleave;
 2662 
 2663    /* */
 2664    if((field = sep->ss_field) != NULL){
 2665       if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
 2666          match = a_ALL;
 2667       else{
 2668          fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
 2669          match = a_ITER;
 2670       }
 2671 #ifdef HAVE_REGEX
 2672    }else if(sep->ss_fieldre != NULL){
 2673       match = a_RE;
 2674 #endif
 2675    }else
 2676       match = a_ALL;
 2677 
 2678    /* Iterate over all the headers */
 2679    while(lc > 0){
 2680       struct name *np;
 2681 
 2682       if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
 2683          break;
 2684 
 2685       /* Is this a header we are interested in? */
 2686       if(match == a_ITER){
 2687          char *itercp;
 2688 
 2689          memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
 2690          while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
 2691             /* It may be an abbreviation */
 2692             char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
 2693             size_t i;
 2694             char c1;
 2695 
 2696             if(field[0] != '\0' && field[1] == '\0'){
 2697                c1 = lowerconv(field[0]);
 2698                for(i = 0; i < n_NELEM(x); ++i){
 2699                   if(c1 == x[i][0]){
 2700                      field = x[i];
 2701                      break;
 2702                   }
 2703                }
 2704             }
 2705 
 2706             if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
 2707                break;
 2708          }
 2709          if(field == NULL)
 2710             continue;
 2711 #ifdef HAVE_REGEX
 2712       }else if(match == a_RE){
 2713          char *cp;
 2714          size_t i;
 2715 
 2716          i = PTR2SIZE(colon - *linebuf);
 2717          cp = n_lofi_alloc(i +1);
 2718          memcpy(cp, *linebuf, i);
 2719          cp[i] = '\0';
 2720          i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
 2721          n_lofi_free(cp);
 2722          if(!i)
 2723             continue;
 2724 #endif
 2725       }
 2726 
 2727       /* It could be a plain existence test */
 2728       if(sep->ss_field_exists){
 2729          rv = TRU1;
 2730          break;
 2731       }
 2732 
 2733       /* Need to check the body */
 2734       while(blankchar(*++colon))
 2735          ;
 2736       in.s = colon;
 2737 
 2738       /* Shall we split into address list and match as/the addresses only?
 2739        * TODO at some later time we should ignore and log efforts to search
 2740        * TODO a skinned address list if we know the header has none such */
 2741       if(sep->ss_skin){
 2742          if((np = lextract(in.s, GSKIN)) == NULL)
 2743             continue;
 2744          out.s = np->n_name;
 2745       }else{
 2746          np = NULL;
 2747          in.l = strlen(in.s);
 2748          mime_fromhdr(&in, &out, TD_ICONV);
 2749       }
 2750 
 2751 jnext_name:
 2752 #ifdef HAVE_REGEX
 2753       if(sep->ss_bodyre != NULL)
 2754          rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
 2755       else
 2756 #endif
 2757          rv = substr(out.s, sep->ss_body);
 2758 
 2759       if(np == NULL)
 2760          free(out.s);
 2761       if(rv)
 2762          break;
 2763       if(np != NULL && (np = np->n_flink) != NULL){
 2764          out.s = np->n_name;
 2765          goto jnext_name;
 2766       }
 2767    }
 2768 
 2769 jleave:
 2770    if(match == a_ITER)
 2771       n_lofi_free(fiter.s);
 2772    NYD_LEAVE;
 2773    return rv;
 2774 }
 2775 
 2776 FL char const *
 2777 n_header_is_standard(char const *name, size_t len){
 2778    static char const * const names[] = {
 2779       "Bcc", "Cc", "From",
 2780       "In-Reply-To", "Mail-Followup-To",
 2781       "Message-ID", "References", "Reply-To",
 2782       "Sender", "Subject", "To",
 2783       "Mailx-Command",
 2784       "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
 2785       "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
 2786       NULL
 2787    };
 2788    char const * const *rv;
 2789    NYD_ENTER;
 2790 
 2791    if(len == UIZ_MAX)
 2792       len = strlen(name);
 2793 
 2794    for(rv = names; *rv != NULL; ++rv)
 2795       if(!ascncasecmp(*rv, name, len))
 2796          break;
 2797    NYD_LEAVE;
 2798    return *rv;
 2799 }
 2800 
 2801 FL bool_t
 2802 n_header_add_custom(struct n_header_field **hflp, char const *dat,
 2803       bool_t heap){
 2804    size_t i;
 2805    ui32_t nl, bl;
 2806    char const *cp;
 2807    struct n_header_field *hfp;
 2808    NYD_ENTER;
 2809 
 2810    hfp = NULL;
 2811 
 2812    /* For (-C) convenience, allow leading WS */
 2813    while(blankchar(*dat))
 2814       ++dat;
 2815 
 2816    /* Isolate the header field from the body */
 2817    for(cp = dat;; ++cp){
 2818       if(fieldnamechar(*cp))
 2819          continue;
 2820       if(*cp == '\0'){
 2821          if(cp == dat)
 2822             goto jename;
 2823       }else if(*cp != ':' && !blankchar(*cp)){
 2824 jename:
 2825          cp = N_("Invalid custom header (not \"field: body\"): %s\n");
 2826          goto jerr;
 2827       }
 2828       break;
 2829    }
 2830    nl = (ui32_t)PTR2SIZE(cp - dat);
 2831    if(nl == 0)
 2832       goto jename;
 2833 
 2834    /* Verify the custom header does not use standard/managed field name */
 2835    if(n_header_is_standard(dat, nl) != NULL){
 2836       cp = N_("Custom headers cannot use standard header names: %s\n");
 2837       goto jerr;
 2838    }
 2839 
 2840    /* Skip on over to the body */
 2841    while(blankchar(*cp))
 2842       ++cp;
 2843    if(*cp++ != ':')
 2844       goto jename;
 2845    while(blankchar(*cp))
 2846       ++cp;
 2847    bl = (ui32_t)strlen(cp);
 2848    for(i = bl++; i-- != 0;)
 2849       if(cntrlchar(cp[i])){
 2850          cp = N_("Invalid custom header: contains control characters: %s\n");
 2851          goto jerr;
 2852       }
 2853 
 2854    i = n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl +1 + bl;
 2855    *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
 2856    hfp->hf_next = NULL;
 2857    hfp->hf_nl = nl;
 2858    hfp->hf_bl = bl - 1;
 2859    memcpy(hfp->hf_dat, dat, nl);
 2860       hfp->hf_dat[nl++] = '\0';
 2861       memcpy(hfp->hf_dat + nl, cp, bl);
 2862 jleave:
 2863    NYD_LEAVE;
 2864    return (hfp != NULL);
 2865 
 2866 jerr:
 2867    n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
 2868    goto jleave;
 2869 }
 2870 
 2871 /* s-it-mode */