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 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7  * SPDX-License-Identifier: BSD-3-Clause
8  */
9 /*
10  * Copyright (c) 1980, 1993
11  *      The Regents of the University of California.  All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 #undef su_FILE
38 #define su_FILE header
39 #define mx_SOURCE
40 
41 #ifndef mx_HAVE_AMALGAMATION
42 # include "mx/nail.h"
43 #endif
44 
45 #include <pwd.h>
46 
47 #include <su/cs.h>
48 #include <su/icodec.h>
49 #include <su/mem.h>
50 
51 #include "mx/cmd.h"
52 #include "mx/cmd-mlist.h"
53 #include "mx/colour.h"
54 #include "mx/file-streams.h"
55 #include "mx/names.h"
56 #include "mx/termios.h"
57 #include "mx/ui-str.h"
58 #include "mx/url.h"
59 
60 /* TODO fake */
61 #include "su/code-in.h"
62 
63 struct a_header_cmatch_data{
64    u32 hcmd_len_x;      /* Length of .tdata,.. */
65    u32 hcmd_len_min;    /* ..less all optional entries */
66    char const *hcmd_data;  /* Template date - see a_header_cmatch_data[] */
67 };
68 
69 /* Template characters for cmatch_data.tdata:
70  * 'A'   An upper case char
71  * 'a'   A lower case char
72  * ' '   A space
73  * '0'   A digit
74  * 'O'   An optional digit or space; MUST be followed by '0space'!
75  * ':'   A colon
76  * '+'  Either a plus or a minus sign */
77 static struct a_header_cmatch_data const a_header_cmatch_data[] = {
78    {24, 23, "Aaa Aaa O0 00:00:00 0000"},     /* BSD/ISO C90 ctime */
79    {28, 27, "Aaa Aaa O0 00:00:00 AAA 0000"}, /* BSD tmz */
80    {21, 20, "Aaa Aaa O0 00:00 0000"},        /* SysV ctime */
81    {25, 24, "Aaa Aaa O0 00:00 AAA 0000"},    /* SysV tmz */
82    /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
83     * in the wild (by UW-imap) */
84    {30, 29, "Aaa Aaa O0 00:00:00 0000 +0000"},
85    /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
86     * zone strings; note that 1. is strictly speaking not correct as some
87     * letters are not used, and 2. is not because only "UT" is defined */
88 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
89    {28 - 2, 27 - 2, __reuse},
90    {28 - 1, 27 - 1, __reuse},
91    {28 - 0, 27 - 0, __reuse},
92    {0, 0, NULL}
93 };
94 #define a_HEADER_DATE_MINLEN 20
95 CTAV(n_FROM_DATEBUF > sizeof("From_") -1 + 3 + 30 +1);
96 
97 /* Savage extract date field from From_ line.  linelen is convenience as line
98  * must be terminated (but it may end in a newline [sequence]).
99  * Return whether the From_ line was parsed successfully (-1 if the From_ line
100  * wasn't really RFC 4155 compliant) */
101 static int a_header_extract_date_from_from_(char const *line, uz linelen,
102             char datebuf[n_FROM_DATEBUF]);
103 
104 /* Skip over "word" as found in From_ line */
105 static char const *a_header__from_skipword(char const *wp);
106 
107 /* Match the date string against the date template (tp), return if match.
108  * See a_header_cmatch_data[] for template character description */
109 static boole a_header_cmatch(char const *tp, char const *date);
110 
111 /* Check whether date is a valid 'From_' date.
112  * (Rather ctime(3) generated dates, according to RFC 4155) */
113 static boole a_header_is_date(char const *date);
114 
115 /* JulianDayNumber converter(s) */
116 static uz a_header_gregorian_to_jdn(u32 y, u32 m, u32 d);
117 #if 0
118 static void a_header_jdn_to_gregorian(uz jdn,
119                u32 *yp, u32 *mp, u32 *dp);
120 #endif
121 
122 /* ... And place the extracted date in `date' */
123 static void a_header_parse_from_(struct message *mp,
124       char date[n_FROM_DATEBUF]);
125 
126 /* Convert the domain part of a skinned address to IDNA.
127  * If an error occurs before Unicode information is available, revert the IDNA
128  * error to a normal CHAR one so that the error message doesn't talk Unicode */
129 #ifdef mx_HAVE_IDNA
130 static struct n_addrguts *a_header_idna_apply(struct n_addrguts *agp);
131 #endif
132 
133 /* Classify and check a (possibly skinned) header body according to RFC
134  * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
135  * also a file or a pipe command, so check that first, then.
136  * Otherwise perform content checking and isolate the domain part (for IDNA).
137  * issingle_hack <-> GNOT_A_LIST */
138 static boole a_header_addrspec_check(struct n_addrguts *agp, boole skinned,
139                boole issingle_hack);
140 
141 /* Return the next header field found in the given message.
142  * Return >= 0 if something found, < 0 elsewise.
143  * "colon" is set to point to the colon in the header.
144  * Must deal with \ continuations & other such fraud */
145 static long a_gethfield(enum n_header_extract_flags hef, FILE *f,
146                char **linebuf, uz *linesize, long rem, char **colon);
147 
148 static int                 msgidnextc(char const **cp, int *status);
149 
150 static char const *        nexttoken(char const *cp);
151 
152 static int
a_header_extract_date_from_from_(char const * line,uz linelen,char datebuf[n_FROM_DATEBUF])153 a_header_extract_date_from_from_(char const *line, uz linelen,
154    char datebuf[n_FROM_DATEBUF])
155 {
156    int rv;
157    char const *cp = line;
158    NYD_IN;
159 
160    rv = 1;
161 
162    /* "From " */
163    cp = a_header__from_skipword(cp);
164    if (cp == NULL)
165       goto jerr;
166    /* "addr-spec " */
167    cp = a_header__from_skipword(cp);
168    if (cp == NULL)
169       goto jerr;
170    if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
171          (cp[2] == 'y' || cp[2] == 'Y')){
172       cp = a_header__from_skipword(cp);
173       if (cp == NULL)
174          goto jerr;
175    }
176    /* It seems there are invalid MBOX archives in the wild, compare
177     * . http://bugs.debian.org/624111
178     * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
179     * What they do is that they obfuscate the address to "name at host",
180     * and even "name at host dot dom dot dom.
181     * The [Aa][Tt] is also RFC 733, so be tolerant */
182    else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
183          cp[2] == ' '){
184       rv = -1;
185       cp += 3;
186 jat_dot:
187       cp = a_header__from_skipword(cp);
188       if (cp == NULL)
189          goto jerr;
190       if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
191             (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
192          cp += 4;
193          goto jat_dot;
194       }
195    }
196 
197    linelen -= P2UZ(cp - line);
198    if (linelen < a_HEADER_DATE_MINLEN)
199       goto jerr;
200    if (cp[linelen - 1] == '\n') {
201       --linelen;
202       /* (Rather IMAP/POP3 only) */
203       if (cp[linelen - 1] == '\r')
204          --linelen;
205       if (linelen < a_HEADER_DATE_MINLEN)
206          goto jerr;
207    }
208    if (linelen >= n_FROM_DATEBUF)
209       goto jerr;
210 
211 jleave:
212    su_mem_copy(datebuf, cp, linelen);
213    datebuf[linelen] = '\0';
214    NYD_OU;
215    return rv;
216 jerr:
217    cp = _("<Unknown date>");
218    linelen = su_cs_len(cp);
219    if(linelen >= n_FROM_DATEBUF)
220       linelen = n_FROM_DATEBUF -1;
221    rv = 0;
222    goto jleave;
223 }
224 
225 static char const *
a_header__from_skipword(char const * wp)226 a_header__from_skipword(char const *wp)
227 {
228    char c = 0;
229    NYD2_IN;
230 
231    if (wp != NULL) {
232       while ((c = *wp++) != '\0' && !su_cs_is_blank(c)) {
233          if (c == '"') {
234             while ((c = *wp++) != '\0' && c != '"')
235                ;
236             if (c != '"')
237                --wp;
238          }
239       }
240       for (; su_cs_is_blank(c); c = *wp++)
241          ;
242    }
243    NYD2_OU;
244    return (c == 0 ? NULL : wp - 1);
245 }
246 
247 static boole
a_header_cmatch(char const * tp,char const * date)248 a_header_cmatch(char const *tp, char const *date){
249    boole rv;
250    char tc, dc;
251    NYD2_IN;
252 
253    for(;;){
254       tc = *tp++;
255       dc = *date++;
256       if((rv = (tc == '\0' && dc == '\0')))
257          break; /* goto jleave; */
258 
259       switch(tc){
260       case 'a':
261          if(!su_cs_is_lower(dc))
262             goto jleave;
263          break;
264       case 'A':
265          if(!su_cs_is_upper(dc))
266             goto jleave;
267          break;
268       case ' ':
269          if(dc != ' ')
270             goto jleave;
271          break;
272       case '0':
273          if(!su_cs_is_digit(dc))
274             goto jleave;
275          break;
276       case 'O':
277          if(!su_cs_is_digit(dc) && dc != ' ')
278                goto jleave;
279          /*tc = *tp++*/ ++tp; /* is "0"! */
280          dc = *date;
281          if(su_cs_is_digit(dc))
282             ++date;
283          break;
284       case ':':
285          if(dc != ':')
286             goto jleave;
287          break;
288       case '+':
289          if(dc != '+' && dc != '-')
290             goto jleave;
291          break;
292       }
293    }
294 jleave:
295    NYD2_OU;
296    return rv;
297 }
298 
299 static boole
a_header_is_date(char const * date)300 a_header_is_date(char const *date){
301    struct a_header_cmatch_data const *hcmdp;
302    uz dl;
303    boole rv;
304    NYD2_IN;
305 
306    rv = FAL0;
307 
308    if((dl = su_cs_len(date)) >= a_HEADER_DATE_MINLEN)
309       for(hcmdp = a_header_cmatch_data; hcmdp->hcmd_data != NULL; ++hcmdp)
310          if(dl >= hcmdp->hcmd_len_min && dl <= hcmdp->hcmd_len_x &&
311                (rv = a_header_cmatch(hcmdp->hcmd_data, date)))
312             break;
313    NYD2_OU;
314    return rv;
315 }
316 
317 static uz
a_header_gregorian_to_jdn(u32 y,u32 m,u32 d)318 a_header_gregorian_to_jdn(u32 y, u32 m, u32 d){
319    /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
320     * (via third hand, plus adjustments).
321     * This algorithm is supposed to work for all dates in between 1582-10-15
322     * (0001-01-01 but that not Gregorian) and 65535-12-31 */
323    uz jdn;
324    NYD2_IN;
325 
326 #if 0
327    if(y == 0)
328       y = 1;
329    if(m == 0)
330       m = 1;
331    if(d == 0)
332       d = 1;
333 #endif
334 
335    if(m > 2)
336       m -= 3;
337    else{
338       m += 9;
339       --y;
340    }
341    jdn = y;
342    jdn /= 100;
343    y -= 100 * jdn;
344    y *= 1461;
345    y >>= 2;
346    jdn *= 146097;
347    jdn >>= 2;
348    jdn += y;
349    jdn += d;
350    jdn += 1721119;
351    m *= 153;
352    m += 2;
353    m /= 5;
354    jdn += m;
355    NYD2_OU;
356    return jdn;
357 }
358 
359 #if 0
360 static void
361 a_header_jdn_to_gregorian(uz jdn, u32 *yp, u32 *mp, u32 *dp){
362    /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
363     * (via third hand, plus adjustments) */
364    uz y, x;
365    NYD2_IN;
366 
367    jdn -= 1721119;
368    jdn <<= 2;
369    --jdn;
370    y =   jdn / 146097;
371          jdn %= 146097;
372    jdn |= 3;
373    y *= 100;
374    y +=  jdn / 1461;
375          jdn %= 1461;
376    jdn += 4;
377    jdn >>= 2;
378    x = jdn;
379    jdn <<= 2;
380    jdn += x;
381    jdn -= 3;
382    x =   jdn / 153;  /* x -> month */
383          jdn %= 153;
384    jdn += 5;
385    jdn /= 5; /* jdn -> day */
386    if(x < 10)
387       x += 3;
388    else{
389       x -= 9;
390       ++y;
391    }
392 
393    *yp = (u32)(y & 0xFFFF);
394    *mp = (u32)(x & 0xFF);
395    *dp = (u32)(jdn & 0xFF);
396    NYD2_OU;
397 }
398 #endif /* 0 */
399 
400 static void
a_header_parse_from_(struct message * mp,char date[n_FROM_DATEBUF])401 a_header_parse_from_(struct message *mp, char date[n_FROM_DATEBUF]){
402    FILE *ibuf;
403    int hlen;
404    char *hline;
405    uz hsize;
406    NYD2_IN;
407 
408    mx_fs_linepool_aquire(&hline, &hsize);
409 
410    if((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
411          (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
412       a_header_extract_date_from_from_(hline, hlen, date);
413 
414    mx_fs_linepool_release(hline, hsize);
415    NYD2_OU;
416 }
417 
418 #ifdef mx_HAVE_IDNA
419 static struct n_addrguts *
a_header_idna_apply(struct n_addrguts * agp)420 a_header_idna_apply(struct n_addrguts *agp){
421    struct n_string idna_ascii;
422    NYD_IN;
423 
424    n_string_creat_auto(&idna_ascii);
425 
426    if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
427          agp->ag_slen - agp->ag_sdom_start))
428       agp->ag_n_flags ^= mx_NAME_ADDRSPEC_ERR_IDNA | mx_NAME_ADDRSPEC_ERR_CHAR;
429    else{
430       /* Replace the domain part of .ag_skinned with IDNA version */
431       n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
432 
433       agp->ag_skinned = n_string_cp(&idna_ascii);
434       agp->ag_slen = idna_ascii.s_len;
435       agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
436          mx_NAME_NAME_SALLOC | mx_NAME_SKINNED | mx_NAME_IDNA, '\0');
437    }
438    NYD_OU;
439    return agp;
440 }
441 #endif /* mx_HAVE_IDNA */
442 
443 static boole
a_header_addrspec_check(struct n_addrguts * agp,boole skinned,boole issingle_hack)444 a_header_addrspec_check(struct n_addrguts *agp, boole skinned,
445       boole issingle_hack)
446 {
447    char *addr, *p;
448    union {boole b; char c; unsigned char u; u32 ui32; s32 si32;} c;
449    enum{
450       a_NONE,
451       a_IDNA_ENABLE = 1u<<0,
452       a_IDNA_APPLY = 1u<<1,
453       a_REDO_NODE_AFTER_ADDR = 1u<<2,
454       a_RESET_MASK = a_IDNA_ENABLE | a_IDNA_APPLY | a_REDO_NODE_AFTER_ADDR,
455       a_IN_QUOTE = 1u<<8,
456       a_IN_AT = 1u<<9,
457       a_IN_DOMAIN = 1u<<10,
458       a_DOMAIN_V6 = 1u<<11,
459       a_DOMAIN_MASK = a_IN_DOMAIN | a_DOMAIN_V6
460    } flags;
461    NYD_IN;
462 
463    flags = a_NONE;
464 #ifdef mx_HAVE_IDNA
465    if(!ok_blook(idna_disable))
466       flags = a_IDNA_ENABLE;
467 #endif
468 
469    if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
470       agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
471             mx_NAME_ADDRSPEC_ERR_EMPTY, '\0');
472       goto jleave;
473    }
474 
475    addr = agp->ag_skinned;
476 
477    /* If the field is not a recipient, it cannot be a file or a pipe */
478    if (!skinned)
479       goto jaddr_check;
480 
481    /* When changing any of the following adjust any RECIPIENTADDRSPEC;
482     * grep the latter for the complete picture */
483    if (*addr == '|') {
484       agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISPIPE;
485       goto jleave;
486    }
487    if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
488          (addr[0] == '-' && addr[1] == '\0'))
489       goto jisfile;
490    if (su_mem_find(addr, '@', agp->ag_slen) == NULL) {
491       if (*addr == '+')
492          goto jisfile;
493       for (p = addr; (c.c = *p); ++p) {
494          if (c.c == '!' || c.c == '%')
495             break;
496          if (c.c == '/') {
497 jisfile:
498             agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISFILE;
499             goto jleave;
500          }
501       }
502    }
503 
504 jaddr_check:
505    /* TODO This is false.  If super correct this should work on wide
506     * TODO characters, just in case (some bytes of) the ASCII set is (are)
507     * TODO shared; it may yet tear apart multibyte sequences, possibly.
508     * TODO All this should interact with mime_enc_mustquote(), too!
509     * TODO That is: once this is an object, we need to do this in a way
510     * TODO that it is valid for the wire format (instead)! */
511    /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
512     * TODO Note this correlats with addrspec_with_guts() which is in front
513     * TODO of us and encapsulates (what it thinks is, sigh) the address
514     * TODO boundary.  ALL THIS should be one object that knows how to deal */
515    flags &= a_RESET_MASK;
516    for (p = addr; (c.c = *p++) != '\0';) {
517       if (c.c == '"') {
518          flags ^= a_IN_QUOTE;
519       } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
520 #ifdef mx_HAVE_IDNA
521          if ((flags & (a_IN_DOMAIN | a_IDNA_ENABLE)) ==
522                (a_IN_DOMAIN | a_IDNA_ENABLE))
523             flags |= a_IDNA_APPLY;
524          else
525 #endif
526             break;
527       } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
528          if ((c.c == ']' && *p != '\0') || c.c == '\\' || su_cs_is_white(c.c))
529             break;
530       } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
531          /*EMPTY*/;
532       } else if (c.c == '\\' && *p != '\0') {
533          ++p;
534       } else if (c.c == '@') {
535          if(flags & a_IN_AT){
536             agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
537                   mx_NAME_ADDRSPEC_ERR_ATSEQ, c.u);
538             goto jleave;
539          }
540          agp->ag_sdom_start = P2UZ(p - addr);
541          agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
542          flags &= ~a_DOMAIN_MASK;
543          flags |= (*p == '[') ? a_IN_AT | a_IN_DOMAIN | a_DOMAIN_V6
544                : a_IN_AT | a_IN_DOMAIN;
545          continue;
546       }
547       /* TODO This interferes with our alias handling, which allows :!
548        * TODO Update manual on support (search the several ALIASCOLON)! */
549       else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
550             c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
551             c.c == '\\' || c.c == ',' || su_cs_is_blank(c.c))
552          break;
553       flags &= ~a_IN_AT;
554    }
555    if (c.c != '\0') {
556       agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
557             mx_NAME_ADDRSPEC_ERR_CHAR, c.u);
558       goto jleave;
559    }
560 
561    /* If we do not think this is an address we may treat it as an alias name
562     * if and only if the original input is identical to the skinned version */
563    if(!(agp->ag_n_flags & mx_NAME_ADDRSPEC_ISADDR) &&
564          !su_cs_cmp(agp->ag_skinned, agp->ag_input)){
565       /* TODO This may be an UUCP address */
566       agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISNAME;
567       if(!mx_alias_is_valid_name(agp->ag_input))
568          agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
569                mx_NAME_ADDRSPEC_ERR_NAME, '.');
570    }else{
571       /* If we seem to know that this is an address.  Ensure this is correct
572        * according to RFC 5322 TODO the entire address parser should be like
573        * TODO that for one, and then we should know whether structured or
574        * TODO unstructured, and just parse correctly overall!
575        * TODO In addition, this can be optimised a lot.
576        * TODO And it is far from perfect: it should not forget whether no
577        * TODO whitespace followed some snippet, and it was written hastily.
578        * TODO It is even wrong sometimes.  Not only for strange cases */
579       struct a_token{
580          struct a_token *t_last;
581          struct a_token *t_next;
582          enum{
583             a_T_TATOM = 1u<<0,
584             a_T_TCOMM = 1u<<1,
585             a_T_TQUOTE = 1u<<2,
586             a_T_TADDR = 1u<<3,
587             a_T_TMASK = (1u<<4) - 1,
588 
589             a_T_SPECIAL = 1u<<8     /* An atom actually needs to go TQUOTE */
590          } t_f;
591          u8 t__pad[4];
592          uz t_start;
593          uz t_end;
594       } *thead, *tcurr, *tp;
595 
596       struct n_string ost, *ostp;
597       char const *cp, *cp1st, *cpmax, *xp;
598       void *lofi_snap;
599 
600       /* Name and domain must be non-empty */
601       if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
602 jeat:
603          c.c = '@';
604          agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
605                mx_NAME_ADDRSPEC_ERR_ATSEQ, c.u);
606          goto jleave;
607       }
608 
609       cp = agp->ag_input;
610 
611       /* Nothing to do if there is only an address (in angle brackets) */
612       /* TODO This is wrong since we allow invalid constructs in local-part
613        * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
614        * TODO "abcd"@abc.  Etc. */
615       if(agp->ag_iaddr_start == 0){
616          /* No @ seen? */
617          if(!(agp->ag_n_flags & mx_NAME_ADDRSPEC_ISADDR))
618             goto jeat;
619          if(agp->ag_iaddr_aend == agp->ag_ilen)
620             goto jleave;
621       }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
622             agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
623             cp[agp->ag_iaddr_aend] == '>'){
624          /* No @ seen?  Possibly insert n_nodename() */
625          if(!(agp->ag_n_flags & mx_NAME_ADDRSPEC_ISADDR)){
626             cp = &agp->ag_input[agp->ag_iaddr_start];
627             cpmax = &agp->ag_input[agp->ag_iaddr_aend];
628             goto jinsert_domain;
629          }
630          goto jleave;
631       }
632 
633       /* It is not, so parse off all tokens, then resort and rejoin */
634       lofi_snap = n_lofi_snap_create();
635 
636       cp1st = cp;
637       if((c.ui32 = agp->ag_iaddr_start) > 0)
638          --c.ui32;
639       cpmax = &cp[c.ui32];
640 
641       thead = tcurr = NULL;
642 jnode_redo:
643       for(tp = NULL; cp < cpmax;){
644          switch((c.c = *cp)){
645          case '(':
646             if(tp != NULL)
647                tp->t_end = P2UZ(cp - cp1st);
648             tp = n_lofi_alloc(sizeof *tp);
649             tp->t_next = NULL;
650             if((tp->t_last = tcurr) != NULL)
651                tcurr->t_next = tp;
652             else
653                thead = tp;
654             tcurr = tp;
655             tp->t_f = a_T_TCOMM;
656             tp->t_start = P2UZ(++cp - cp1st);
657             xp = skip_comment(cp);
658             tp->t_end = P2UZ(xp - cp1st);
659             cp = xp;
660             if(tp->t_end > tp->t_start){
661                if(xp[-1] == ')')
662                   --tp->t_end;
663                else{
664                   /* No closing comment - strip trailing whitespace */
665                   while(su_cs_is_blank(*--xp))
666                      if(--tp->t_end == tp->t_start)
667                         break;
668                }
669             }
670             tp = NULL;
671             break;
672 
673          case '"':
674             if(tp != NULL)
675                tp->t_end = P2UZ(cp - cp1st);
676             tp = n_lofi_alloc(sizeof *tp);
677             tp->t_next = NULL;
678             if((tp->t_last = tcurr) != NULL)
679                tcurr->t_next = tp;
680             else
681                thead = tp;
682             tcurr = tp;
683             tp->t_f = a_T_TQUOTE;
684             tp->t_start = P2UZ(++cp - cp1st);
685             for(xp = cp; xp < cpmax; ++xp){
686                if((c.c = *xp) == '"')
687                   break;
688                if(c.c == '\\' && xp[1] != '\0')
689                   ++xp;
690             }
691             tp->t_end = P2UZ(xp - cp1st);
692             cp = &xp[1];
693             if(tp->t_end > tp->t_start){
694                /* No closing quote - strip trailing whitespace */
695                if(*xp != '"'){
696                   while(su_cs_is_blank(*xp--))
697                      if(--tp->t_end == tp->t_start)
698                         break;
699                }
700             }
701             tp = NULL;
702             break;
703 
704          default:
705             if(su_cs_is_blank(c.c)){
706                if(tp != NULL)
707                   tp->t_end = P2UZ(cp - cp1st);
708                tp = NULL;
709                ++cp;
710                break;
711             }
712 
713             if(tp == NULL){
714                tp = n_lofi_alloc(sizeof *tp);
715                tp->t_next = NULL;
716                if((tp->t_last = tcurr) != NULL)
717                   tcurr->t_next = tp;
718                else
719                   thead = tp;
720                tcurr = tp;
721                tp->t_f = a_T_TATOM;
722                tp->t_start = P2UZ(cp - cp1st);
723             }
724             ++cp;
725 
726             /* Reverse solidus transforms the following into a quoted-pair, and
727              * therefore (must occur in comment or quoted-string only) the
728              * entire atom into a quoted string */
729             if(c.c == '\\'){
730                tp->t_f |= a_T_SPECIAL;
731                if(cp < cpmax)
732                   ++cp;
733                break;
734             }
735 
736             /* Is this plain RFC 5322 "atext", or "specials"?
737              * TODO Because we don't know structured/unstructured, nor anything
738              * TODO else, we need to treat "dot-atom" as being identical to
739              * TODO "specials".
740              * However, if the 8th bit is set, this will be RFC 2047 converted
741              * and the entire sequence is skipped */
742             if(!(c.u & 0x80) && !su_cs_is_alnum(c.c) &&
743                   c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
744                   c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
745                   c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
746                   c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
747                   c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
748                tp->t_f |= a_T_SPECIAL;
749             break;
750          }
751       }
752       if(tp != NULL)
753          tp->t_end = P2UZ(cp - cp1st);
754 
755       if(!(flags & a_REDO_NODE_AFTER_ADDR)){
756          flags |= a_REDO_NODE_AFTER_ADDR;
757 
758          /* The local-part may be in quotes.. */
759          if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
760                tp->t_end == agp->ag_iaddr_start - 1){
761             /* ..so backward extend it, including the starting quote */
762             /* TODO This is false and the code below #if 0 away.  We would
763              * TODO need to create a properly quoted local-part HERE AND NOW
764              * TODO and REPLACE the original data with that version, but the
765              * TODO current code cannot do that.  The node needs the data,
766              * TODO not only offsets for that, for example.  If we had all that
767              * TODO the code below could produce a really valid thing */
768             if(tp->t_start > 0)
769                --tp->t_start;
770             if(tp->t_start > 0 &&
771                   (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
772                      agp->ag_input[tp->t_start - 1] == '\\')
773                --tp->t_start;
774             tp->t_f = a_T_TADDR | a_T_SPECIAL;
775          }else{
776             tp = n_lofi_alloc(sizeof *tp);
777             tp->t_next = NULL;
778             if((tp->t_last = tcurr) != NULL)
779                tcurr->t_next = tp;
780             else
781                thead = tp;
782             tcurr = tp;
783             tp->t_f = a_T_TADDR;
784             tp->t_start = agp->ag_iaddr_start;
785             /* TODO Very special case because of our hacky non-object-based and
786              * TODO non-compliant address parser.  Note */
787             if(tp->t_last == NULL && tp->t_start > 0)
788                tp->t_start = 0;
789             if(agp->ag_input[tp->t_start] == '<')
790                ++tp->t_start;
791 
792             /* TODO Very special check for whether we need to massage the
793              * TODO local part.  This is wrong, but otherwise even more so */
794 #if 0
795             cp = &agp->ag_input[tp->t_start];
796             cpmax = &agp->ag_input[agp->ag_iaddr_aend];
797             while(cp < cpmax){
798                c.c = *cp++;
799                if(!(c.u & 0x80) && !su_cs_is_alnum(c.c) &&
800                      c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
801                      c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
802                      c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
803                      c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
804                      c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
805                   tp->t_f |= a_T_SPECIAL;
806                   break;
807                }
808             }
809 #endif
810          }
811          tp->t_end = agp->ag_iaddr_aend;
812          ASSERT(tp->t_start <= tp->t_end);
813          tp = NULL;
814 
815          cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
816          cpmax = &agp->ag_input[agp->ag_ilen];
817          if(cp < cpmax)
818             goto jnode_redo;
819       }
820 
821       /* Nothing may follow the address, move it to the end */
822       ASSERT(tcurr != NULL);
823       if(tcurr != NULL && !(tcurr->t_f & a_T_TADDR)){
824          for(tp = thead; tp != NULL; tp = tp->t_next){
825             if(tp->t_f & a_T_TADDR){
826                if(tp->t_last != NULL)
827                   tp->t_last->t_next = tp->t_next;
828                else
829                   thead = tp->t_next;
830                if(tp->t_next != NULL)
831                   tp->t_next->t_last = tp->t_last;
832 
833                tcurr = tp;
834                while(tp->t_next != NULL)
835                   tp = tp->t_next;
836                tp->t_next = tcurr;
837                tcurr->t_last = tp;
838                tcurr->t_next = NULL;
839                break;
840             }
841          }
842       }
843 
844       /* Make ranges contiguous: ensure a continuous range of atoms is
845        * converted to a SPECIAL one if at least one of them requires it */
846       for(tp = thead; tp != NULL; tp = tp->t_next){
847          if(tp->t_f & a_T_SPECIAL){
848             tcurr = tp;
849             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
850                tp->t_f |= a_T_SPECIAL;
851             tp = tcurr;
852             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
853                tp->t_f |= a_T_SPECIAL;
854             if(tp == NULL)
855                break;
856          }
857       }
858 
859       /* And yes, we want quotes to extend as much as possible */
860       for(tp = thead; tp != NULL; tp = tp->t_next){
861          if(tp->t_f & a_T_TQUOTE){
862             tcurr = tp;
863             while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
864                tp->t_f |= a_T_SPECIAL;
865             tp = tcurr;
866             while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
867                tp->t_f |= a_T_SPECIAL;
868             if(tp == NULL)
869                break;
870          }
871       }
872 
873       /* Then rejoin */
874       ostp = n_string_creat_auto(&ost);
875       if((c.ui32 = agp->ag_ilen) <= U32_MAX >> 1)
876          ostp = n_string_reserve(ostp, c.ui32 <<= 1);
877 
878       for(tcurr = thead; tcurr != NULL;){
879          if(tcurr != thead)
880             ostp = n_string_push_c(ostp, ' ');
881          if(tcurr->t_f & a_T_TADDR){
882             if(tcurr->t_last != NULL)
883                ostp = n_string_push_c(ostp, '<');
884             agp->ag_iaddr_start = ostp->s_len;
885             /* Now it is terrible to say, but if that thing contained
886              * quotes, then those may contain quoted-pairs! */
887 #if 0
888             if(!(tcurr->t_f & a_T_SPECIAL)){
889 #endif
890                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
891                      (tcurr->t_end - tcurr->t_start));
892 #if 0
893             }else{
894                boole quot, esc;
895 
896                ostp = n_string_push_c(ostp, '"');
897                quot = TRU1;
898 
899                cp = &cp1st[tcurr->t_start];
900                cpmax = &cp1st[tcurr->t_end];
901                for(esc = FAL0; cp < cpmax;){
902                   if((c.c = *cp++) == '\\' && !esc){
903                      if(cp < cpmax && (*cp == '"' || *cp == '\\'))
904                         esc = TRU1;
905                   }else{
906                      if(esc || c.c == '"')
907                         ostp = n_string_push_c(ostp, '\\');
908                      else if(c.c == '@'){
909                         ostp = n_string_push_c(ostp, '"');
910                         quot = FAL0;
911                      }
912                      ostp = n_string_push_c(ostp, c.c);
913                      esc = FAL0;
914                   }
915                }
916             }
917 #endif
918             agp->ag_iaddr_aend = ostp->s_len;
919 
920             if(tcurr->t_last != NULL)
921                ostp = n_string_push_c(ostp, '>');
922             tcurr = tcurr->t_next;
923          }else if(tcurr->t_f & a_T_TCOMM){
924             ostp = n_string_push_c(ostp, '(');
925             ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
926                   (tcurr->t_end - tcurr->t_start));
927             while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
928                tcurr = tp;
929                ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
930                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
931                      (tcurr->t_end - tcurr->t_start));
932             }
933             ostp = n_string_push_c(ostp, ')');
934             tcurr = tcurr->t_next;
935          }else if(tcurr->t_f & a_T_TQUOTE){
936 jput_quote:
937             ostp = n_string_push_c(ostp, '"');
938             tp = tcurr;
939             do/* while tcurr && TATOM||TQUOTE */{
940                cp = &cp1st[tcurr->t_start];
941                cpmax = &cp1st[tcurr->t_end];
942                if(cp == cpmax)
943                   continue;
944 
945                if(tcurr != tp)
946                   ostp = n_string_push_c(ostp, ' ');
947 
948                if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
949                   ostp = n_string_push_buf(ostp, cp, P2UZ(cpmax - cp));
950                else{
951                   boole esc;
952 
953                   for(esc = FAL0; cp < cpmax;){
954                      if((c.c = *cp++) == '\\' && !esc){
955                         if(cp < cpmax && (*cp == '"' || *cp == '\\'))
956                            esc = TRU1;
957                      }else{
958                         if(esc || c.c == '"'){
959 jput_quote_esc:
960                            ostp = n_string_push_c(ostp, '\\');
961                         }
962                         ostp = n_string_push_c(ostp, c.c);
963                         esc = FAL0;
964                      }
965                   }
966                   if(esc){
967                      c.c = '\\';
968                      goto jput_quote_esc;
969                   }
970                }
971             }while((tcurr = tcurr->t_next) != NULL &&
972                (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
973             ostp = n_string_push_c(ostp, '"');
974          }else if(tcurr->t_f & a_T_SPECIAL)
975             goto jput_quote;
976          else{
977             /* Can we use a fast join mode? */
978             for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
979                if(!(tcurr->t_f & a_T_TATOM))
980                   break;
981                if(tcurr != tp)
982                   ostp = n_string_push_c(ostp, ' ');
983                ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
984                      (tcurr->t_end - tcurr->t_start));
985             }
986          }
987       }
988 
989       n_lofi_snap_unroll(lofi_snap);
990 
991       agp->ag_input = n_string_cp(ostp);
992       agp->ag_ilen = ostp->s_len;
993       /*ostp = n_string_drop_ownership(ostp);*/
994 
995       /* Name and domain must be non-empty, the second */
996       cp = &agp->ag_input[agp->ag_iaddr_start];
997       cpmax = &agp->ag_input[agp->ag_iaddr_aend];
998       if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
999          c.c = '@';
1000          agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
1001                mx_NAME_ADDRSPEC_ERR_ATSEQ, c.u);
1002          goto jleave;
1003       }
1004 
1005       addr = agp->ag_skinned = savestrbuf(cp, P2UZ(cpmax - cp));
1006 
1007       /* TODO This parser is a mess.  We do not know whether this is truly
1008        * TODO valid, and all our checks are not truly RFC conforming.
1009        * TODO Do check the skinned thing by itself once more, in order
1010        * TODO to catch problems from reordering, e.g., this additional
1011        * TODO test catches a final address without AT..
1012        * TODO This is a plain copy+paste of the weird thing above, no care */
1013       agp->ag_n_flags &= ~mx_NAME_ADDRSPEC_ISADDR;
1014       flags &= a_RESET_MASK;
1015       for (p = addr; (c.c = *p++) != '\0';) {
1016          if(c.c == '"')
1017             flags ^= a_IN_QUOTE;
1018          else if (c.u < 040 || c.u >= 0177) {
1019 #ifdef mx_HAVE_IDNA
1020                if(!(flags & a_IN_DOMAIN))
1021 #endif
1022                   break;
1023          } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
1024             if ((c.c == ']' && *p != '\0') || c.c == '\\' ||
1025                   su_cs_is_white(c.c))
1026                break;
1027          } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
1028             /*EMPTY*/;
1029          } else if (c.c == '\\' && *p != '\0') {
1030             ++p;
1031          } else if (c.c == '@') {
1032             if(flags & a_IN_AT){
1033                agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
1034                      mx_NAME_ADDRSPEC_ERR_ATSEQ, c.u);
1035                goto jleave;
1036             }
1037             flags |= a_IN_AT;
1038             agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
1039             flags &= ~a_DOMAIN_MASK;
1040             flags |= (*p == '[') ? a_IN_DOMAIN | a_DOMAIN_V6 : a_IN_DOMAIN;
1041             continue;
1042          } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
1043                c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
1044                c.c == '\\' || c.c == ',' || su_cs_is_blank(c.c))
1045             break;
1046          flags &= ~a_IN_AT;
1047       }
1048 
1049       if(c.c != '\0')
1050          agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
1051                mx_NAME_ADDRSPEC_ERR_CHAR, c.u);
1052       else if(!(agp->ag_n_flags & mx_NAME_ADDRSPEC_ISADDR)){
1053          /* This is not an address, but if we had seen angle brackets convert
1054           * it to a n_nodename() address if the name is a valid user */
1055 jinsert_domain:
1056          if(cp > &agp->ag_input[0] && cp[-1] == '<' &&
1057                cpmax <= &agp->ag_input[agp->ag_ilen] && cpmax[0] == '>'){
1058             if(su_cs_cmp(addr, ok_vlook(LOGNAME)) && getpwnam(addr) == NIL){
1059                agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
1060                      mx_NAME_ADDRSPEC_ERR_NAME, '*');
1061                goto jleave;
1062             }
1063 
1064             /* XXX However, if hostname is set to the empty string this
1065              * XXX indicates that the used *mta* will perform the
1066              * XXX auto-expansion instead.  Not so with `addrcodec' though */
1067             agp->ag_n_flags |= mx_NAME_ADDRSPEC_ISADDR;
1068             if(!issingle_hack &&
1069                   (cp = ok_vlook(hostname)) != NIL && *cp == '\0')
1070                agp->ag_n_flags |= mx_NAME_ADDRSPEC_WITHOUT_DOMAIN;
1071             else{
1072                c.ui32 = su_cs_len(cp = n_nodename(TRU1));
1073                /* This is yet IDNA converted.. */
1074                ostp = n_string_creat_auto(&ost);
1075                ostp = n_string_assign_buf(ostp, agp->ag_input, agp->ag_ilen);
1076                ostp = n_string_insert_c(ostp, agp->ag_iaddr_aend++, '@');
1077                ostp = n_string_insert_buf(ostp, agp->ag_iaddr_aend, cp,
1078                      c.ui32);
1079                agp->ag_iaddr_aend += c.ui32;
1080                agp->ag_input = n_string_cp(ostp);
1081                agp->ag_ilen = ostp->s_len;
1082                /*ostp = n_string_drop_ownership(ostp);*/
1083 
1084                cp = &agp->ag_input[agp->ag_iaddr_start];
1085                cpmax = &agp->ag_input[agp->ag_iaddr_aend];
1086                agp->ag_skinned = savestrbuf(cp, P2UZ(cpmax - cp));
1087             }
1088          }else
1089             agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
1090                   mx_NAME_ADDRSPEC_ERR_ATSEQ, '@');
1091       }
1092    }
1093 
1094 jleave:
1095 #ifdef mx_HAVE_IDNA
1096    if(!(agp->ag_n_flags & mx_NAME_ADDRSPEC_INVALID) && (flags & a_IDNA_APPLY))
1097       agp = a_header_idna_apply(agp);
1098 #endif
1099    NYD_OU;
1100    return !(agp->ag_n_flags & mx_NAME_ADDRSPEC_INVALID);
1101 }
1102 
1103 static long
a_gethfield(enum n_header_extract_flags hef,FILE * f,char ** linebuf,uz * linesize,long rem,char ** colon)1104 a_gethfield(enum n_header_extract_flags hef, FILE *f,
1105    char **linebuf, uz *linesize, long rem, char **colon)
1106 {
1107    char *line2, *cp, *cp2;
1108    uz line2size;
1109    int c, isenc;
1110    NYD2_IN;
1111 
1112    if (*linebuf == NULL)
1113       *linebuf = n_realloc(*linebuf, *linesize = 1);
1114    **linebuf = '\0';
1115 
1116    mx_fs_linepool_aquire(&line2, &line2size);
1117 
1118    for (;;) {
1119       if (--rem < 0) {
1120          rem = -1;
1121          break;
1122       }
1123       if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
1124          rem = -1;
1125          break;
1126       }
1127       if((hef & n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS) && **linebuf == '#'){
1128          /* A comment may be last before body, too, ensure empty last line */
1129          **linebuf = '\0';
1130          continue;
1131       }
1132 
1133       for (cp = *linebuf; fieldnamechar(*cp); ++cp)
1134          ;
1135       if (cp > *linebuf)
1136          while (su_cs_is_blank(*cp))
1137             ++cp;
1138       if (cp == *linebuf) /* TODO very first line of input with lead WS? */
1139          continue;
1140       /* XXX Not a header line, logging only for -t / compose mode? */
1141       if(*cp != ':'){
1142          if(!(hef & n_HEADER_EXTRACT_IGNORE_FROM_) ||
1143                !is_head(*linebuf, c, FAL0))
1144             n_err(_("Not a header line, skipping: %s\n"),
1145                n_shexp_quote_cp(*linebuf, FAL0));
1146          continue;
1147       }
1148 
1149       /* I guess we got a headline.  Handle wraparound */
1150       *colon = cp;
1151       cp = *linebuf + c;
1152       for (;;) {
1153          isenc = 0;
1154          while (PCMP(--cp, >=, *linebuf) && su_cs_is_blank(*cp))
1155             ;
1156          cp++;
1157          if (rem <= 0)
1158             break;
1159          if (PCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
1160             isenc |= 1;
1161          ungetc(c = getc(f), f);
1162          if (!su_cs_is_blank(c))
1163             break;
1164          c = readline_restart(f, &line2, &line2size, 0);
1165          if (c < 0)
1166             break;
1167          --rem;
1168          for (cp2 = line2; su_cs_is_blank(*cp2); ++cp2)
1169             ;
1170          c -= (int)P2UZ(cp2 - line2);
1171          if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
1172             isenc |= 2;
1173          if (PCMP(cp + c, >=, *linebuf + *linesize - 2)) {
1174             uz diff = P2UZ(cp - *linebuf),
1175                colondiff = P2UZ(*colon - *linebuf);
1176             *linebuf = n_realloc(*linebuf, *linesize += c + 2);
1177             cp = &(*linebuf)[diff];
1178             *colon = &(*linebuf)[colondiff];
1179          }
1180          if (isenc != 3)
1181             *cp++ = ' ';
1182          su_mem_copy(cp, cp2, c);
1183          cp += c;
1184       }
1185       *cp = '\0';
1186       break;
1187    }
1188 
1189    mx_fs_linepool_release(line2, line2size);
1190 
1191    NYD2_OU;
1192    return rem;
1193 }
1194 
1195 static int
msgidnextc(char const ** cp,int * status)1196 msgidnextc(char const **cp, int *status)
1197 {
1198    int c;
1199    NYD2_IN;
1200 
1201    ASSERT(cp != NULL);
1202    ASSERT(*cp != NULL);
1203    ASSERT(status != NULL);
1204 
1205    for (;;) {
1206       if (*status & 01) {
1207          if (**cp == '"') {
1208             *status &= ~01;
1209             (*cp)++;
1210             continue;
1211          }
1212          if (**cp == '\\') {
1213             (*cp)++;
1214             if (**cp == '\0')
1215                goto jeof;
1216          }
1217          goto jdfl;
1218       }
1219       switch (**cp) {
1220       case '(':
1221          *cp = skip_comment(&(*cp)[1]);
1222          continue;
1223       case '>':
1224       case '\0':
1225 jeof:
1226          c = '\0';
1227          goto jleave;
1228       case '"':
1229          (*cp)++;
1230          *status |= 01;
1231          continue;
1232       case '@':
1233          *status |= 02;
1234          /*FALLTHRU*/
1235       default:
1236 jdfl:
1237          c = *(*cp)++ & 0377;
1238          c = (*status & 02) ? su_cs_to_lower(c) : c;
1239          goto jleave;
1240       }
1241    }
1242 jleave:
1243    NYD2_OU;
1244    return c;
1245 }
1246 
1247 static char const *
nexttoken(char const * cp)1248 nexttoken(char const *cp)
1249 {
1250    NYD2_IN;
1251    for (;;) {
1252       if (*cp == '\0') {
1253          cp = NULL;
1254          break;
1255       }
1256 
1257       if (*cp == '(') {
1258          uz nesting = 1;
1259 
1260          do switch (*++cp) {
1261          case '(':
1262             ++nesting;
1263             break;
1264          case ')':
1265             --nesting;
1266             break;
1267          } while (nesting > 0 && *cp != '\0'); /* XXX error? */
1268       } else if (su_cs_is_blank(*cp) || *cp == ',')
1269          ++cp;
1270       else
1271          break;
1272    }
1273    NYD2_OU;
1274    return cp;
1275 }
1276 
1277 FL char const *
myaddrs(struct header * hp)1278 myaddrs(struct header *hp) /* TODO vanish! */
1279 {
1280    /* TODO myaddrs()+myorigin() should vanish, these should simply be *from*
1281     * TODO and *sender*, respectively.  So either it is *from* or *sender*,
1282     * TODO or what we parsed as From:/Sender: from a template.  This latter
1283     * TODO should set *from* / *sender* in a scope, we should use *sender*:
1284     * TODO *sender* should be set to the real *from*! */
1285    boole issnd;
1286    struct mx_name *np;
1287    char const *rv, *mta;
1288    NYD_IN;
1289 
1290    if(hp != NULL && (np = hp->h_from) != NIL){
1291       if((rv = np->n_fullname) != NIL)
1292          goto jleave;
1293       if((rv = np->n_name) != NIL)
1294          goto jleave;
1295    }
1296 
1297    /* Verified once variable had been set */
1298    if((rv = ok_vlook(from)) != NIL)
1299       goto jleave;
1300 
1301    /* When invoking *sendmail* directly, it's its task to generate an otherwise
1302     * undeterminable From: address.  However, if the user sets *hostname*,
1303     * accept his desire */
1304    if(ok_vlook(hostname) != NIL)
1305       goto jnodename;
1306    if(ok_vlook(smtp) != NIL || /* TODO obsolete -> mta */
1307          /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1308          ((mta = mx_url_servbyname(ok_vlook(mta), NIL, &issnd)) != NIL &&
1309          *mta != '\0' && issnd))
1310       goto jnodename;
1311 jleave:
1312    NYD_OU;
1313    return rv;
1314 
1315 jnodename:{
1316       char *cp;
1317       char const *hn, *ln;
1318       uz i;
1319 
1320       hn = n_nodename(TRU1);
1321       ln = ok_vlook(LOGNAME); /* TODO I'd like to have USER@HOST --> from */
1322       i = su_cs_len(ln) + su_cs_len(hn) + 1 +1;
1323       rv = cp = n_autorec_alloc(i);
1324       su_cs_pcopy(su_cs_pcopy(su_cs_pcopy(cp, ln), n_at), hn);
1325    }
1326    goto jleave;
1327 }
1328 
1329 FL char const *
myorigin(struct header * hp)1330 myorigin(struct header *hp) /* TODO vanish! see myaddrs() */
1331 {
1332    char const *rv = NULL, *ccp;
1333    struct mx_name *np;
1334    NYD_IN;
1335 
1336    if((ccp = myaddrs(hp)) != NULL &&
1337          (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1338       if(np->n_flink == NULL)
1339          rv = ccp;
1340       /* Verified upon variable set time */
1341       else if((ccp = ok_vlook(sender)) != NULL)
1342          rv = ccp;
1343       /* TODO why not else rv = n_poption_arg_r; ?? */
1344    }
1345    NYD_OU;
1346    return rv;
1347 }
1348 
1349 FL boole
is_head(char const * linebuf,uz linelen,boole check_rfc4155)1350 is_head(char const *linebuf, uz linelen, boole check_rfc4155)
1351 {
1352    char date[n_FROM_DATEBUF];
1353    boole rv;
1354    NYD2_IN;
1355 
1356    if ((rv = (linelen >= 5 && !su_mem_cmp(linebuf, "From ", 5))) &&
1357          check_rfc4155 &&
1358          (a_header_extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1359           !a_header_is_date(date)))
1360       rv = TRUM1;
1361    NYD2_OU;
1362    return rv;
1363 }
1364 
1365 FL char const *
mx_header_is_valid(char const * name,boole lead_ws,struct str * cramp_or_nil)1366 mx_header_is_valid(char const *name, boole lead_ws, struct str *cramp_or_nil){
1367    char const *cp;
1368    NYD_IN;
1369 
1370    cp = name;
1371 
1372    if(lead_ws){
1373       while(su_cs_is_blank(*cp))
1374          ++cp;
1375       name = cp;
1376    }
1377 
1378    if(cramp_or_nil != NIL)
1379       cramp_or_nil->s = UNCONST(char*,name);
1380 
1381    while(fieldnamechar(*cp))
1382       ++cp;
1383    if(cp == name){
1384       name = NIL;
1385       goto jleave;
1386    }
1387 
1388    if(cramp_or_nil != NIL)
1389       cramp_or_nil->l = P2UZ(cp - name);
1390 
1391    while(su_cs_is_blank(*cp))
1392       ++cp;
1393    if(*cp != ':'){
1394       name = NIL;
1395       goto jleave;
1396    }
1397 
1398    while(su_cs_is_blank(*++cp))
1399       ;
1400    name = cp;
1401 
1402 jleave:
1403    NYD_OU;
1404    return name;
1405 }
1406 
1407 FL boole
n_header_put4compose(FILE * fp,struct header * hp)1408 n_header_put4compose(FILE *fp, struct header *hp){
1409    boole rv;
1410    int t;
1411    NYD_IN;
1412 
1413    t = GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GREF_IRT | GNL | GCOMMA;
1414    if((hp->h_from != NULL || myaddrs(hp) != NULL) ||
1415          (hp->h_sender != NULL || ok_vlook(sender) != NULL) ||
1416          (hp->h_reply_to != NULL || ok_vlook(reply_to) != NULL) ||
1417             ok_vlook(replyto) != NULL /* v15compat, OBSOLETE */ ||
1418          hp->h_list_post != NULL || (hp->h_flags & HF_LIST_REPLY))
1419       t |= GIDENT;
1420 
1421    rv = n_puthead(TRUM1, hp, fp, t, SEND_TODISP, CONV_NONE, NULL, NULL);
1422    NYD_OU;
1423    return rv;
1424 }
1425 
1426 FL void
n_header_extract(enum n_header_extract_flags hef,FILE * fp,struct header * hp,s8 * checkaddr_err_or_null)1427 n_header_extract(enum n_header_extract_flags hef, FILE *fp, struct header *hp,
1428    s8 *checkaddr_err_or_null)
1429 {
1430    struct str suffix;
1431    struct n_header_field **hftail;
1432    struct header nh, *hq = &nh;
1433    char *linebuf, *colon;
1434    uz linesize, seenfields = 0;
1435    int c;
1436    long lc;
1437    off_t firstoff;
1438    char const *val, *cp;
1439    NYD_IN;
1440 
1441    mx_fs_linepool_aquire(&linebuf, &linesize);
1442    if(linebuf != NIL)
1443       linebuf[0] = '\0';
1444 
1445    su_mem_set(hq, 0, sizeof *hq);
1446    if(hef & n_HEADER_EXTRACT_PREFILL_RECEIVERS){
1447       hq->h_to = hp->h_to;
1448       hq->h_cc = hp->h_cc;
1449       hq->h_bcc = hp->h_bcc;
1450    }
1451    hftail = &hq->h_user_headers;
1452 
1453    if((firstoff = ftell(fp)) == -1)
1454       goto jeseek;
1455    for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1456       ;
1457    c = fseek(fp, firstoff, SEEK_SET);
1458    clearerr(fp);
1459    if(c != 0){
1460 jeseek:
1461       if(checkaddr_err_or_null != NULL)
1462          *checkaddr_err_or_null = -1;
1463       n_err("I/O error while parsing headers, operation aborted\n");
1464       goto jleave;
1465    }
1466 
1467    /* TODO yippieia, cat(check(lextract)) :-) */
1468    while ((lc = a_gethfield(hef, fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1469       struct mx_name *np;
1470 
1471       /* We explicitly allow EAF_NAME for some addressees since aliases are not
1472        * yet expanded when we parse these! */
1473       if ((val = n_header_get_field(linebuf, "to", &suffix)) != NULL) {
1474          ++seenfields;
1475          if(suffix.s != su_NIL && suffix.l > 0 &&
1476                !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
1477             goto jebadhead;
1478          hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL |
1479                (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
1480                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1481       } else if ((val = n_header_get_field(linebuf, "cc", &suffix)) != NULL) {
1482          ++seenfields;
1483          if(suffix.s != su_NIL && suffix.l > 0 &&
1484                !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
1485             goto jebadhead;
1486          hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL |
1487                (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
1488                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1489       } else if ((val = n_header_get_field(linebuf, "bcc", &suffix)) != NULL) {
1490          ++seenfields;
1491          if(suffix.s != su_NIL && suffix.l > 0 &&
1492                !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
1493             goto jebadhead;
1494          hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL |
1495                (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
1496                EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1497       } else if ((val = n_header_get_field(linebuf, "fcc", NULL)) != NULL) {
1498          if(hef & n_HEADER_EXTRACT__MODE_MASK){
1499             ++seenfields;
1500             hq->h_fcc = cat(hq->h_fcc, nalloc_fcc(val));
1501          }else
1502             goto jebadhead;
1503       } else if ((val = n_header_get_field(linebuf, "from", NULL)) != NULL) {
1504          if(hef & n_HEADER_EXTRACT_FULL){
1505             ++seenfields;
1506             hq->h_from = cat(hq->h_from,
1507                   checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1508                      EACM_STRICT, NULL));
1509          }
1510       }else if((val = n_header_get_field(linebuf, "reply-to", NULL)) != NULL){
1511          ++seenfields;
1512          hq->h_reply_to = cat(hq->h_reply_to,
1513                checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1514       }else if((val = n_header_get_field(linebuf, "sender", NULL)) != NULL){
1515          if(hef & n_HEADER_EXTRACT_FULL){
1516             ++seenfields;
1517             hq->h_sender = checkaddrs(n_extract_single(val,
1518                   GEXTRA | GFULL | GFULLEXTRA), EACM_STRICT, NULL);
1519          } else
1520             goto jebadhead;
1521       }else if((val = n_header_get_field(linebuf, "subject", NULL)) != NULL){
1522          ++seenfields;
1523          for (cp = val; su_cs_is_blank(*cp); ++cp)
1524             ;
1525          hq->h_subject = (hq->h_subject != NULL)
1526                ? savecatsep(hq->h_subject, ' ', cp) : savestr(cp);
1527       }
1528       /* The remaining are mostly hacked in and thus TODO -- at least in
1529        * TODO respect to their content checking */
1530       else if((val = n_header_get_field(linebuf, "message-id", NULL)) != NULL){
1531          if(hef & n_HEADER_EXTRACT__MODE_MASK){
1532             np = checkaddrs(lextract(val, GREF | GNOT_A_LIST),
1533                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1534                   NULL);
1535             if (np == NULL || np->n_flink != NULL)
1536                goto jebadhead;
1537             ++seenfields;
1538             hq->h_message_id = np;
1539          }else
1540             goto jebadhead;
1541       }else if((val = n_header_get_field(linebuf, "in-reply-to", NULL)
1542             ) != NULL){
1543          if(hef & n_HEADER_EXTRACT__MODE_MASK){
1544             np = checkaddrs(lextract(val, GREF),
1545                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1546                   NULL);
1547             ++seenfields;
1548             hq->h_in_reply_to = np;
1549          }else
1550             goto jebadhead;
1551       }else if((val = n_header_get_field(linebuf, "references", NULL)
1552             ) != NULL){
1553          if(hef & n_HEADER_EXTRACT__MODE_MASK){
1554             ++seenfields;
1555             /* TODO Limit number of references TODO better on parser side */
1556             hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1557                   /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1558                   NULL));
1559          }else
1560             goto jebadhead;
1561       }
1562       /* and that is very hairy */
1563       else if((val = n_header_get_field(linebuf, "mail-followup-to", NULL)
1564                ) != NULL){
1565          if(hef & n_HEADER_EXTRACT__MODE_MASK){
1566             ++seenfields;
1567             hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val,
1568                   GEXTRA | GFULL),
1569                   /*EACM_STRICT | TODO '/' valid! | EACM_NOLOG | */EACM_NONAME,
1570                   checkaddr_err_or_null));
1571          }else
1572             goto jebadhead;
1573       }
1574       /* A free-form header; a_gethfield() did some verification already.. */
1575       else{
1576          struct str hfield;
1577          struct n_header_field *hfp;
1578          uz bl;
1579 
1580          if((cp = mx_header_is_valid(linebuf, FAL0, &hfield)) == NIL){
1581 jebadhead:
1582             n_err(_("Ignoring header field: %s\n"), linebuf);
1583             continue;
1584          }
1585          bl = su_cs_len(cp) +1;
1586 
1587          ++seenfields;
1588          *hftail =
1589          hfp = n_autorec_alloc(VSTRUCT_SIZEOF(struct n_header_field,
1590                hf_dat) + hfield.l +1 + bl);
1591             hftail = &hfp->hf_next;
1592          hfp->hf_next = NIL;
1593          hfp->hf_nl = S(u32,hfield.l);
1594          hfp->hf_bl = S(u32,bl - 1);
1595          su_mem_copy(hfp->hf_dat, hfield.s, hfield.l);
1596             hfp->hf_dat[hfield.l++] = '\0';
1597             su_mem_copy(&hfp->hf_dat[hfield.l], cp, bl);
1598       }
1599    }
1600 
1601    /* In case the blank line after the header has been edited out.  Otherwise,
1602     * fetch the header separator */
1603    if (linebuf != NULL) {
1604       if (linebuf[0] != '\0') {
1605          for (cp = linebuf; *(++cp) != '\0';)
1606             ;
1607          fseek(fp, (long)-P2UZ(1 + cp - linebuf), SEEK_CUR);
1608       } else {
1609          if ((c = getc(fp)) != '\n' && c != EOF)
1610             ungetc(c, fp);
1611       }
1612    }
1613 
1614    if (seenfields > 0 &&
1615          (checkaddr_err_or_null == NULL || *checkaddr_err_or_null == 0)) {
1616       hp->h_to = hq->h_to;
1617       hp->h_cc = hq->h_cc;
1618       hp->h_bcc = hq->h_bcc;
1619       hp->h_from = hq->h_from;
1620       hp->h_reply_to = hq->h_reply_to;
1621       hp->h_sender = hq->h_sender;
1622       if(hq->h_subject != NULL ||
1623             (hef & n_HEADER_EXTRACT__MODE_MASK) != n_HEADER_EXTRACT_FULL)
1624          hp->h_subject = hq->h_subject;
1625       hp->h_user_headers = hq->h_user_headers;
1626 
1627       if(hef & n_HEADER_EXTRACT__MODE_MASK){
1628          hp->h_fcc = hq->h_fcc;
1629          if(hef & n_HEADER_EXTRACT_FULL)
1630             hp->h_ref = hq->h_ref;
1631          hp->h_message_id = hq->h_message_id;
1632          hp->h_in_reply_to = hq->h_in_reply_to;
1633          /* TODO For now the user cannot force "throwing away of M-F-T:" by
1634           * TODO simply deleting the header; it is possible to adjust the
1635           * TODO content, but is still mangled further on: very bad */
1636          if(hq->h_mft != NULL)
1637             hp->h_mft = hq->h_mft;
1638 
1639          /* And perform additional validity checks so that we don't bail later
1640           * on TODO this is good and the place where this should occur,
1641           * TODO unfortunately a lot of other places do again and blabla */
1642          if(hp->h_from == NULL)
1643             hp->h_from = n_poption_arg_r;
1644          else if((hef & n_HEADER_EXTRACT_FULL) &&
1645                hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1646             hp->h_sender = n_extract_single(ok_vlook(sender),
1647                   GEXTRA | GFULL | GFULLEXTRA);
1648       }
1649    } else
1650       n_err(_("Restoring deleted header lines\n"));
1651 
1652 jleave:
1653    mx_fs_linepool_release(linebuf, linesize);
1654    NYD_OU;
1655 }
1656 
1657 FL char *
hfield_mult(char const * field,struct message * mp,int mult)1658 hfield_mult(char const *field, struct message *mp, int mult)
1659 {
1660    FILE *ibuf;
1661    struct str hfs;
1662    long lc;
1663    uz linesize;
1664    char *linebuf, *colon;
1665    char const *hfield;
1666    NYD_IN;
1667 
1668    mx_fs_linepool_aquire(&linebuf, &linesize);
1669 
1670    /* There are (spam) messages which have header bytes which are many KB when
1671     * joined, so resize a single heap storage until we are done if we shall
1672     * collect a field that may have multiple bodies; only otherwise use the
1673     * string dope directly */
1674    su_mem_set(&hfs, 0, sizeof hfs);
1675 
1676    if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1677       goto jleave;
1678    if ((lc = mp->m_lines - 1) < 0)
1679       goto jleave;
1680 
1681    if ((mp->m_flag & MNOFROM) == 0 &&
1682          readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1683       goto jleave;
1684    while (lc > 0) {
1685       if ((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, &linebuf, &linesize,
1686             lc, &colon)) < 0)
1687          break;
1688       if ((hfield = n_header_get_field(linebuf, field, NULL)) != NULL &&
1689             *hfield != '\0') {
1690          if (mult)
1691             n_str_add_buf(&hfs, hfield, su_cs_len(hfield));
1692          else {
1693             hfs.s = savestr(hfield);
1694             break;
1695          }
1696       }
1697    }
1698 
1699 jleave:
1700    mx_fs_linepool_release(linebuf, linesize);
1701    if (mult && hfs.s != NULL) {
1702       colon = savestrbuf(hfs.s, hfs.l);
1703       n_free(hfs.s);
1704       hfs.s = colon;
1705    }
1706    NYD_OU;
1707    return hfs.s;
1708 }
1709 
1710 FL char const *
n_header_get_field(char const * linebuf,char const * field,struct str * suffix_or_nil)1711 n_header_get_field(char const *linebuf, char const *field,
1712    struct str *suffix_or_nil)
1713 {
1714    char const *rv = NULL;
1715    NYD2_IN;
1716 
1717    if(suffix_or_nil != su_NIL)
1718       suffix_or_nil->s = su_NIL;
1719 
1720    while (su_cs_to_lower(*linebuf) == su_cs_to_lower(*field)) {
1721       ++linebuf;
1722       ++field;
1723    }
1724 
1725    if(*field != '\0')
1726       goto jleave;
1727 
1728    if(suffix_or_nil != su_NIL && *linebuf == '?'){
1729       char c;
1730       uz i;
1731 
1732       for(i = 0; (c = *linebuf) != '\0'; ++linebuf, ++i)
1733          if(su_cs_is_blank(c) || c == ':')
1734             break;
1735       if(i > 0){
1736          suffix_or_nil->l = --i;
1737          suffix_or_nil->s = UNCONST(char*,&linebuf[-i]);
1738       }
1739    }
1740 
1741    while(su_cs_is_blank(*linebuf))
1742       ++linebuf;
1743    if(*linebuf++ != ':')
1744       goto jleave;
1745    while(su_cs_is_blank(*linebuf)) /* TODO header parser.. strip trailing WS */
1746       ++linebuf;
1747 
1748    rv = linebuf;
1749 jleave:
1750    NYD2_OU;
1751    return rv;
1752 }
1753 
1754 FL char const *
skip_comment(char const * cp)1755 skip_comment(char const *cp)
1756 {
1757    uz nesting;
1758    NYD_IN;
1759 
1760    for (nesting = 1; nesting > 0 && *cp; ++cp) {
1761       switch (*cp) {
1762       case '\\':
1763          if (cp[1])
1764             ++cp;
1765          break;
1766       case '(':
1767          ++nesting;
1768          break;
1769       case ')':
1770          --nesting;
1771          break;
1772       }
1773    }
1774    NYD_OU;
1775    return cp;
1776 }
1777 
1778 FL char const *
routeaddr(char const * name)1779 routeaddr(char const *name)
1780 {
1781    char const *np, *rp = NULL;
1782    NYD_IN;
1783 
1784    for (np = name; *np; np++) {
1785       switch (*np) {
1786       case '(':
1787          np = skip_comment(np + 1) - 1;
1788          break;
1789       case '"':
1790          while (*np) {
1791             if (*++np == '"')
1792                break;
1793             if (*np == '\\' && np[1])
1794                np++;
1795          }
1796          break;
1797       case '<':
1798          rp = np;
1799          break;
1800       case '>':
1801          goto jleave;
1802       }
1803    }
1804    rp = NULL;
1805 jleave:
1806    NYD_OU;
1807    return rp;
1808 }
1809 
1810 FL enum expand_addr_flags
expandaddr_to_eaf(void)1811 expandaddr_to_eaf(void){ /* TODO should happen at var assignment time */
1812    struct eafdesc{
1813       char eafd_name[15];
1814       boole eafd_is_target;
1815       u32 eafd_andoff;
1816       u32 eafd_or;
1817    } const eafa[] = {
1818       {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1819       {"fail", FAL0, EAF_NONE, EAF_FAIL},
1820       {"failinvaddr\0", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1821       {"domaincheck\0", FAL0, EAF_NONE, EAF_DOMAINCHECK | EAF_ADDR},
1822       {"nametoaddr\0", FAL0, EAF_NONE, EAF_NAMETOADDR},
1823       {"shquote", FAL0, EAF_NONE, EAF_SHEXP_PARSE},
1824       {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1825          {"fcc", TRU1, EAF_NONE, EAF_FCC}, /* Fcc: only */
1826          {"file", TRU1, EAF_NONE, EAF_FILE | EAF_FCC}, /* Fcc: + other addr */
1827          {"pipe", TRU1, EAF_NONE, EAF_PIPE}, /* TODO No Pcc: yet! */
1828          {"name", TRU1, EAF_NONE, EAF_NAME},
1829          {"addr", TRU1, EAF_NONE, EAF_ADDR}
1830    }, *eafp;
1831 
1832    char *buf;
1833    enum expand_addr_flags rv;
1834    char const *cp;
1835    NYD2_IN;
1836 
1837    if((cp = ok_vlook(expandaddr)) == NULL)
1838       rv = EAF_RESTRICT_TARGETS;
1839    else if(*cp == '\0')
1840       rv = EAF_TARGET_MASK;
1841    else{
1842       rv = EAF_TARGET_MASK;
1843 
1844       for(buf = savestr(cp); (cp = su_cs_sep_c(&buf, ',', TRU1)) != NULL;){
1845          boole minus;
1846 
1847          if((minus = (*cp == '-')) || (*cp == '+' ? (minus = TRUM1) : FAL0))
1848             ++cp;
1849 
1850          for(eafp = eafa;; ++eafp) {
1851             if(eafp == &eafa[NELEM(eafa)]){
1852                if(n_poption & n_PO_D_V)
1853                   n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1854                break;
1855             }else if(!su_cs_cmp_case(cp, eafp->eafd_name)){
1856                if(minus){
1857                   if(eafp->eafd_is_target){
1858                      if(minus != TRU1)
1859                         goto jandor;
1860                      else
1861                         rv &= ~eafp->eafd_or;
1862                   }else if(n_poption & n_PO_D_V)
1863                      n_err(_("- or + prefix invalid for *expandaddr* value: "
1864                         "%s\n"), --cp);
1865                }else{
1866 jandor:
1867                   rv &= ~eafp->eafd_andoff;
1868                   rv |= eafp->eafd_or;
1869                }
1870                break;
1871             }else if(!su_cs_cmp_case(cp, "noalias")){ /* TODO v15 OBSOLETE */
1872                n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1873                rv &= ~EAF_NAME;
1874                break;
1875             }else if(!su_cs_cmp_case(cp, "namehostex")){ /* TODO v15 OBSOLETE*/
1876                n_OBSOLETE(_("*expandaddr*: "
1877                   "weird namehostex renamed to nametoaddr, "
1878                   "sorry for the inconvenience!"));
1879                rv |= EAF_NAMETOADDR;
1880                break;
1881             }
1882          }
1883       }
1884 
1885       if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1886             (n_poption & n_PO_TILDE_FLAG)))
1887          rv |= EAF_TARGET_MASK;
1888       else if(n_poption & n_PO_D_V){
1889          if(!(rv & EAF_TARGET_MASK))
1890             n_err(_("*expandaddr* does not allow any addressees\n"));
1891          else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1892             n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1893       }
1894    }
1895    NYD2_OU;
1896    return rv;
1897 }
1898 
1899 FL s8
is_addr_invalid(struct mx_name * np,enum expand_addr_check_mode eacm)1900 is_addr_invalid(struct mx_name *np, enum expand_addr_check_mode eacm){
1901    /* TODO This is called much too often!  Message->DOMTree->modify->..
1902     * TODO I.e., [verify once before compose-mode], verify once after
1903     * TODO compose-mode, store result in object */
1904    char cbuf[sizeof "'\\U12340'"];
1905    char const *cs;
1906    int f;
1907    s8 rv;
1908    enum expand_addr_flags eaf;
1909    NYD_IN;
1910 
1911    eaf = expandaddr_to_eaf();
1912    f = np->n_flags;
1913 
1914    if((rv = ((f & mx_NAME_ADDRSPEC_INVALID) != 0))){
1915       if(eaf & EAF_FAILINVADDR)
1916          rv = -rv;
1917 
1918       if(!(eacm & EACM_NOLOG) && !(f & mx_NAME_ADDRSPEC_ERR_EMPTY)){
1919          u32 c;
1920          boole ok8bit;
1921          char const *fmt;
1922 
1923          fmt = "'\\x%02X'";
1924          ok8bit = TRU1;
1925 
1926          if(f & mx_NAME_ADDRSPEC_ERR_IDNA) {
1927             cs = _("Invalid domain name: %s, character %s\n");
1928             fmt = "'\\U%04X'";
1929             ok8bit = FAL0;
1930          }else if(f & mx_NAME_ADDRSPEC_ERR_ATSEQ)
1931             cs = _("%s contains invalid %s sequence\n");
1932          else if(f & mx_NAME_ADDRSPEC_ERR_NAME)
1933             cs = _("%s is an invalid alias name\n");
1934          else
1935             cs = _("%s contains invalid byte %s\n");
1936 
1937          c = mx_name_flags_get_err_wc(f);
1938          if(ok8bit && c >= 040 && c <= 0177)
1939             snprintf(cbuf, sizeof cbuf, "'%c'", S(char,c));
1940          else
1941             snprintf(cbuf, sizeof cbuf, fmt, c);
1942          goto jprint;
1943       }
1944       goto jleave;
1945    }
1946 
1947    /* *expandaddr* stuff */
1948    if(!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1949       goto jleave;
1950 
1951    /* This header does not allow such targets at all (XXX >RFC 5322 parser) */
1952    if((eacm & EACM_STRICT) && (f & mx_NAME_ADDRSPEC_ISFILEORPIPE)){
1953       if(eaf & EAF_FAIL)
1954          rv = -rv;
1955       cs = _("%s%s: file or pipe addressees not allowed here\n");
1956       goto j0print;
1957    }
1958 
1959    eaf |= (eacm & EAF_TARGET_MASK);
1960    if(eacm & EACM_NONAME)
1961       eaf &= ~EAF_NAME;
1962    if(eaf & EAF_FAIL)
1963       rv = -rv;
1964 
1965    switch(f & mx_NAME_ADDRSPEC_ISMASK){
1966    case mx_NAME_ADDRSPEC_ISFILE:
1967       if((eaf & EAF_FILE) || ((eaf & EAF_FCC) && (np->n_type & GBCC_IS_FCC)))
1968          goto jgood;
1969       cs = _("%s%s: *expandaddr* does not allow file target\n");
1970       break;
1971    case mx_NAME_ADDRSPEC_ISPIPE:
1972       if(eaf & EAF_PIPE)
1973          goto jgood;
1974       cs = _("%s%s: *expandaddr* does not allow command pipe target\n");
1975       break;
1976    case mx_NAME_ADDRSPEC_ISNAME:
1977       if((eaf & EAF_NAMETOADDR) &&
1978             (!su_cs_cmp(np->n_name, ok_vlook(LOGNAME)) ||
1979                getpwnam(np->n_name) != NIL)){
1980          np->n_flags ^= mx_NAME_ADDRSPEC_ISADDR | mx_NAME_ADDRSPEC_ISNAME;
1981          np->n_name = np->n_fullname = savecatsep(np->n_name, '@',
1982                n_nodename(TRU1));
1983          goto jisaddr;
1984       }
1985 
1986       if(eaf & EAF_NAME)
1987          goto jgood;
1988       if(!(eaf & EAF_FAIL) && (eacm & EACM_NONAME_OR_FAIL)){
1989          rv = -rv;
1990          cs = _("%s%s: user name (MTA alias) targets are not allowed\n");
1991       }else
1992          cs = _("%s%s: *expandaddr* does not allow user name target\n");
1993       break;
1994    default:
1995    case mx_NAME_ADDRSPEC_ISADDR:
1996 jisaddr:
1997       if(!(eaf & EAF_ADDR)){
1998          cs = _("%s%s: *expandaddr* does not allow mail address target\n");
1999          break;
2000       }
2001       if(!(eacm & EACM_DOMAINCHECK) || !(eaf & EAF_DOMAINCHECK))
2002          goto jgood;
2003       else{
2004          char const *doms;
2005 
2006          ASSERT(np->n_flags & mx_NAME_SKINNED);
2007          /* XXX We had this info before, and threw it away.. */
2008          doms = su_cs_rfind_c(np->n_name, '@');
2009          ASSERT(doms != NULL);
2010          ++doms;
2011 
2012          if(!su_cs_cmp_case("localhost", doms))
2013             goto jgood;
2014          if(!su_cs_cmp_case(n_nodename(TRU1), doms))
2015             goto jgood;
2016 
2017          if((cs = ok_vlook(expandaddr_domaincheck)) != NULL){
2018             char *cpb, *cp;
2019 
2020             cpb = savestr(cs);
2021             while((cp = su_cs_sep_c(&cpb, ',', TRU1)) != NULL)
2022                if(!su_cs_cmp_case(cp, doms))
2023                   goto jgood;
2024          }
2025       }
2026       cs = _("%s%s: *expandaddr*: not \"domaincheck\" whitelisted\n");
2027       break;
2028    }
2029 
2030 j0print:
2031    cbuf[0] = '\0';
2032    if(!(eacm & EACM_NOLOG))
2033 jprint:
2034       n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
2035    goto jleave;
2036 jgood:
2037    rv = FAL0;
2038 jleave:
2039    NYD_OU;
2040    return rv;
2041 }
2042 
2043 FL char *
skin(char const * name)2044 skin(char const *name)
2045 {
2046    struct n_addrguts ag;
2047    char *rv;
2048    NYD_IN;
2049 
2050    if(name != NULL){
2051       /*name =*/ n_addrspec_with_guts(&ag, name, GSKIN);
2052       rv = ag.ag_skinned;
2053       if(!(ag.ag_n_flags & mx_NAME_NAME_SALLOC))
2054          rv = savestrbuf(rv, ag.ag_slen);
2055    }else
2056       rv = NULL;
2057    NYD_OU;
2058    return rv;
2059 }
2060 
2061 /* TODO addrspec_with_guts: RFC 5322
2062  * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC. BAD BAD BAD!!! */
2063 FL char const *
n_addrspec_with_guts(struct n_addrguts * agp,char const * name,u32 gfield)2064 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, u32 gfield){
2065    char const *cp;
2066    char *cp2, *bufend, *nbuf, c;
2067    enum{
2068       a_NONE,
2069       a_DOSKIN = 1u<<0,
2070       a_NOLIST = 1u<<1,
2071       a_QUENCO = 1u<<2,
2072 
2073       a_GOTLT = 1u<<3,
2074       a_GOTADDR = 1u<<4,
2075       a_GOTSPACE = 1u<<5,
2076       a_LASTSP = 1u<<6,
2077       a_IDX0 = 1u<<7
2078    } flags;
2079    NYD_IN;
2080 
2081 jredo_uri:
2082    su_mem_set(agp, 0, sizeof *agp);
2083 
2084    if((agp->ag_input = name) == NULL || (agp->ag_ilen = su_cs_len(name)) == 0){
2085       agp->ag_skinned = n_UNCONST(n_empty); /* ok: mx_NAME_SALLOC is not set */
2086       agp->ag_slen = 0;
2087       agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
2088             mx_NAME_ADDRSPEC_ERR_EMPTY, '\0');
2089       goto jleave;
2090    }
2091 
2092    flags = a_NONE;
2093    if(gfield & (GFULL | GSKIN | GREF))
2094       flags |= a_DOSKIN;
2095    if(gfield & GNOT_A_LIST)
2096       flags |= a_NOLIST;
2097    if(gfield & GQUOTE_ENCLOSED_OK){
2098       gfield ^= GQUOTE_ENCLOSED_OK;
2099       flags |= a_QUENCO;
2100    }
2101 
2102    if(!(flags & a_DOSKIN)){
2103       /*agp->ag_iaddr_start = 0;*/
2104       agp->ag_iaddr_aend = agp->ag_ilen;
2105       agp->ag_skinned = n_UNCONST(name); /* (mx_NAME_SALLOC not set) */
2106       agp->ag_slen = agp->ag_ilen;
2107       agp->ag_n_flags = mx_NAME_SKINNED;
2108       goto jcheck;
2109    }
2110 
2111    /* We will skin that thing */
2112    nbuf = n_lofi_alloc(agp->ag_ilen +1);
2113    /*agp->ag_iaddr_start = 0;*/
2114    cp2 = bufend = nbuf;
2115 
2116    /* TODO This is complete crap and should use a token parser.
2117     * TODO It can be fooled and is too stupid to find an email address in
2118     * TODO something valid unless it contains <>.  oh my */
2119    for(cp = name++; (c = *cp++) != '\0';){
2120       switch (c) {
2121       case '(':
2122          cp = skip_comment(cp);
2123          flags &= ~a_LASTSP;
2124          break;
2125       case '"':
2126          /* Start of a "quoted-string".  Copy it in its entirety */
2127          /* XXX RFC: quotes are "semantically invisible"
2128           * XXX But it was explicitly added (Changelog.Heirloom,
2129           * XXX [9.23] released 11/15/00, "Do not remove quotes
2130           * XXX when skinning names"?  No more info.. */
2131          *cp2++ = c;
2132          ASSERT(!(flags & a_IDX0));
2133          if((flags & a_QUENCO) && cp == name)
2134             flags |= a_IDX0;
2135          while ((c = *cp) != '\0') { /* TODO improve */
2136             ++cp;
2137             if (c == '"') {
2138                *cp2++ = c;
2139                /* Special case: if allowed so and anything is placed in quotes
2140                 * then throw away the quotes and start all over again */
2141                if((flags & a_IDX0) && *cp == '\0'){
2142                   name = savestrbuf(name, P2UZ(--cp - name));
2143                   goto jredo_uri;
2144                }
2145                break;
2146             }
2147             if (c != '\\')
2148                *cp2++ = c;
2149             else if ((c = *cp) != '\0') {
2150                *cp2++ = c;
2151                ++cp;
2152             }
2153          }
2154          flags &= ~(a_LASTSP | a_IDX0);
2155          break;
2156       case ' ':
2157       case '\t':
2158          if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
2159             flags |= a_GOTSPACE;
2160             agp->ag_iaddr_aend = P2UZ(cp - name);
2161          }
2162          if (cp[0] == 'a' && cp[1] == 't' && su_cs_is_blank(cp[2]))
2163             cp += 3, *cp2++ = '@';
2164          else if (cp[0] == '@' && su_cs_is_blank(cp[1]))
2165             cp += 2, *cp2++ = '@';
2166          else
2167             flags |= a_LASTSP;
2168          break;
2169       case '<':
2170          agp->ag_iaddr_start = P2UZ(cp - (name - 1));
2171          cp2 = bufend;
2172          flags &= ~(a_GOTSPACE | a_LASTSP);
2173          flags |= a_GOTLT | a_GOTADDR;
2174          break;
2175       case '>':
2176          if(flags & a_GOTLT){
2177             /* (_addrspec_check() verifies these later!) */
2178             flags &= ~(a_GOTLT | a_LASTSP);
2179             agp->ag_iaddr_aend = P2UZ(cp - name);
2180 
2181             /* Skip over the entire remaining field */
2182             while((c = *cp) != '\0'){
2183                if(c == ',' && !(flags & a_NOLIST))
2184                   break;
2185                ++cp;
2186                if (c == '(')
2187                   cp = skip_comment(cp);
2188                else if (c == '"')
2189                   while ((c = *cp) != '\0') {
2190                      ++cp;
2191                      if (c == '"')
2192                         break;
2193                      if (c == '\\' && *cp != '\0')
2194                         ++cp;
2195                   }
2196             }
2197             break;
2198          }
2199          /* FALLTHRU */
2200       default:
2201          if(flags & a_LASTSP){
2202             flags &= ~a_LASTSP;
2203             if(flags & a_GOTADDR)
2204                *cp2++ = ' ';
2205          }
2206          *cp2++ = c;
2207          /* This character is forbidden here, but it may nonetheless be
2208           * present: ensure we turn this into something valid!  (E.g., if the
2209           * next character would be a "..) */
2210          if(c == '\\' && *cp != '\0')
2211             *cp2++ = *cp++;
2212          if(c == ',' && !(flags & a_NOLIST)){
2213             if(!(flags & a_GOTLT)){
2214                *cp2++ = ' ';
2215                for(; su_cs_is_blank(*cp); ++cp)
2216                   ;
2217                flags &= ~a_LASTSP;
2218                bufend = cp2;
2219             }
2220          }else if(!(flags & a_GOTADDR)){
2221             flags |= a_GOTADDR;
2222             agp->ag_iaddr_start = P2UZ(cp - name);
2223          }
2224       }
2225    }
2226    agp->ag_slen = P2UZ(cp2 - nbuf);
2227 
2228    if (agp->ag_iaddr_aend == 0)
2229       agp->ag_iaddr_aend = agp->ag_ilen;
2230    /* Misses > */
2231    else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
2232       cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
2233       su_mem_copy(cp2, agp->ag_input, agp->ag_ilen);
2234       agp->ag_iaddr_aend = agp->ag_ilen;
2235       cp2[agp->ag_ilen++] = '>';
2236       cp2[agp->ag_ilen] = '\0';
2237       agp->ag_input = cp2;
2238    }
2239 
2240    agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
2241    n_lofi_free(nbuf);
2242    agp->ag_n_flags = mx_NAME_NAME_SALLOC | mx_NAME_SKINNED;
2243 jcheck:
2244    if(a_header_addrspec_check(agp, ((flags & a_DOSKIN) != 0),
2245          ((flags & GNOT_A_LIST) != 0)) <= FAL0)
2246       name = NULL;
2247    else
2248       name = agp->ag_input;
2249 jleave:
2250    NYD_OU;
2251    return name;
2252 }
2253 
2254 FL int
c_addrcodec(void * vp)2255 c_addrcodec(void *vp){
2256    struct str trims;
2257    struct n_string s_b, *s;
2258    uz alen;
2259    int mode;
2260    char const **argv, *varname, *act, *cp;
2261    NYD_IN;
2262 
2263    s = n_string_creat_auto(&s_b);
2264    argv = vp;
2265    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
2266 
2267    act = *argv;
2268    for(cp = act; *cp != '\0' && !su_cs_is_space(*cp); ++cp)
2269       ;
2270    mode = 0;
2271    if(*act == '+')
2272       mode = 1, ++act;
2273    if(*act == '+')
2274       mode = 2, ++act;
2275    if(*act == '+')
2276       mode = 3, ++act;
2277    if(act >= cp)
2278       goto jesynopsis;
2279    alen = P2UZ(cp - act);
2280    if(*cp != '\0')
2281       ++cp;
2282 
2283    trims.l = su_cs_len(trims.s = n_UNCONST(cp));
2284    cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
2285    if(trims.l <= UZ_MAX / 4)
2286          trims.l <<= 1;
2287    s = n_string_reserve(s, trims.l);
2288 
2289    n_pstate_err_no = su_ERR_NONE;
2290 
2291    if(su_cs_starts_with_case_n("encode", act, alen)){
2292       /* This function cannot be a simple nalloc() wrapper even later on, since
2293        * we may need to turn any ", () or \ into quoted-pairs */
2294       struct mx_name *np;
2295       char c;
2296 
2297       while((c = *cp++) != '\0'){
2298          if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
2299                (c == '\\' && mode < 3))
2300             s = n_string_push_c(s, '\\');
2301          s = n_string_push_c(s, c);
2302       }
2303 
2304       if((np = n_extract_single(cp = n_string_cp(s), GTO | GFULL)) != NULL)
2305          cp = np->n_fullname;
2306       else{
2307          n_pstate_err_no = su_ERR_INVAL;
2308          vp = NULL;
2309       }
2310    }else if(mode == 0){
2311       if(su_cs_starts_with_case_n("decode", act, alen)){
2312          char c;
2313 
2314          while((c = *cp++) != '\0'){
2315             switch(c){
2316             case '(':
2317                s = n_string_push_c(s, '(');
2318                act = skip_comment(cp);
2319                if(--act > cp)
2320                   s = n_string_push_buf(s, cp, P2UZ(act - cp));
2321                s = n_string_push_c(s, ')');
2322                cp = ++act;
2323                break;
2324             case '"':
2325                while(*cp != '\0'){
2326                   if((c = *cp++) == '"')
2327                      break;
2328                   if(c == '\\' && (c = *cp) != '\0')
2329                      ++cp;
2330                   s = n_string_push_c(s, c);
2331                }
2332                break;
2333             default:
2334                if(c == '\\' && (c = *cp++) == '\0')
2335                   break;
2336                s = n_string_push_c(s, c);
2337                break;
2338             }
2339          }
2340          cp = n_string_cp(s);
2341       }else if(su_cs_starts_with_case_n("skin", act, alen) ||
2342             (mode = 1, su_cs_starts_with_case_n("skinlist", act, alen))){
2343          struct mx_name *np;
2344 
2345          if((np = n_extract_single(cp, GTO | GFULL)) != NULL){
2346             s8 mltype;
2347 
2348             cp = np->n_name;
2349 
2350             if(mode == 1 &&
2351                   (mltype = mx_mlist_query(cp, FAL0)) != mx_MLIST_OTHER &&
2352                   mltype != mx_MLIST_POSSIBLY)
2353                n_pstate_err_no = su_ERR_EXIST;
2354          }else{
2355             n_pstate_err_no = su_ERR_INVAL;
2356             vp = NULL;
2357          }
2358       }else
2359          goto jesynopsis;
2360    }else
2361       goto jesynopsis;
2362 
2363    if(varname == NULL){
2364       if(fprintf(n_stdout, "%s\n", cp) < 0){
2365          n_pstate_err_no = su_err_no();
2366          vp = NULL;
2367       }
2368    }else if(!n_var_vset(varname, (up)cp)){
2369       n_pstate_err_no = su_ERR_NOTSUP;
2370       vp = NULL;
2371    }
2372 
2373 jleave:
2374    NYD_OU;
2375    return (vp != NULL ? 0 : 1);
2376 jesynopsis:
2377    mx_cmd_print_synopsis(mx_cmd_firstfit("addrcodec"), NIL);
2378    n_pstate_err_no = su_ERR_INVAL;
2379    vp = NULL;
2380    goto jleave;
2381 }
2382 
2383 FL char *
realname(char const * name)2384 realname(char const *name)
2385 {
2386    char const *cp, *cq, *cstart = NULL, *cend = NULL;
2387    char *rname, *rp;
2388    struct str in, out;
2389    int quoted, good, nogood;
2390    NYD_IN;
2391 
2392    if ((cp = n_UNCONST(name)) == NULL)
2393       goto jleave;
2394    for (; *cp != '\0'; ++cp) {
2395       switch (*cp) {
2396       case '(':
2397          if (cstart != NULL) {
2398             /* More than one comment in address, doesn't make sense to display
2399              * it without context.  Return the entire field */
2400             cp = mime_fromaddr(name);
2401             goto jleave;
2402          }
2403          cstart = cp++;
2404          cp = skip_comment(cp);
2405          cend = cp--;
2406          if (cend <= cstart)
2407             cend = cstart = NULL;
2408          break;
2409       case '"':
2410          while (*cp) {
2411             if (*++cp == '"')
2412                break;
2413             if (*cp == '\\' && cp[1])
2414                ++cp;
2415          }
2416          break;
2417       case '<':
2418          if (cp > name) {
2419             cstart = name;
2420             cend = cp;
2421          }
2422          break;
2423       case ',':
2424          /* More than one address. Just use the first one */
2425          goto jbrk;
2426       }
2427    }
2428 
2429 jbrk:
2430    if (cstart == NULL) {
2431       if (*name == '<') {
2432          /* If name contains only a route-addr, the surrounding angle brackets
2433           * don't serve any useful purpose when displaying, so remove */
2434          cp = prstr(skin(name));
2435       } else
2436          cp = mime_fromaddr(name);
2437       goto jleave;
2438    }
2439 
2440    /* Strip quotes. Note that quotes that appear within a MIME encoded word are
2441     * not stripped. The idea is to strip only syntactical relevant things (but
2442     * this is not necessarily the most sensible way in practice) */
2443    rp = rname = n_lofi_alloc(P2UZ(cend - cstart +1));
2444    quoted = 0;
2445    for (cp = cstart; cp < cend; ++cp) {
2446       if (*cp == '(' && !quoted) {
2447          cq = skip_comment(++cp);
2448          if (PCMP(--cq, >, cend))
2449             cq = cend;
2450          while (cp < cq) {
2451             if (*cp == '\\' && PCMP(cp + 1, <, cq))
2452                ++cp;
2453             *rp++ = *cp++;
2454          }
2455       } else if (*cp == '\\' && PCMP(cp + 1, <, cend))
2456          *rp++ = *++cp;
2457       else if (*cp == '"') {
2458          quoted = !quoted;
2459          continue;
2460       } else
2461          *rp++ = *cp;
2462    }
2463    *rp = '\0';
2464    in.s = rname;
2465    in.l = rp - rname;
2466    mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2467    n_lofi_free(rname);
2468    rname = savestr(out.s);
2469    n_free(out.s);
2470 
2471    while (su_cs_is_blank(*rname))
2472       ++rname;
2473    for (rp = rname; *rp != '\0'; ++rp)
2474       ;
2475    while (PCMP(--rp, >=, rname) && su_cs_is_blank(*rp))
2476       *rp = '\0';
2477    if (rp == rname) {
2478       cp = mime_fromaddr(name);
2479       goto jleave;
2480    }
2481 
2482    /* mime_fromhdr() has converted all nonprintable characters to question
2483     * marks now. These and blanks are considered uninteresting; if the
2484     * displayed part of the real name contains more than 25% of them, it is
2485     * probably better to display the plain email address instead */
2486    good = 0;
2487    nogood = 0;
2488    for (rp = rname; *rp != '\0' && PCMP(rp, <, rname + 20); ++rp)
2489       if (*rp == '?' || su_cs_is_blank(*rp))
2490          ++nogood;
2491       else
2492          ++good;
2493    cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2494 jleave:
2495    NYD_OU;
2496    return n_UNCONST(cp);
2497 }
2498 
2499 FL struct mx_name *
mx_header_list_post_of(struct message * mp)2500 mx_header_list_post_of(struct message *mp){
2501    char *cp;
2502    struct mx_name *rv;
2503    NYD_IN;
2504 
2505    rv = NIL;
2506 
2507    /* RFC 2369 says this is a potential list, in preference order */
2508    if((cp = hfield1("list-post", mp)) != NIL &&
2509          (rv = lextract(cp, GEXTRA)) != NIL){
2510       do{
2511          uz i;
2512 
2513          if(*(cp = rv->n_name) == '<' && cp[i = su_cs_len(cp) -1] == '>'){
2514             ++cp;
2515             cp[--i] = '\0';
2516             if((cp = mx_url_mailto_to_address(cp)) != NIL){
2517                rv = n_extract_single(cp, GEXTRA);
2518 
2519                if(rv != NIL && is_addr_invalid(rv, EACM_STRICT)){
2520                   if(n_poption & n_PO_D_V)
2521                      n_err(_("Invalid List-Post: header: %s\n"),
2522                         n_shexp_quote_cp(cp, FAL0));
2523                }
2524                break;
2525             }
2526          }else if(!su_cs_cmp_case(rv->n_name, "no")){
2527             rv = R(struct mx_name*,-1);
2528             break;
2529          }
2530       }while((rv = rv->n_flink) != NIL);
2531    }
2532 
2533    NYD_OU;
2534    return rv;
2535 }
2536 
2537 FL struct mx_name *
mx_header_sender_of(struct message * mp,u32 gf)2538 mx_header_sender_of(struct message *mp, u32 gf){
2539    struct mx_name *rv;
2540    char const *cp;
2541    NYD_IN;
2542 
2543    if(gf == 0)
2544       gf = GFULL | GSKIN;
2545 
2546    if((cp = hfield1("from", mp)) != NIL && *cp != '\0' &&
2547          (rv = lextract(cp, gf)) != NIL && rv->n_flink == NIL){
2548       ;
2549    }else if((cp = hfield1("sender", mp)) != NIL && *cp != '\0' &&
2550          (rv = lextract(cp, gf)) != NIL)
2551       ;
2552    else
2553       rv = NIL;
2554 
2555    NYD_OU;
2556    return rv;
2557 }
2558 
2559 FL char *
n_header_senderfield_of(struct message * mp)2560 n_header_senderfield_of(struct message *mp){
2561    char *cp;
2562    struct mx_name *np;
2563    NYD_IN;
2564 
2565    if((np = mx_header_sender_of(mp, GFULL | GSKIN)) != NIL){
2566       cp = np->n_fullname;
2567       goto jleave;
2568    }
2569 
2570    /* C99 */{
2571       char *linebuf, *namebuf, *cp2;
2572       uz linesize, namesize;
2573       FILE *ibuf;
2574       int f1st = 1;
2575 
2576       mx_fs_linepool_aquire(&linebuf, &linesize);
2577       mx_fs_linepool_aquire(&namebuf, &namesize);
2578 
2579       /* And fallback only works for MBOX */
2580       namebuf = n_realloc(namebuf, namesize = LINESIZE);
2581       namebuf[0] = 0;
2582       if (mp->m_flag & MNOFROM)
2583          goto jout;
2584       if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2585          goto jout;
2586       if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2587          goto jout;
2588 
2589 jnewname:
2590       if (namesize <= linesize)
2591          namebuf = n_realloc(namebuf, namesize = linesize +1);
2592       for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2593          ;
2594       for (; su_cs_is_blank(*cp); ++cp)
2595          ;
2596       for (cp2 = namebuf + su_cs_len(namebuf);
2597             *cp && !su_cs_is_blank(*cp) &&
2598             PCMP(cp2, <, namebuf + namesize -1);)
2599          *cp2++ = *cp++;
2600       *cp2 = '\0';
2601 
2602       if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2603          goto jout;
2604       if ((cp = su_cs_find_c(linebuf, 'F')) == NULL)
2605          goto jout;
2606       if (strncmp(cp, "From", 4))
2607          goto jout;
2608       if (namesize <= linesize)
2609          namebuf = n_realloc(namebuf, namesize = linesize + 1);
2610 
2611       /* UUCP from 976 (we do not support anyway!) */
2612       while ((cp = su_cs_find_c(cp, 'r')) != NULL) {
2613          if (!strncmp(cp, "remote", 6)) {
2614             if ((cp = su_cs_find_c(cp, 'f')) == NULL)
2615                break;
2616             if (strncmp(cp, "from", 4) != 0)
2617                break;
2618             if ((cp = su_cs_find_c(cp, ' ')) == NULL)
2619                break;
2620             cp++;
2621             if (f1st) {
2622                strncpy(namebuf, cp, namesize);
2623                f1st = 0;
2624             } else {
2625                cp2 = su_cs_rfind_c(namebuf, '!') + 1;
2626                strncpy(cp2, cp, P2UZ(namebuf + namesize - cp2));
2627             }
2628             namebuf[namesize - 2] = '!';
2629             namebuf[namesize - 1] = '\0';
2630             goto jnewname;
2631          }
2632          cp++;
2633       }
2634 jout:
2635       if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2636             *cp == '\0')
2637          cp = savestr(namebuf);
2638 
2639       mx_fs_linepool_release(namebuf, namesize);
2640       mx_fs_linepool_release(linebuf, linesize);
2641    }
2642 
2643 jleave:
2644    NYD_OU;
2645    return cp;
2646 }
2647 
2648 FL char const *
subject_re_trim(char const * s)2649 subject_re_trim(char const *s){
2650    struct{
2651       u8 len;
2652       char  dat[7];
2653    }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2654       {3, "re:"},
2655       {3, "aw:"}, {5, "antw:"}, /* de */
2656       {3, "wg:"}, /* Seen too often in the wild */
2657       {0, ""}
2658    };
2659 
2660    boole any;
2661    char *re_st, *re_st_x;
2662    char const *orig_s;
2663    uz re_l;
2664    NYD_IN;
2665 
2666    any = FAL0;
2667    orig_s = s;
2668    re_st = NULL;
2669    UNINIT(re_l, 0);
2670 
2671    if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2672          (re_l = su_cs_len(re_st_x)) > 0){
2673       re_st = n_lofi_alloc(++re_l * 2);
2674       su_mem_copy(re_st, re_st_x, re_l);
2675    }
2676 
2677 jouter:
2678    while(*s != '\0'){
2679       while(su_cs_is_space(*s))
2680          ++s;
2681 
2682       for(pp = ignored; pp->len > 0; ++pp)
2683          if(su_cs_starts_with_case(s, pp->dat)){
2684             s += pp->len;
2685             any = TRU1;
2686             goto jouter;
2687          }
2688 
2689       if(re_st != NULL){
2690          char *cp;
2691 
2692          su_mem_copy(re_st_x = &re_st[re_l], re_st, re_l);
2693          while((cp = su_cs_sep_c(&re_st_x, ',', TRU1)) != NULL)
2694             if(su_cs_starts_with_case(s, cp)){
2695                s += su_cs_len(cp);
2696                any = TRU1;
2697                goto jouter;
2698             }
2699       }
2700       break;
2701    }
2702 
2703    if(re_st != NULL)
2704       n_lofi_free(re_st);
2705    NYD_OU;
2706    return any ? s : orig_s;
2707 }
2708 
2709 FL int
msgidcmp(char const * s1,char const * s2)2710 msgidcmp(char const *s1, char const *s2)
2711 {
2712    int q1 = 0, q2 = 0, c1, c2;
2713    NYD_IN;
2714 
2715    while(*s1 == '<')
2716       ++s1;
2717    while(*s2 == '<')
2718       ++s2;
2719 
2720    do {
2721       c1 = msgidnextc(&s1, &q1);
2722       c2 = msgidnextc(&s2, &q2);
2723       if (c1 != c2)
2724          break;
2725    } while (c1 && c2);
2726    NYD_OU;
2727    return c1 - c2;
2728 }
2729 
2730 FL char const *
fakefrom(struct message * mp)2731 fakefrom(struct message *mp)
2732 {
2733    char const *name;
2734    NYD_IN;
2735 
2736    if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2737          ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2738       /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2739        * RFC 4155 however requires a RFC 5322 (2822) conforming
2740        * "addr-spec", but we simply can't provide that */
2741       name = "MAILER-DAEMON";
2742    NYD_OU;
2743    return name;
2744 }
2745 
2746 #if defined mx_HAVE_IMAP_SEARCH || defined mx_HAVE_IMAP
2747 FL time_t
unixtime(char const * fromline)2748 unixtime(char const *fromline)
2749 {
2750    char const *fp, *xp;
2751    time_t t;
2752    s32 i, year, month, day, hour, minute, second;
2753    NYD2_IN;
2754 
2755    for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2756       ;
2757    fp -= 24;
2758    if (P2UZ(fp - fromline) < 7)
2759       goto jinvalid;
2760    if (fp[3] != ' ')
2761       goto jinvalid;
2762    for (i = 0;;) {
2763       if (!strncmp(fp + 4, n_month_names[i], 3))
2764          break;
2765       if (n_month_names[++i][0] == '\0')
2766          goto jinvalid;
2767    }
2768    month = i + 1;
2769    if (fp[7] != ' ')
2770       goto jinvalid;
2771    su_idec_s32_cp(&day, &fp[8], 10, &xp);
2772    if (*xp != ' ' || xp != fp + 10)
2773       goto jinvalid;
2774    su_idec_s32_cp(&hour, &fp[11], 10, &xp);
2775    if (*xp != ':' || xp != fp + 13)
2776       goto jinvalid;
2777    su_idec_s32_cp(&minute, &fp[14], 10, &xp);
2778    if (*xp != ':' || xp != fp + 16)
2779       goto jinvalid;
2780    su_idec_s32_cp(&second, &fp[17], 10, &xp);
2781    if (*xp != ' ' || xp != fp + 19)
2782       goto jinvalid;
2783    su_idec_s32_cp(&year, &fp[20], 10, &xp);
2784    if (xp != fp + 24)
2785       goto jinvalid;
2786    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2787       goto jinvalid;
2788 
2789    t += n_time_tzdiff(t, NIL, NIL);
2790 
2791 jleave:
2792    NYD2_OU;
2793    return t;
2794 jinvalid:
2795    t = n_time_epoch();
2796    goto jleave;
2797 }
2798 #endif /* mx_HAVE_IMAP_SEARCH || mx_HAVE_IMAP */
2799 
2800 FL time_t
rfctime(char const * date)2801 rfctime(char const *date) /* TODO su_idec_ return tests */
2802 {
2803    char const *cp, *x;
2804    time_t t;
2805    s32 i, year, month, day, hour, minute, second;
2806    NYD2_IN;
2807 
2808    cp = date;
2809 
2810    if ((cp = nexttoken(cp)) == NULL)
2811       goto jinvalid;
2812    if (su_cs_is_alpha(cp[0]) && su_cs_is_alpha(cp[1]) &&
2813          su_cs_is_alpha(cp[2]) && cp[3] == ',') {
2814       if ((cp = nexttoken(&cp[4])) == NULL)
2815          goto jinvalid;
2816    }
2817    su_idec_s32_cp(&day, cp, 10, &x);
2818    if ((cp = nexttoken(x)) == NULL)
2819       goto jinvalid;
2820    for (i = 0;;) {
2821       if (!strncmp(cp, n_month_names[i], 3))
2822          break;
2823       if (n_month_names[++i][0] == '\0')
2824          goto jinvalid;
2825    }
2826    month = i + 1;
2827    if ((cp = nexttoken(&cp[3])) == NULL)
2828       goto jinvalid;
2829    /* RFC 5322, 4.3:
2830     *  Where a two or three digit year occurs in a date, the year is to be
2831     *  interpreted as follows: If a two digit year is encountered whose
2832     *  value is between 00 and 49, the year is interpreted by adding 2000,
2833     *  ending up with a value between 2000 and 2049.  If a two digit year
2834     *  is encountered with a value between 50 and 99, or any three digit
2835     *  year is encountered, the year is interpreted by adding 1900 */
2836    su_idec_s32_cp(&year, cp, 10, &x);
2837    i = (int)P2UZ(x - cp);
2838    if (i == 2 && year >= 0 && year <= 49)
2839       year += 2000;
2840    else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2841       year += 1900;
2842    if ((cp = nexttoken(x)) == NULL)
2843       goto jinvalid;
2844    su_idec_s32_cp(&hour, cp, 10, &x);
2845    if (*x != ':')
2846       goto jinvalid;
2847    cp = &x[1];
2848    su_idec_s32_cp(&minute, cp, 10, &x);
2849    if (*x == ':') {
2850       cp = &x[1];
2851       su_idec_s32_cp(&second, cp, 10, &x);
2852    } else
2853       second = 0;
2854 
2855    if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2856       goto jinvalid;
2857    if ((cp = nexttoken(x)) != NULL) {
2858       char buf[3];
2859       int sign = 1;
2860 
2861       switch (*cp) {
2862       case '+':
2863          sign = -1;
2864          /* FALLTHRU */
2865       case '-':
2866          ++cp;
2867          break;
2868       }
2869       if (su_cs_is_digit(cp[0]) && su_cs_is_digit(cp[1]) &&
2870             su_cs_is_digit(cp[2]) && su_cs_is_digit(cp[3])) {
2871          s64 tadj;
2872 
2873          buf[2] = '\0';
2874          buf[0] = cp[0];
2875          buf[1] = cp[1];
2876          su_idec_s32_cp(&i, buf, 10, NULL);
2877          tadj = (s64)i * 3600; /* XXX */
2878          buf[0] = cp[2];
2879          buf[1] = cp[3];
2880          su_idec_s32_cp(&i, buf, 10, NULL);
2881          tadj += (s64)i * 60; /* XXX */
2882          if (sign < 0)
2883             tadj = -tadj;
2884          t += (time_t)tadj;
2885       }
2886       /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2887        * TODO once again, Christos Zoulas and NetBSD Mail have done
2888        * TODO a really good job already, but using strptime(3), which
2889        * TODO is not portable.  Nonetheless, WE must improve, not
2890        * TODO at last because we simply ignore obsolete timezones!!
2891        * TODO See RFC 5322, 4.3! */
2892    }
2893 jleave:
2894    NYD2_OU;
2895    return t;
2896 jinvalid:
2897    t = 0;
2898    goto jleave;
2899 }
2900 
2901 FL time_t
combinetime(int year,int month,int day,int hour,int minute,int second)2902 combinetime(int year, int month, int day, int hour, int minute, int second){
2903    uz const jdn_epoch = 2440588;
2904    boole const y2038p = (sizeof(time_t) == 4);
2905 
2906    uz jdn;
2907    time_t t;
2908    NYD2_IN;
2909 
2910    if(UCMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2911          UCMP(32, minute, >=, n_DATE_MINSHOUR) ||
2912          UCMP(32, hour, >=, n_DATE_HOURSDAY) ||
2913          day < 1 || day > 31 ||
2914          month < 1 || month > 12 ||
2915          year < 1970)
2916       goto jerr;
2917 
2918    if(year >= 1970 + ((y2038p ? S32_MAX : S64_MAX) /
2919          (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2920       /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2921        * test by stepping second-wise around the flip.  Don't care otherwise */
2922       if(!y2038p)
2923          goto jerr;
2924       if(year > 2038 || month > 1 || day > 19 ||
2925             hour > 3 || minute > 14 || second > 7)
2926          goto jerr;
2927    }
2928 
2929    t = second;
2930    t += minute * n_DATE_SECSMIN;
2931    t += hour * n_DATE_SECSHOUR;
2932 
2933    jdn = a_header_gregorian_to_jdn(year, month, day);
2934    jdn -= jdn_epoch;
2935    t += (time_t)jdn * n_DATE_SECSDAY;
2936 jleave:
2937    NYD2_OU;
2938    return t;
2939 jerr:
2940    t = (time_t)-1;
2941    goto jleave;
2942 }
2943 
2944 FL void
substdate(struct message * m)2945 substdate(struct message *m)
2946 {
2947    /* The Date: of faked From_ lines is traditionally the date the message was
2948     * written to the mail file. Try to determine this using RFC message header
2949     * fields, or fall back to current time */
2950    char const *cp;
2951    NYD_IN;
2952 
2953    m->m_time = 0;
2954    if ((cp = hfield1("received", m)) != NULL) {
2955       while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2956          do
2957             ++cp;
2958          while (su_cs_is_alnum(*cp));
2959       }
2960       if (cp && *++cp)
2961          m->m_time = rfctime(cp);
2962    }
2963    if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2964       if ((cp = hfield1("date", m)) != NULL)
2965          m->m_time = rfctime(cp);
2966    }
2967    if (m->m_time == 0 || m->m_time > time_current.tc_time)
2968       m->m_time = time_current.tc_time;
2969    NYD_OU;
2970 }
2971 
2972 FL char *
n_header_textual_date_info(struct message * mp,char const ** color_tag_or_null)2973 n_header_textual_date_info(struct message *mp, char const **color_tag_or_null){
2974    struct tm tmlocal;
2975    char *rv;
2976    char const *fmt, *cp;
2977    time_t t;
2978    NYD_IN;
2979    UNUSED(color_tag_or_null);
2980 
2981    t = mp->m_time;
2982    fmt = ok_vlook(datefield);
2983 
2984 jredo:
2985    if(fmt != NULL){
2986       u8 i;
2987 
2988       cp = hfield1("date", mp);/* TODO use m_date field! */
2989       if(cp == NULL){
2990          fmt = NULL;
2991          goto jredo;
2992       }
2993 
2994       t = rfctime(cp);
2995       rv = n_time_ctime(t, NULL);
2996       cp = ok_vlook(datefield_markout_older);
2997       i = (*fmt != '\0');
2998       if(cp != NULL)
2999          i |= (*cp != '\0') ? 2 | 4 : 2; /* XXX no magics */
3000 
3001       /* May we strftime(3)? */
3002       if(i & (1 | 4)){
3003          /* This localtime(3) should not fail since rfctime(3).. but .. */
3004          struct tm *tmp;
3005          time_t t2;
3006 
3007          /* TODO the datetime stuff is horror: mails should be parsed into
3008           * TODO an object tree, and date: etc. have a datetime object, which
3009           * TODO verifies upon parse time; then ALL occurrences of datetime are
3010           * TODO valid all through the program; and: to_wire, to_user! */
3011          t2 = t;
3012 jredo_localtime:
3013          if((tmp = localtime(&t2)) == NULL){
3014             t2 = 0;
3015             goto jredo_localtime;
3016          }
3017          su_mem_copy(&tmlocal, tmp, sizeof *tmp);
3018       }
3019 
3020       if((i & 2) &&
3021             (UCMP(64, t, >, time_current.tc_time + n_DATE_SECSDAY) ||
3022 #define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY)
3023             UCMP(64, t + _6M, <, time_current.tc_time))){
3024 #undef _6M
3025          if((fmt = (i & 4) ? cp : NULL) == NULL){
3026             char *x;
3027             LCTA(n_FROM_DATEBUF >= 4 + 7 + 1 + 4, "buffer too small");
3028 
3029             x = n_autorec_alloc(n_FROM_DATEBUF);
3030             su_mem_set(x, ' ', 4 + 7 + 1 + 4);
3031             su_mem_copy(&x[4], &rv[4], 7);
3032             x[4 + 7] = ' ';
3033             su_mem_copy(&x[4 + 7 + 1], &rv[20], 4);
3034             x[4 + 7 + 1 + 4] = '\0';
3035             rv = x;
3036          }
3037          mx_COLOUR(
3038             if(color_tag_or_null != NULL)
3039                *color_tag_or_null = mx_COLOUR_TAG_SUM_OLDER;
3040          )
3041       }else if((i & 1) == 0)
3042          fmt = NULL;
3043 
3044       if(fmt != NULL){
3045          uz j;
3046 
3047          for(j = n_FROM_DATEBUF;; j <<= 1){
3048             i = strftime(rv = n_autorec_alloc(j), j, fmt, &tmlocal);
3049             if(i != 0)
3050                break;
3051             if(j > 128){
3052                n_err(_("Ignoring this date format: %s\n"),
3053                   n_shexp_quote_cp(fmt, FAL0));
3054                su_cs_pcopy_n(rv, n_time_ctime(t, NULL), j);
3055             }
3056          }
3057       }
3058    }else if(t == (time_t)0 && !(mp->m_flag & MNOFROM)){
3059       /* TODO eliminate this path, query the FROM_ date in setptr(),
3060        * TODO all other codepaths do so by themselves ALREADY ?????
3061        * TODO ASSERT(mp->m_time != 0);, then
3062        * TODO ALSO changes behaviour of datefield_markout_older */
3063       a_header_parse_from_(mp, rv = n_autorec_alloc(n_FROM_DATEBUF));
3064    }else
3065       rv = savestr(n_time_ctime(t, NULL));
3066    NYD_OU;
3067    return rv;
3068 }
3069 
3070 FL struct mx_name *
n_header_textual_sender_info(struct message * mp,char ** cumulation_or_null,char ** addr_or_null,char ** name_real_or_null,char ** name_full_or_null,boole * is_to_or_null)3071 n_header_textual_sender_info(struct message *mp, char **cumulation_or_null,
3072       char **addr_or_null, char **name_real_or_null, char **name_full_or_null,
3073       boole *is_to_or_null){
3074    struct n_string s_b1, s_b2, *sp1, *sp2;
3075    struct mx_name *np, *np2;
3076    boole isto, b;
3077    char *cp;
3078    NYD_IN;
3079 
3080    cp = n_header_senderfield_of(mp);
3081    isto = FAL0;
3082 
3083    if((np = lextract(cp, GFULL | GSKIN)) != NULL){
3084       if(is_to_or_null != NULL && ok_blook(showto) &&
3085             np->n_flink == NULL && mx_name_is_mine(np->n_name)){
3086          if((cp = hfield1("to", mp)) != NULL &&
3087                (np2 = lextract(cp, GFULL | GSKIN)) != NULL){
3088             np = np2;
3089             isto = TRU1;
3090          }
3091       }
3092 
3093       if(((b = ok_blook(showname)) && cumulation_or_null != NULL) ||
3094             name_real_or_null != NULL || name_full_or_null != NULL){
3095          uz i;
3096 
3097          for(i = 0, np2 = np; np2 != NULL; np2 = np2->n_flink)
3098             i += su_cs_len(np2->n_fullname) +3;
3099 
3100          sp1 = n_string_book(n_string_creat_auto(&s_b1), i);
3101          sp2 = (name_full_or_null == NULL) ? NULL
3102                : n_string_book(n_string_creat_auto(&s_b2), i);
3103 
3104          for(np2 = np; np2 != NULL; np2 = np2->n_flink){
3105             if(sp1->s_len > 0){
3106                sp1 = n_string_push_c(sp1, ',');
3107                sp1 = n_string_push_c(sp1, ' ');
3108                if(sp2 != NULL){
3109                   sp2 = n_string_push_c(sp2, ',');
3110                   sp2 = n_string_push_c(sp2, ' ');
3111                }
3112             }
3113 
3114             if((cp = realname(np2->n_fullname)) == NULL || *cp == '\0')
3115                cp = np2->n_name;
3116             sp1 = n_string_push_cp(sp1, cp);
3117             if(sp2 != NULL)
3118                sp2 = n_string_push_cp(sp2, (*np2->n_fullname == '\0'
3119                      ? np2->n_name : np2->n_fullname));
3120          }
3121 
3122          n_string_cp(sp1);
3123          if(b && cumulation_or_null != NULL)
3124             *cumulation_or_null = sp1->s_dat;
3125          if(name_real_or_null != NULL)
3126             *name_real_or_null = sp1->s_dat;
3127          if(name_full_or_null != NULL)
3128             *name_full_or_null = n_string_cp(sp2);
3129 
3130          /* n_string_gut(n_string_drop_ownership(sp2)); */
3131          /* n_string_gut(n_string_drop_ownership(sp1)); */
3132       }
3133 
3134       if((b = (!b && cumulation_or_null != NULL)) || addr_or_null != NULL){
3135          cp = detract(np, GCOMMA | GNAMEONLY);
3136          if(b)
3137             *cumulation_or_null = cp;
3138          if(addr_or_null != NULL)
3139             *addr_or_null = cp;
3140       }
3141    }else if(cumulation_or_null != NULL || addr_or_null != NULL ||
3142          name_real_or_null != NULL || name_full_or_null != NULL){
3143       cp = savestr(n_empty);
3144 
3145       if(cumulation_or_null != NULL)
3146          *cumulation_or_null = cp;
3147       if(addr_or_null != NULL)
3148          *addr_or_null = cp;
3149       if(name_real_or_null != NULL)
3150          *name_real_or_null = cp;
3151       if(name_full_or_null != NULL)
3152          *name_full_or_null = cp;
3153    }
3154 
3155    if(is_to_or_null != NULL)
3156       *is_to_or_null = isto;
3157    NYD_OU;
3158    return np;
3159 }
3160 
3161 FL void
setup_from_and_sender(struct header * hp)3162 setup_from_and_sender(struct header *hp){
3163    char const *addr;
3164    struct mx_name *np;
3165    NYD_IN;
3166 
3167    /* If -t parsed or composed From: then take it.  With -t we otherwise
3168     * want -r to be honoured in favour of *from* in order to have
3169     * a behaviour that is compatible with what users would expect from e.g.
3170     * postfix(1) */
3171    if((np = hp->h_from) != NIL ||
3172          ((n_poption & n_PO_t_FLAG) && (np = n_poption_arg_r) != NIL)){
3173       ;
3174    }else if((addr = myaddrs(hp)) != NIL)
3175       np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
3176 
3177    hp->h_from = np;
3178 
3179    /* RFC 5322 says
3180     *  If the originator of the message can be indicated by a single mailbox
3181     *  and the author and transmitter are identical, the "Sender:" field SHOULD
3182     *  NOT be used.  Otherwise, both fields SHOULD appear. */
3183    if((np = hp->h_sender) != NIL){
3184       ;
3185    }else if((addr = ok_vlook(sender)) != NIL)
3186       np = n_extract_single(addr, GEXTRA | GFULL | GFULLEXTRA);
3187 
3188    if(np != NIL && hp->h_from != NIL && hp->h_from->n_flink == NIL &&
3189          mx_name_is_same_address(hp->h_from, np))
3190       np = NIL;
3191 
3192    hp->h_sender = np;
3193 
3194    NYD_OU;
3195 }
3196 
3197 FL struct mx_name const *
check_from_and_sender(struct mx_name const * fromfield,struct mx_name const * senderfield)3198 check_from_and_sender(struct mx_name const *fromfield,
3199    struct mx_name const *senderfield)
3200 {
3201    struct mx_name const *rv = NULL;
3202    NYD_IN;
3203 
3204    if (senderfield != NULL) {
3205       if (senderfield->n_flink != NULL) {
3206          n_err(_("The Sender: field may contain only one address\n"));
3207          goto jleave;
3208       }
3209       rv = senderfield;
3210    }
3211 
3212    if (fromfield != NULL) {
3213       if (fromfield->n_flink != NULL && senderfield == NULL) {
3214          n_err(_("A Sender: is required when there are multiple "
3215             "addresses in From:\n"));
3216          goto jleave;
3217       }
3218       if (rv == NULL)
3219          rv = fromfield;
3220    }
3221 
3222    if (rv == NULL)
3223       rv = (struct mx_name*)0x1;
3224 jleave:
3225    NYD_OU;
3226    return rv;
3227 }
3228 
3229 #ifdef mx_HAVE_XTLS
3230 FL char *
getsender(struct message * mp)3231 getsender(struct message *mp)
3232 {
3233    char *cp;
3234    struct mx_name *np;
3235    NYD_IN;
3236 
3237    if ((cp = hfield1("from", mp)) == NULL ||
3238          (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
3239       cp = NULL;
3240    else
3241       cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
3242    NYD_OU;
3243    return cp;
3244 }
3245 #endif
3246 
3247 FL struct mx_name *
n_header_setup_in_reply_to(struct header * hp)3248 n_header_setup_in_reply_to(struct header *hp){
3249    struct mx_name *np;
3250    NYD_IN;
3251 
3252    np = NULL;
3253 
3254    if(hp != NULL)
3255       if((np = hp->h_in_reply_to) == NULL && (np = hp->h_ref) != NULL)
3256          while(np->n_flink != NULL)
3257             np = np->n_flink;
3258    NYD_OU;
3259    return np;
3260 }
3261 
3262 FL int
grab_headers(enum n_go_input_flags gif,struct header * hp,enum gfield gflags,int subjfirst)3263 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
3264       int subjfirst)
3265 {
3266    /* TODO grab_headers: again, check counts etc. against RFC;
3267     * TODO (now assumes check_from_and_sender() is called afterwards ++ */
3268    int errs;
3269    int volatile comma;
3270    NYD_IN;
3271 
3272    errs = 0;
3273    comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
3274 
3275    if (gflags & GTO)
3276       hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
3277    if (subjfirst && (gflags & GSUBJECT))
3278       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
3279    if (gflags & GCC)
3280       hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
3281    if (gflags & GBCC)
3282       hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
3283 
3284    if (gflags & GEXTRA) {
3285       if (hp->h_from == NULL)
3286          hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
3287       hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
3288             GEXTRA | GFULL | GFULLEXTRA);
3289       if (hp->h_reply_to == NULL) {
3290          struct mx_name *v15compat;
3291 
3292          if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
3293             n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
3294          hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
3295          if(hp->h_reply_to == NULL) /* v15 */
3296             hp->h_reply_to = v15compat;
3297       }
3298       hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
3299             GEXTRA | GFULL);
3300       if (hp->h_sender == NULL)
3301          hp->h_sender = n_extract_single(ok_vlook(sender), GEXTRA | GFULL);
3302       hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
3303             GEXTRA | GFULL | GNOT_A_LIST);
3304    }
3305 
3306    if (!subjfirst && (gflags & GSUBJECT))
3307       hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
3308 
3309    NYD_OU;
3310    return errs;
3311 }
3312 
3313 FL boole
n_header_match(struct message * mp,struct search_expr const * sep)3314 n_header_match(struct message *mp, struct search_expr const *sep){
3315    struct str fiter, in, out;
3316    char const *field;
3317    long lc;
3318    FILE *ibuf;
3319    uz linesize;
3320    char *linebuf, *colon;
3321    enum {a_NONE, a_ALL, a_ITER, a_RE} match;
3322    boole rv;
3323    NYD_IN;
3324 
3325    rv = FAL0;
3326    match = a_NONE;
3327    mx_fs_linepool_aquire(&linebuf, &linesize);
3328    UNINIT(fiter.l, 0);
3329    UNINIT(fiter.s, NIL);
3330 
3331    if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
3332       goto jleave;
3333    if((lc = mp->m_lines - 1) < 0)
3334       goto jleave;
3335 
3336    if((mp->m_flag & MNOFROM) == 0 &&
3337          readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
3338       goto jleave;
3339 
3340    /* */
3341    if((field = sep->ss_field) != NULL){
3342       if(!su_cs_cmp_case(field, "header") ||
3343             (field[0] == '<' && field[1] == '\0'))
3344          match = a_ALL;
3345       else{
3346          fiter.s = n_lofi_alloc((fiter.l = su_cs_len(field)) +1);
3347          match = a_ITER;
3348       }
3349 #ifdef mx_HAVE_REGEX
3350    }else if(sep->ss_fieldre != NULL){
3351       match = a_RE;
3352 #endif
3353    }else
3354       match = a_ALL;
3355 
3356    /* Iterate over all the headers */
3357    while(lc > 0){
3358       struct mx_name *np;
3359 
3360       if((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, &linebuf, &linesize,
3361             lc, &colon)) <= 0)
3362          break;
3363 
3364       /* Is this a header we are interested in? */
3365       if(match == a_ITER){
3366          char *itercp;
3367 
3368          su_mem_copy(itercp = fiter.s, sep->ss_field, fiter.l +1);
3369          while((field = su_cs_sep_c(&itercp, ',', TRU1)) != NULL){
3370             /* It may be an abbreviation */
3371             char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
3372             uz i;
3373             char c1;
3374 
3375             if(field[0] != '\0' && field[1] == '\0'){
3376                c1 = su_cs_to_lower(field[0]);
3377                for(i = 0; i < NELEM(x); ++i){
3378                   if(c1 == x[i][0]){
3379                      field = x[i];
3380                      break;
3381                   }
3382                }
3383             }
3384 
3385             if(!su_cs_cmp_case_n(field, linebuf, P2UZ(colon - linebuf)))
3386                break;
3387          }
3388          if(field == NULL)
3389             continue;
3390 #ifdef mx_HAVE_REGEX
3391       }else if(match == a_RE){
3392          char *cp;
3393          uz i;
3394 
3395          i = P2UZ(colon - linebuf);
3396          cp = n_lofi_alloc(i +1);
3397          su_mem_copy(cp, linebuf, i);
3398          cp[i] = '\0';
3399          i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
3400          n_lofi_free(cp);
3401          if(!i)
3402             continue;
3403 #endif
3404       }
3405 
3406       /* It could be a plain existence test */
3407       if(sep->ss_field_exists){
3408          rv = TRU1;
3409          break;
3410       }
3411 
3412       /* Need to check the body */
3413       while(su_cs_is_blank(*++colon))
3414          ;
3415       in.s = colon;
3416 
3417       /* Shall we split into address list and match as/the addresses only?
3418        * TODO at some later time we should ignore and log efforts to search
3419        * TODO a skinned address list if we know the header has none such */
3420       if(sep->ss_skin){
3421          if((np = lextract(in.s, GSKIN)) == NULL)
3422             continue;
3423          out.s = np->n_name;
3424       }else{
3425          np = NULL;
3426          in.l = su_cs_len(in.s);
3427          mime_fromhdr(&in, &out, TD_ICONV);
3428       }
3429 
3430 jnext_name:
3431 #ifdef mx_HAVE_REGEX
3432       if(sep->ss_bodyre != NULL)
3433          rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
3434       else
3435 #endif
3436          rv = substr(out.s, sep->ss_body);
3437 
3438       if(np == NULL)
3439          n_free(out.s);
3440       if(rv)
3441          break;
3442       if(np != NULL && (np = np->n_flink) != NULL){
3443          out.s = np->n_name;
3444          goto jnext_name;
3445       }
3446    }
3447 
3448 jleave:
3449    if(match == a_ITER)
3450       n_lofi_free(fiter.s);
3451    mx_fs_linepool_release(linebuf, linesize);
3452    NYD_OU;
3453    return rv;
3454 }
3455 
3456 FL char const *
n_header_is_known(char const * name,uz len)3457 n_header_is_known(char const *name, uz len){
3458    static char const * const names[] = {
3459       /* RFC 5322 header names common here */
3460       "Bcc", "Cc", "From",
3461       "In-Reply-To", "Mail-Followup-To",
3462       "Message-ID", "References", "Reply-To",
3463       "Sender", "Subject", "To",
3464       /* More known, here and there */
3465       "Fcc",
3466       /* Mailx internal temporaries */
3467       "Mailx-Command",
3468       "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From",
3469          "Mailx-Orig-Sender", "Mailx-Orig-To",
3470       "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
3471       /* Rest of RFC 5322 standard headers, almost never seen here.
3472        * As documented for *customhdr*, allow Comments:, Keywords: */
3473       /*"Comments",*/ "Date",
3474       /*"Keywords",*/ "Received",
3475       "Resent-Bcc", "Resent-Cc", "Resent-Date",
3476          "Resent-From", "Resent-Message-ID", "Resent-Reply-To",
3477          "Resent-Sender", "Resent-To",
3478       "Return-Path",
3479       NIL
3480    };
3481    char const * const *rv;
3482    NYD_IN;
3483 
3484    if(len == UZ_MAX)
3485       len = su_cs_len(name);
3486 
3487    for(rv = names; *rv != NIL; ++rv)
3488       if(!su_cs_cmp_case_n(*rv, name, len))
3489          break;
3490 
3491    NYD_OU;
3492    return *rv;
3493 }
3494 
3495 FL boole
n_header_add_custom(struct n_header_field ** hflp,char const * dat,boole heap)3496 n_header_add_custom(struct n_header_field **hflp, char const *dat, boole heap){
3497    struct str hname;
3498    uz i;
3499    u32 bl;
3500    char const *cp;
3501    struct n_header_field *hfp;
3502    NYD_IN;
3503 
3504    hfp = NIL;
3505 
3506    /* For (-C) convenience, allow leading WS */
3507    if((cp = mx_header_is_valid(dat, TRU1, &hname)) == NIL){
3508       cp = N_("Invalid custom header (not valid \"field: body\"): %s\n");
3509       goto jerr;
3510    }
3511 
3512    /* Verify the custom header does not use standard/managed field name */
3513    if(n_header_is_known(hname.s, hname.l) != NIL){
3514       cp = N_("Custom headers cannot use standard header names: %s\n");
3515       goto jerr;
3516    }
3517 
3518    /* Skip on over to the body */
3519    bl = S(u32,su_cs_len(cp));
3520    while(bl > 0 && su_cs_is_space(cp[bl - 1]))
3521       --bl;
3522    for(i = bl++; i-- != 0;)
3523       if(su_cs_is_cntrl(cp[i])){
3524          cp = N_("Invalid custom header: contains control characters: %s\n");
3525          goto jerr;
3526       }
3527 
3528    i = VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + hname.l +1 + bl +1;
3529    *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
3530    hfp->hf_next = NULL;
3531    hfp->hf_nl = hname.l;
3532    hfp->hf_bl = bl - 1;
3533    su_mem_copy(hfp->hf_dat, hname.s, hname.l);
3534       hfp->hf_dat[hname.l++] = '\0';
3535       su_mem_copy(&hfp->hf_dat[hname.l], cp, bl);
3536 
3537 jleave:
3538    NYD_OU;
3539    return (hfp != NIL);
3540 
3541 jerr:
3542    n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
3543    goto jleave;
3544 }
3545 
3546 #include "su/code-ou.h"
3547 /* s-it-mode */
3548