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