1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Message sending lifecycle, header composing, etc.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 /*
9  * Copyright (c) 1980, 1993
10  *      The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #undef su_FILE
37 #define su_FILE sendout
38 #define mx_SOURCE
39 
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43 
44 #include <su/cs.h>
45 #include <su/mem.h>
46 
47 #include "mx/attachments.h"
48 #include "mx/child.h"
49 #include "mx/cmd.h"
50 #include "mx/cmd-mlist.h"
51 #include "mx/cred-auth.h"
52 #include "mx/file-locks.h"
53 #include "mx/file-streams.h"
54 #include "mx/iconv.h"
55 #include "mx/mime-type.h"
56 #include "mx/names.h"
57 #include "mx/net-smtp.h"
58 #include "mx/privacy.h"
59 #include "mx/random.h"
60 #include "mx/sigs.h"
61 #include "mx/tty.h"
62 #include "mx/url.h"
63 
64 /* TODO fake */
65 #include "su/code-in.h"
66 
67 #undef SEND_LINESIZE
68 #define SEND_LINESIZE \
69    ((1024 / B64_ENCODE_INPUT_PER_LINE) * B64_ENCODE_INPUT_PER_LINE)
70 
71 enum a_sendout_addrline_flags{
72    a_SENDOUT_AL_INC_INVADDR = 1<<0, /* _Do_ include invalid addresses */
73    a_SENDOUT_AL_DOMIME = 1<<1,      /* Perform MIME conversion */
74    a_SENDOUT_AL_COMMA = GCOMMA,
75    a_SENDOUT_AL_FILES = GFILES,
76    _a_SENDOUT_AL_GMASK = a_SENDOUT_AL_COMMA | a_SENDOUT_AL_FILES
77 };
78 CTA(!(_a_SENDOUT_AL_GMASK & (a_SENDOUT_AL_INC_INVADDR|a_SENDOUT_AL_DOMIME)),
79    "Code-required condition not satisfied but actual bit carrier value");
80 
81 enum a_sendout_sendwait_flags{
82    a_SENDOUT_SWF_NONE,
83    a_SENDOUT_SWF_MTA = 1u<<0,
84    a_SENDOUT_SWF_PCC = 1u<<1,
85    a_SENDOUT_SWF_MASK = a_SENDOUT_SWF_MTA | a_SENDOUT_SWF_PCC
86 };
87 
88 static char const *__sendout_ident; /* TODO temporary; rewrite n_puthead() */
89 static char *  _sendout_boundary;
90 static s8   _sendout_error;
91 
92 /* */
93 static u32 a_sendout_sendwait_to_swf(void);
94 
95 /* *fullnames* appears after command line arguments have been parsed */
96 static struct mx_name *a_sendout_fullnames_cleanup(struct mx_name *np);
97 
98 /* */
99 static boole a_sendout_put_name(char const *line, enum gfield w,
100       enum sendaction action, char const *prefix, FILE *fo,
101       struct mx_name **xp);
102 
103 /* Place Content-Type:, Content-Transfer-Encoding:, Content-Disposition:
104  * headers, respectively */
105 static int a_sendout_put_ct(FILE *fo, char const *contenttype,
106                char const *charset);
107 su_SINLINE int a_sendout_put_cte(FILE *fo, enum conversion conv);
108 static int a_sendout_put_cd(FILE *fo, char const *cd, char const *filename);
109 
110 /* Put all entries of the given header list */
111 static boole        _sendout_header_list(FILE *fo, struct n_header_field *hfp,
112                         boole nodisp);
113 
114 /* */
115 static s32 a_sendout_body(FILE *fo, FILE *fi, enum conversion convert);
116 
117 /* Write an attachment to the file buffer, converting to MIME */
118 static s32 a_sendout_attach_file(struct header *hp, struct mx_attachment *ap,
119       FILE *fo, boole force);
120 static s32 a_sendout__attach_file(struct header *hp, struct mx_attachment *ap,
121       FILE *f, boole force);
122 
123 /* There are non-local receivers, collect credentials etc. */
124 static boole        _sendbundle_setup_creds(struct sendbundle *sbpm,
125                         boole signing_caps);
126 
127 /* Attach a message to the file buffer */
128 static s32 a_sendout_attach_msg(struct header *hp, struct mx_attachment *ap,
129                FILE *fo);
130 
131 /* Generate the body of a MIME multipart message */
132 static s32 make_multipart(struct header *hp, int convert, FILE *fi,
133    FILE *fo, char const *contenttype, char const *charset, boole force);
134 
135 /* Prepend a header in front of the collected stuff and return the new file */
136 static FILE *a_sendout_infix(struct header *hp, FILE *fi, boole dosign,
137       boole force);
138 
139 /* Check whether Disposition-Notification-To: is desired */
140 static boole        _check_dispo_notif(struct mx_name *mdn, struct header *hp,
141                         FILE *fo);
142 
143 /* Send mail to a bunch of user names.  The interface is through mail() */
144 static int a_sendout_sendmail(void *v, enum n_mailsend_flags msf);
145 
146 /* Deal with file and pipe addressees */
147 static struct mx_name *a_sendout_file_a_pipe(struct mx_name *names, FILE *fo,
148                      boole *senderror);
149 
150 /* Record outgoing mail if instructed to do so; in *record* unless to is set */
151 static boole a_sendout_mightrecord(FILE *fp, struct mx_name *to, boole resend);
152 
153 static boole a_sendout__savemail(char const *name, FILE *fp, boole resend);
154 
155 /* Move a message over to non-local recipients (via MTA) */
156 static boole a_sendout_transfer(struct sendbundle *sbp, boole resent,
157       boole *senderror);
158 
159 /* Actual MTA interaction */
160 static boole a_sendout_mta_start(struct sendbundle *sbp);
161 static char const **a_sendout_mta_file_args(struct mx_name *to,
162       struct header *hp);
163 static void a_sendout_mta_file_debug(struct sendbundle *sbp, char const *mta,
164       char const **args);
165 static boole a_sendout_mta_test(struct sendbundle *sbp, char const *mta);
166 
167 /* Create a Message-ID: header field.  Use either host name or from address */
168 static char const *a_sendout_random_id(struct header *hp, boole msgid);
169 
170 /* Format the given header line to not exceed 72 characters */
171 static boole a_sendout_put_addrline(char const *hname, struct mx_name *np,
172                FILE *fo, enum a_sendout_addrline_flags saf);
173 
174 /* Rewrite a message for resending, adding the Resent-Headers */
175 static boole a_sendout_infix_resend(FILE *fi, FILE *fo, struct message *mp,
176       struct mx_name *to, int add_resent);
177 
178 static u32
a_sendout_sendwait_to_swf(void)179 a_sendout_sendwait_to_swf(void){ /* TODO should happen at var assign time */
180    char *buf;
181    u32 rv;
182    char const *cp;
183    NYD2_IN;
184 
185    if((cp = ok_vlook(sendwait)) == NIL)
186       rv = a_SENDOUT_SWF_NONE;
187    else if(*cp == '\0')
188       rv = a_SENDOUT_SWF_MASK;
189    else{
190       rv = a_SENDOUT_SWF_NONE;
191 
192       for(buf = savestr(cp); (cp = su_cs_sep_c(&buf, ',', TRU1)) != NIL;){
193          if(!su_cs_cmp_case(cp, "mta"))
194             rv |= a_SENDOUT_SWF_MTA;
195          else if(!su_cs_cmp_case(cp, "pcc"))
196             rv |= a_SENDOUT_SWF_PCC;
197          else if(n_poption & n_PO_D_V)
198             n_err(_("Unknown *sendwait* content: %s\n"),
199                n_shexp_quote_cp(cp, FAL0));
200       }
201    }
202    NYD2_OU;
203    return rv;
204 }
205 
206 static struct mx_name *
a_sendout_fullnames_cleanup(struct mx_name * np)207 a_sendout_fullnames_cleanup(struct mx_name *np){
208    struct mx_name *xp;
209    NYD2_IN;
210 
211    for(xp = np; xp != NULL; xp = xp->n_flink){
212       xp->n_type &= ~(GFULL | GFULLEXTRA);
213       xp->n_fullname = xp->n_name;
214       xp->n_fullextra = NULL;
215    }
216    NYD2_OU;
217    return np;
218 }
219 
220 static boole
a_sendout_put_name(char const * line,enum gfield w,enum sendaction action,char const * prefix,FILE * fo,struct mx_name ** xp)221 a_sendout_put_name(char const *line, enum gfield w, enum sendaction action,
222    char const *prefix, FILE *fo, struct mx_name **xp){
223    boole rv;
224    struct mx_name *np;
225    NYD_IN;
226 
227    np = (w & GNOT_A_LIST ? n_extract_single : lextract)(line, GEXTRA | GFULL);
228    if(xp != NULL)
229       *xp = np;
230 
231    if(np == NULL)
232       rv = FAL0;
233    else
234       rv = a_sendout_put_addrline(prefix, np, fo, ((w & GCOMMA) |
235             ((action != SEND_TODISP) ? a_SENDOUT_AL_DOMIME : 0)));
236    NYD_OU;
237    return rv;
238 }
239 
240 static int
a_sendout_put_ct(FILE * fo,char const * contenttype,char const * charset)241 a_sendout_put_ct(FILE *fo, char const *contenttype, char const *charset){
242    int rv;
243    NYD2_IN;
244 
245    if((rv = fprintf(fo, "Content-Type: %s", contenttype)) < 0)
246       goto jerr;
247 
248    if(charset == NULL)
249       goto jend;
250 
251    if(putc(';', fo) == EOF)
252       goto jerr;
253    ++rv;
254 
255    if(su_cs_len(contenttype) + sizeof("Content-Type: ;")-1 > 50){
256       if(putc('\n', fo) == EOF)
257          goto jerr;
258       ++rv;
259    }
260 
261    /* C99 */{
262       int i;
263 
264       i = fprintf(fo, " charset=%s", charset);
265       if(i < 0)
266          goto jerr;
267       rv += i;
268    }
269 
270 jend:
271    if(putc('\n', fo) == EOF)
272       goto jerr;
273    ++rv;
274 jleave:
275    NYD2_OU;
276    return rv;
277 jerr:
278    rv = -1;
279    goto jleave;
280 }
281 
282 su_SINLINE int
a_sendout_put_cte(FILE * fo,enum conversion conv)283 a_sendout_put_cte(FILE *fo, enum conversion conv){
284    int rv;
285    NYD2_IN;
286 
287    /* RFC 2045, 6.1.:
288     *    This is the default value -- that is,
289     *    "Content-Transfer-Encoding: 7BIT" is assumed if the
290     *     Content-Transfer-Encoding header field is not present.
291     */
292    rv = (conv == CONV_7BIT) ? 0
293          : fprintf(fo, "Content-Transfer-Encoding: %s\n",
294             mime_enc_from_conversion(conv));
295    NYD2_OU;
296    return rv;
297 }
298 
299 static int
a_sendout_put_cd(FILE * fo,char const * cd,char const * filename)300 a_sendout_put_cd(FILE *fo, char const *cd, char const *filename){
301    struct str f;
302    s8 mpc;
303    int rv;
304    NYD2_IN;
305 
306    f.s = NULL;
307 
308    /* xxx Ugly with the trailing space in case of wrap! */
309    if((rv = fprintf(fo, "Content-Disposition: %s; ", cd)) < 0)
310       goto jerr;
311 
312    if(!(mpc = mime_param_create(&f, "filename", filename)))
313       goto jerr;
314    /* Always fold if result contains newlines */
315    if(mpc < 0 || f.l + rv > MIME_LINELEN) { /* FIXME MIME_LINELEN_MAX */
316       if(putc('\n', fo) == EOF || putc(' ', fo) == EOF)
317          goto jerr;
318       rv += 2;
319    }
320    if(fputs(f.s, fo) == EOF || putc('\n', fo) == EOF)
321       goto jerr;
322    rv += (int)++f.l;
323 
324 jleave:
325    NYD2_OU;
326    return rv;
327 jerr:
328    rv = -1;
329    goto jleave;
330 
331 }
332 
333 static boole
_sendout_header_list(FILE * fo,struct n_header_field * hfp,boole nodisp)334 _sendout_header_list(FILE *fo, struct n_header_field *hfp, boole nodisp){
335    boole rv;
336    NYD2_IN;
337 
338    for(rv = TRU1; hfp != NULL; hfp = hfp->hf_next)
339       if(fwrite(hfp->hf_dat, sizeof(char), hfp->hf_nl, fo) != hfp->hf_nl ||
340             putc(':', fo) == EOF || putc(' ', fo) == EOF ||
341             xmime_write(hfp->hf_dat + hfp->hf_nl +1, hfp->hf_bl, fo,
342                (!nodisp ? CONV_NONE : CONV_TOHDR),
343                (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NULL, NULL) < 0 ||
344             putc('\n', fo) == EOF){
345          rv = FAL0;
346          break;
347       }
348    NYD2_OU;
349    return rv;
350 }
351 
352 static s32
a_sendout_body(FILE * fo,FILE * fi,enum conversion convert)353 a_sendout_body(FILE *fo, FILE *fi, enum conversion convert){
354    struct str outrest, inrest;
355    boole iseof, seenempty;
356    char *buf;
357    uz size, bufsize, cnt;
358    s32 rv;
359    NYD2_IN;
360 
361    rv = su_ERR_INVAL;
362    mx_fs_linepool_aquire(&buf, &bufsize);
363    outrest.s = inrest.s = NIL;
364    outrest.l = inrest.l = 0;
365 
366    if(convert == CONV_TOQP || convert == CONV_8BIT || convert == CONV_7BIT
367 #ifdef mx_HAVE_ICONV
368          || iconvd != (iconv_t)-1
369 #endif
370    ){
371       fflush(fi);
372       cnt = fsize(fi);
373    }
374 
375    seenempty = iseof = FAL0;
376    while(!iseof){
377       if(convert == CONV_TOQP || convert == CONV_8BIT || convert == CONV_7BIT
378 #ifdef mx_HAVE_ICONV
379             || iconvd != (iconv_t)-1
380 #endif
381       ){
382          if(fgetline(&buf, &bufsize, &cnt, &size, fi, FAL0) == NIL)
383             break;
384          if(convert != CONV_TOQP && seenempty && is_head(buf, size, FAL0)){
385             if(bufsize - 1 >= size + 1){
386                bufsize += 32;
387                buf = su_MEM_REALLOC(buf, bufsize);
388             }
389             su_mem_move(&buf[1], &buf[0], ++size);
390             buf[0] = '>';
391             seenempty = FAL0;
392          }else
393             seenempty = (size == 1 /*&& buf[0] == '\n'*/);
394       }else if((size = fread(buf, sizeof *buf, bufsize, fi)) == 0)
395          break;
396 joutln:
397       if(xmime_write(buf, size, fo, convert, TD_ICONV, &outrest,
398             (iseof > FAL0 ? NULL : &inrest)) < 0)
399          goto jleave;
400    }
401    if(iseof <= FAL0 && (outrest.l != 0 || inrest.l != 0)){
402       size = 0;
403       iseof = (iseof || inrest.l == 0) ? TRU1 : TRUM1;
404       goto joutln;
405    }
406 
407    rv = ferror(fi) ? su_ERR_IO : su_ERR_NONE;
408 jleave:
409    if(outrest.s != NIL)
410       su_FREE(outrest.s);
411    if(inrest.s != NIL)
412       su_FREE(inrest.s);
413    mx_fs_linepool_release(buf, bufsize);
414 
415    NYD2_OU;
416    return rv;
417 }
418 
419 static s32
a_sendout_attach_file(struct header * hp,struct mx_attachment * ap,FILE * fo,boole force)420 a_sendout_attach_file(struct header *hp, struct mx_attachment *ap, FILE *fo,
421    boole force)
422 {
423    /* TODO of course, the MIME classification needs to performed once
424     * TODO only, not for each and every charset anew ... ;-// */
425    char *charset_iter_orig[2];
426    boole any;
427    long offs;
428    s32 err;
429    NYD_IN;
430 
431    err = su_ERR_NONE;
432 
433    /* Is this already in target charset?  Simply copy over */
434    if(ap->a_conv == mx_ATTACHMENTS_CONV_TMPFILE){
435       err = a_sendout__attach_file(hp, ap, fo, force);
436       mx_fs_close(ap->a_tmpf);
437       su_DBG( ap->a_tmpf = NIL; )
438       goto jleave;
439    }
440 
441    /* If we don't apply charset conversion at all (fixed input=output charset)
442     * we also simply copy over, since it's the users desire */
443    if (ap->a_conv == mx_ATTACHMENTS_CONV_FIX_INCS) {
444       ap->a_charset = ap->a_input_charset;
445       err = a_sendout__attach_file(hp, ap, fo, force);
446       goto jleave;
447    } else
448       ASSERT(ap->a_input_charset != NULL);
449 
450    /* Otherwise we need to iterate over all possible output charsets */
451    if ((offs = ftell(fo)) == -1) {
452       err = su_ERR_IO;
453       goto jleave;
454    }
455    charset_iter_recurse(charset_iter_orig);
456    for(any = FAL0, charset_iter_reset(NULL);; any = TRU1, charset_iter_next()){
457       boole myforce;
458 
459       myforce = FAL0;
460       if (!charset_iter_is_valid()) {
461          if(!any || !(myforce = force)){
462             err = su_ERR_NOENT;
463             break;
464          }
465       }
466       err = a_sendout__attach_file(hp, ap, fo, myforce);
467       if (err == su_ERR_NONE || (err != su_ERR_ILSEQ && err != su_ERR_INVAL))
468          break;
469       clearerr(fo);
470       if (fseek(fo, offs, SEEK_SET) == -1) {
471          err = su_ERR_IO;
472          break;
473       }
474       if (ap->a_conv != mx_ATTACHMENTS_CONV_DEFAULT) {
475          err = su_ERR_ILSEQ;
476          break;
477       }
478       ap->a_charset = NULL;
479    }
480    charset_iter_restore(charset_iter_orig);
481 jleave:
482    NYD_OU;
483    return err;
484 }
485 
486 static s32
a_sendout__attach_file(struct header * hp,struct mx_attachment * ap,FILE * fo,boole force)487 a_sendout__attach_file(struct header *hp, struct mx_attachment *ap, FILE *fo,
488    boole force)
489 {
490    FILE *fi;
491    char const *charset;
492    enum conversion convert;
493    boole do_iconv;
494    s32 err;
495    NYD_IN;
496 
497    err = su_ERR_NONE;
498 
499    /* Either charset-converted temporary file, or plain path */
500    if(ap->a_conv == mx_ATTACHMENTS_CONV_TMPFILE){
501       fi = ap->a_tmpf;
502       ASSERT(ftell(fi) == 0);
503    }else if((fi = mx_fs_open(ap->a_path, "r")) == NIL){
504       err = su_err_no();
505       n_err(_("%s: %s\n"), n_shexp_quote_cp(ap->a_path, FAL0),
506          su_err_doc(err));
507       goto jleave;
508    }
509 
510    /* MIME part header for attachment */
511    {  char const *ct, *cp;
512 
513       /* No MBOXO quoting here, never!! */
514       ct = ap->a_content_type;
515       charset = ap->a_charset;
516       convert = mx_mimetype_classify_file(fi, (char const**)&ct,
517             &charset, &do_iconv, TRU1);
518 
519       if(charset == NIL || ap->a_conv == mx_ATTACHMENTS_CONV_FIX_INCS ||
520             ap->a_conv == mx_ATTACHMENTS_CONV_TMPFILE)
521          do_iconv = FAL0;
522 
523       if(force && do_iconv){
524          convert = CONV_TOB64;
525          ap->a_content_type = ct = "application/octet-stream";
526          ap->a_charset = charset = NULL;
527          do_iconv = FAL0;
528       }
529 
530       if (fprintf(fo, "\n--%s\n", _sendout_boundary) < 0 ||
531             a_sendout_put_ct(fo, ct, charset) < 0 ||
532             a_sendout_put_cte(fo, convert) < 0 ||
533             a_sendout_put_cd(fo, ap->a_content_disposition, ap->a_name) < 0)
534          goto jerr_header;
535 
536       if((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")){
537          struct mx_name *np;
538 
539          /* TODO RFC 2046 specifies that the same Content-ID should be used
540           * TODO for identical data; this is too hard for use right now,
541           * TODO because if done right it should be checksum based!?! */
542          if((np = ap->a_content_id) != NULL)
543             cp = np->n_name;
544          else
545             cp = a_sendout_random_id(hp, FAL0);
546 
547          if(cp != NULL && fprintf(fo, "Content-ID: <%s>\n", cp) < 0)
548             goto jerr_header;
549       }
550 
551       if ((cp = ap->a_content_description) != NULL &&
552             (fputs("Content-Description: ", fo) == EOF ||
553              xmime_write(cp, su_cs_len(cp), fo, CONV_TOHDR,
554                (TD_ISPR | TD_ICONV), NULL, NULL) < 0 || putc('\n', fo) == EOF))
555          goto jerr_header;
556 
557       if (putc('\n', fo) == EOF) {
558 jerr_header:
559          err = su_err_no();
560          goto jerr_fclose;
561       }
562    }
563 
564 #ifdef mx_HAVE_ICONV
565    if (iconvd != (iconv_t)-1)
566       n_iconv_close(iconvd);
567    if (do_iconv) {
568       /* Do not avoid things like utf-8 -> utf-8 to be able to detect encoding
569        * errors XXX also this should be !iconv_is_same_charset(), and THAT.. */
570       if (/*su_cs_cmp_case(charset, ap->a_input_charset) &&*/
571             (iconvd = n_iconv_open(charset, ap->a_input_charset)
572                ) == (iconv_t)-1 && (err = su_err_no()) != 0) {
573          if (err == su_ERR_INVAL)
574             n_err(_("Cannot convert from %s to %s\n"), ap->a_input_charset,
575                charset);
576          else
577             n_err(_("iconv_open: %s\n"), su_err_doc(err));
578          goto jerr_fclose;
579       }
580    }
581 #endif
582 
583    err = a_sendout_body(fo, fi, convert);
584 jerr_fclose:
585    if(ap->a_conv != mx_ATTACHMENTS_CONV_TMPFILE)
586       mx_fs_close(fi);
587 
588 jleave:
589    NYD_OU;
590    return err;
591 }
592 
593 static boole
_sendbundle_setup_creds(struct sendbundle * sbp,boole signing_caps)594 _sendbundle_setup_creds(struct sendbundle *sbp, boole signing_caps)
595 {
596    boole v15, rv = FAL0;
597    char *shost, *from;
598    NYD_IN;
599 
600    v15 = (ok_vlook(v15_compat) != su_NIL);
601    shost = (v15 ? ok_vlook(smtp_hostname) : NULL);
602    from = ((signing_caps || !v15 || shost == NULL)
603          ? skin(myorigin(sbp->sb_hp)) : NULL);
604 
605    if (signing_caps) {
606       if (from == NULL) {
607 #ifdef mx_HAVE_SMIME
608          n_err(_("No *from* address for signing specified\n"));
609          goto jleave;
610 #endif
611       } else
612          sbp->sb_signer.l = su_cs_len(sbp->sb_signer.s = from);
613    }
614 
615    /* file:// and test:// MTAs do not need credentials */
616    if(sbp->sb_urlp->url_cproto == CPROTO_NONE){
617       rv = TRU1;
618       goto jleave;
619    }
620 
621 #ifdef mx_HAVE_SMTP
622    if (v15) {
623       if (shost == NULL) {
624          if (from == NULL)
625             goto jenofrom;
626          sbp->sb_urlp->url_u_h.l = su_cs_len(sbp->sb_urlp->url_u_h.s = from);
627       } else
628          __sendout_ident = sbp->sb_urlp->url_u_h.s;
629       if(!mx_cred_auth_lookup(sbp->sb_credp, sbp->sb_urlp))
630          goto jleave;
631    }else{
632       if((sbp->sb_urlp->url_flags & mx_URL_HAD_USER) ||
633             sbp->sb_urlp->url_pass.s != NULL){
634          n_err(_("New-style URL used without *v15-compat* being set\n"));
635          goto jleave;
636       }
637       /* TODO part of the entire myorigin() disaster, get rid of this! */
638       if (from == NULL) {
639 jenofrom:
640          n_err(_("Your configuration requires a *from* address, "
641             "but none was given\n"));
642          goto jleave;
643       }
644       if(!mx_cred_auth_lookup_old(sbp->sb_credp, CPROTO_SMTP, from))
645          goto jleave;
646       sbp->sb_urlp->url_u_h.l = su_cs_len(sbp->sb_urlp->url_u_h.s = from);
647    }
648 
649    rv = TRU1;
650 #endif /* mx_HAVE_SMTP */
651 
652 jleave:
653    NYD_OU;
654    return rv;
655 }
656 
657 static s32
a_sendout_attach_msg(struct header * hp,struct mx_attachment * ap,FILE * fo)658 a_sendout_attach_msg(struct header *hp, struct mx_attachment *ap, FILE *fo)
659 {
660    struct message *mp;
661    char const *ccp;
662    s32 err;
663    NYD_IN;
664    UNUSED(hp);
665 
666    err = su_ERR_NONE;
667 
668    if(fprintf(fo, "\n--%s\nContent-Type: message/rfc822\n"
669          "Content-Disposition: inline\n", _sendout_boundary) < 0)
670       goto jerr;
671 
672    if((ccp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(ccp, "noagent")){
673       struct mx_name *np;
674 
675       /* TODO RFC 2046 specifies that the same Content-ID should be used
676        * TODO for identical data; this is too hard for use right now,
677        * TODO because if done right it should be checksum based!?! */
678       if((np = ap->a_content_id) != NULL)
679          ccp = np->n_name;
680       else
681          ccp = a_sendout_random_id(hp, FAL0);
682 
683       if(ccp != NULL && fprintf(fo, "Content-ID: <%s>\n", ccp) < 0)
684          goto jerr;
685    }
686 
687    if((ccp = ap->a_content_description) != NULL &&
688          (fputs("Content-Description: ", fo) == EOF ||
689           xmime_write(ccp, su_cs_len(ccp), fo, CONV_TOHDR,
690             (TD_ISPR | TD_ICONV), NULL, NULL) < 0 || putc('\n', fo) == EOF))
691       goto jerr;
692    if(putc('\n', fo) == EOF)
693       goto jerr;
694 
695    mp = message + ap->a_msgno - 1;
696    touch(mp);
697    if(sendmp(mp, fo, 0, NULL, SEND_RFC822, NULL) < 0)
698 jerr:
699       if((err = su_err_no()) == su_ERR_NONE)
700          err = su_ERR_IO;
701    NYD_OU;
702    return err;
703 }
704 
705 static s32
make_multipart(struct header * hp,int convert,FILE * fi,FILE * fo,char const * contenttype,char const * charset,boole force)706 make_multipart(struct header *hp, int convert, FILE *fi, FILE *fo,
707    char const *contenttype, char const *charset, boole force)
708 {
709    struct mx_attachment *att;
710    s32 err;
711    NYD_IN;
712 
713    err = su_ERR_NONE;
714 
715    if(fputs("This is a multi-part message in MIME format.\n", fo) == EOF)
716       goto jerr;
717 
718    if(fsize(fi) != 0){
719       char const *cp;
720 
721       if(fprintf(fo, "\n--%s\n", _sendout_boundary) < 0 ||
722             a_sendout_put_ct(fo, contenttype, charset) < 0 ||
723             a_sendout_put_cte(fo, convert) < 0 ||
724             fprintf(fo, "Content-Disposition: inline\n") < 0)
725          goto jerr;
726       if (((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")) &&
727             (cp = a_sendout_random_id(hp, FAL0)) != NULL &&
728             fprintf(fo, "Content-ID: <%s>\n", cp) < 0)
729          goto jerr;
730       if(putc('\n', fo) == EOF)
731          goto jerr;
732 
733       if((err = a_sendout_body(fo, fi, convert)) != su_ERR_NONE)
734          goto jleave;
735 
736       if(ferror(fi))
737          goto jerr;
738    }
739 
740    for (att = hp->h_attach; att != NULL; att = att->a_flink) {
741       if (att->a_msgno) {
742          if ((err = a_sendout_attach_msg(hp, att, fo)) != su_ERR_NONE)
743             goto jleave;
744       }else if((err = a_sendout_attach_file(hp, att, fo, force)
745             ) != su_ERR_NONE)
746          goto jleave;
747    }
748 
749    /* the final boundary with two attached dashes */
750    if(fprintf(fo, "\n--%s--\n", _sendout_boundary) < 0)
751 jerr:
752       if((err = su_err_no()) == su_ERR_NONE)
753          err = su_ERR_IO;
754 jleave:
755    NYD_OU;
756    return err;
757 }
758 
759 static FILE *
a_sendout_infix(struct header * hp,FILE * fi,boole dosign,boole force)760 a_sendout_infix(struct header *hp, FILE *fi, boole dosign, boole force)
761 {
762    struct mx_fs_tmp_ctx *fstcp;
763    enum conversion convert;
764    int err;
765    boole do_iconv;
766    char const *contenttype, *charset;
767    FILE *nfo, *nfi;
768 #ifdef mx_HAVE_ICONV
769    char const *tcs, *convhdr = NULL;
770 #endif
771    NYD_IN;
772 
773    nfi = NULL;
774    charset = NULL;
775    do_iconv = FAL0;
776    err = su_ERR_NONE;
777 
778    if((nfo = mx_fs_tmp_open("infix", (mx_FS_O_WRONLY | mx_FS_O_HOLDSIGS |
779             mx_FS_O_REGISTER), &fstcp)) == NIL){
780       n_perr(_("infix: temporary mail file"), err = su_err_no());
781       goto jleave;
782    }
783 
784    if((nfi = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
785       n_perr(fstcp->fstc_filename, err = su_err_no());
786       mx_fs_close(nfo);
787    }
788 
789    mx_fs_tmp_release(fstcp);
790 
791    if(nfi == NIL)
792       goto jleave;
793 
794    n_pstate &= ~n_PS_HEADER_NEEDED_MIME; /* TODO hack -> be carrier tracked */
795 
796    /* C99 */{
797       boole no_mboxo;
798 
799       no_mboxo = dosign;
800 
801       /* Will be NULL for text/plain */
802       if((n_poption & n_PO_Mm_FLAG) && n_poption_arg_Mm != NULL){
803          contenttype = n_poption_arg_Mm;
804          no_mboxo = TRU1;
805       }else
806          contenttype = "text/plain";
807 
808       convert = mx_mimetype_classify_file(fi, &contenttype, &charset,
809             &do_iconv, no_mboxo);
810    }
811 
812 #ifdef mx_HAVE_ICONV
813    /* This is the logic behind *charset-force-transport*.  XXX very weird
814     * XXX as said a thousand times, Part==object, has dump_to_{wire,user}, and
815     * XXX does it (including _force_) for _itself_ only; the global header
816     * XXX has then to become spliced in (for multipart messages) */
817    if(force && do_iconv){
818       convert = CONV_TOB64;
819       contenttype = "application/octet-stream";
820       charset = NULL;
821       do_iconv = FAL0;
822    }
823 
824    tcs = ok_vlook(ttycharset);
825 
826    if((convhdr = need_hdrconv(hp))){
827       if(iconvd != (iconv_t)-1) /* XXX  */
828          n_iconv_close(iconvd);
829       /* Do not avoid things like utf-8 -> utf-8 to be able to detect encoding
830        * errors XXX also this should be !iconv_is_same_charset(), and THAT.. */
831       if(/*su_cs_cmp_case(convhdr, tcs) != 0 &&*/
832             (iconvd = n_iconv_open(convhdr, tcs)) == (iconv_t)-1 &&
833             (err = su_err_no()) != su_ERR_NONE)
834          goto jiconv_err;
835    }
836 #endif
837    if(!n_puthead(FAL0, hp, nfo,
838          (GTO | GSUBJECT | GCC | GBCC | GNL | GCOMMA | GUA | GMIME | GMSGID |
839          GIDENT | GREF | GDATE), SEND_MBOX, convert, contenttype, charset)){
840       if((err = su_err_no()) == su_ERR_NONE)
841          err = su_ERR_IO;
842       goto jerr;
843    }
844 #ifdef mx_HAVE_ICONV
845    if (iconvd != (iconv_t)-1)
846       n_iconv_close(iconvd);
847 #endif
848 
849 #ifdef mx_HAVE_ICONV
850    if(do_iconv && charset != NIL){ /*TODO charset->mimetype_classify_file*/
851       /* Do not avoid things like utf-8 -> utf-8 to be able to detect encoding
852        * errors XXX also this should be !iconv_is_same_charset(), and THAT.. */
853       if(/*su_cs_cmp_case(charset, tcs) != 0 &&*/
854             (iconvd = n_iconv_open(charset, tcs)) == (iconv_t)-1 &&
855             (err = su_err_no()) != su_ERR_NONE){
856 jiconv_err:
857          if (err == su_ERR_INVAL)
858             n_err(_("Cannot convert from %s to %s\n"), tcs, charset);
859          else
860             n_perr("iconv_open", err);
861          goto jerr;
862       }
863    }
864 #endif
865 
866    if(hp->h_attach != NULL){
867       if((err = make_multipart(hp, convert, fi, nfo, contenttype, charset,
868             force)) != su_ERR_NONE)
869          goto jerr;
870    }else if((err = a_sendout_body(nfo, fi, convert)) != su_ERR_NONE)
871       goto jerr;
872 
873    if(fflush(nfo) == EOF)
874       err = su_err_no();
875 jerr:
876    mx_fs_close(nfo);
877 
878    if(err == su_ERR_NONE){
879       fflush_rewind(nfi);
880       mx_fs_close(fi);
881    }else{
882       mx_fs_close(nfi);
883       nfi = NIL;
884    }
885 
886 jleave:
887 #ifdef mx_HAVE_ICONV
888    if(iconvd != (iconv_t)-1)
889       n_iconv_close(iconvd);
890 #endif
891    if(nfi == NULL)
892       su_err_set_no(err);
893    NYD_OU;
894    return nfi;
895 }
896 
897 static boole
_check_dispo_notif(struct mx_name * mdn,struct header * hp,FILE * fo)898 _check_dispo_notif(struct mx_name *mdn, struct header *hp, FILE *fo)
899 {
900    char const *from;
901    boole rv = TRU1;
902    NYD_IN;
903 
904    /* TODO smtp_disposition_notification (RFC 3798): relation to return-path
905     * TODO not yet checked */
906    if (!ok_blook(disposition_notification_send))
907       goto jleave;
908 
909    if (mdn != NULL && mdn != (struct mx_name*)0x1)
910       from = mdn->n_name;
911    else if ((from = myorigin(hp)) == NULL) {
912       if (n_poption & n_PO_D_V)
913          n_err(_("*disposition-notification-send*: *from* not set\n"));
914       goto jleave;
915    }
916 
917    if (!a_sendout_put_addrline("Disposition-Notification-To:",
918          nalloc(n_UNCONST(from), 0), fo, 0))
919       rv = FAL0;
920 jleave:
921    NYD_OU;
922    return rv;
923 }
924 
925 static int
a_sendout_sendmail(void * v,enum n_mailsend_flags msf)926 a_sendout_sendmail(void *v, enum n_mailsend_flags msf)
927 {
928    struct header head;
929    char *str = v;
930    int rv;
931    NYD_IN;
932 
933    su_mem_set(&head, 0, sizeof head);
934    head.h_mailx_command = "mail";
935    if((head.h_to = lextract(str, GTO |
936          (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN))) != NULL)
937       head.h_mailx_raw_to = n_namelist_dup(head.h_to, head.h_to->n_type);
938    rv = n_mail1(msf, &head, NULL, NULL);
939    NYD_OU;
940    return (rv != OKAY); /* reverse! */
941 }
942 
943 static struct mx_name *
a_sendout_file_a_pipe(struct mx_name * names,FILE * fo,boole * senderror)944 a_sendout_file_a_pipe(struct mx_name *names, FILE *fo, boole *senderror){
945    boole mfap;
946    char const *sh;
947    u32 pipecnt, xcnt, i, swf;
948    struct mx_name *np;
949    FILE *fp, **fppa;
950    NYD_IN;
951 
952    fp = NIL;
953    fppa = NIL;
954 
955    /* Look through all recipients and do a quick return if no file or pipe
956     * addressee is found */
957    for(pipecnt = xcnt = 0, np = names; np != NIL; np = np->n_flink){
958       if(np->n_type & GDEL)
959          continue;
960       switch(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE){
961       case mx_NAME_ADDRSPEC_ISFILE: ++xcnt; break;
962       case mx_NAME_ADDRSPEC_ISPIPE: ++pipecnt; break;
963       }
964    }
965    if((pipecnt | xcnt) == 0)
966       goto jleave;
967 
968    /* Otherwise create an array of file descriptors for each found pipe
969     * addressee to get around the dup(2)-shared-file-offset problem, i.e.,
970     * each pipe subprocess needs its very own file descriptor, and we need
971     * to deal with that.
972     * This is true even if *sendwait* requires fully synchronous mode, since
973     * the shell handlers can fork away and pass the descriptor around, so we
974     * cannot simply use a single one and rewind that after the top children
975     * shell has returned.
976     * To make our life a bit easier let's just use the auto-reclaimed
977     * string storage */
978    if(pipecnt == 0 || (n_poption & n_PO_D)){
979       pipecnt = 0;
980       sh = NIL;
981    }else{
982       i = sizeof(FILE*) * pipecnt;
983       fppa = n_lofi_alloc(i);
984       su_mem_set(fppa, 0, i);
985 
986       sh = ok_vlook(SHELL);
987    }
988 
989    mfap = ok_blook(mbox_fcc_and_pcc);
990    swf = a_sendout_sendwait_to_swf();
991 
992    for(np = names; np != NIL; np = np->n_flink){
993       if(!(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE) || (np->n_type & GDEL))
994          continue;
995 
996       /* In days of old we removed the entry from the the list; now for sake of
997        * header expansion we leave it in and mark it as deleted */
998       np->n_type |= GDEL;
999 
1000       if(n_poption & n_PO_D_VV)
1001          n_err(_(">>> Writing message via %s\n"),
1002             n_shexp_quote_cp(np->n_name, FAL0));
1003       /* We _do_ write to STDOUT, anyway! */
1004       if((n_poption & n_PO_D) &&
1005             ((np->n_flags & mx_NAME_ADDRSPEC_ISPIPE) ||
1006                np->n_name[0] != '-' || np->n_name[1] != '\0'))
1007          continue;
1008 
1009       /* See if we have copied the complete message out yet.  If not, do so */
1010       if(fp == NIL){
1011          int c;
1012          struct mx_fs_tmp_ctx *fstcp;
1013 
1014          if((fp = mx_fs_tmp_open("outof", (mx_FS_O_RDWR | mx_FS_O_HOLDSIGS |
1015                   mx_FS_O_REGISTER), &fstcp)) == NIL){
1016             n_perr(_("Creation of temporary image"), 0);
1017             pipecnt = 0;
1018             goto jerror;
1019          }
1020 
1021          for(i = 0; i < pipecnt; ++i)
1022             if((fppa[i] = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
1023                n_perr(_("Creation of pipe image descriptor"), 0);
1024                break;
1025             }
1026 
1027          mx_fs_tmp_release(fstcp);
1028 
1029          if(i != pipecnt){
1030             pipecnt = i;
1031             goto jerror;
1032          }
1033 
1034          if(mfap)
1035             fprintf(fp, "From %s %s",
1036                ok_vlook(LOGNAME), time_current.tc_ctime);
1037          c = EOF;
1038          while(i = c, (c = getc(fo)) != EOF)
1039             putc(c, fp);
1040          rewind(fo);
1041          if((int)i != '\n')
1042             putc('\n', fp);
1043          if(mfap)
1044             putc('\n', fp);
1045          fflush(fp);
1046          if(ferror(fp)){
1047             n_perr(_("Finalizing write of temporary image"), 0);
1048             goto jerror;
1049          }
1050 
1051          /* From now on use xcnt as a counter for pipecnt */
1052          xcnt = 0;
1053       }
1054 
1055       /* Now either copy "image" to the desired file or give it as the
1056        * standard input to the desired program as appropriate */
1057       if(np->n_flags & mx_NAME_ADDRSPEC_ISPIPE){
1058          struct mx_child_ctx cc;
1059          sigset_t nset;
1060 
1061          sigemptyset(&nset);
1062          sigaddset(&nset, SIGHUP);
1063          sigaddset(&nset, SIGINT);
1064          sigaddset(&nset, SIGQUIT);
1065 
1066          mx_child_ctx_setup(&cc);
1067          cc.cc_flags = mx_CHILD_SPAWN_CONTROL |
1068                (swf & a_SENDOUT_SWF_PCC ? mx_CHILD_RUN_WAIT_LIFE : 0);
1069          cc.cc_mask = &nset;
1070          cc.cc_fds[mx_CHILD_FD_IN] = fileno(fppa[xcnt]);
1071          cc.cc_fds[mx_CHILD_FD_OUT] = mx_CHILD_FD_NULL;
1072          cc.cc_cmd = sh;
1073          cc.cc_args[0] = "-c";
1074          cc.cc_args[1] = &np->n_name[1];
1075 
1076          if(!mx_child_run(&cc)){
1077             n_err(_("Piping message to %s failed\n"),
1078                n_shexp_quote_cp(np->n_name, FAL0));
1079             goto jerror;
1080          }
1081 
1082          /* C99 */{
1083             FILE *tmp;
1084 
1085             tmp = fppa[xcnt];
1086             fppa[xcnt++] = NIL;
1087             mx_fs_close(tmp);
1088          }
1089 
1090          if(!(swf & a_SENDOUT_SWF_PCC))
1091             mx_child_forget(&cc);
1092       }else{
1093          int c;
1094          FILE *fout;
1095          char const *fname, *fnameq;
1096 
1097          if((fname = fexpand(np->n_name, FEXP_NSHELL)) == NIL) /* TODO */
1098             goto jerror;
1099          fnameq = n_shexp_quote_cp(fname, FAL0);
1100 
1101          if(fname[0] == '-' && fname[1] == '\0')
1102             fout = n_stdout;
1103          else{
1104             int xerr;
1105             enum mx_fs_open_state fs;
1106 
1107             if((fout = mx_fs_open_any(fname, (mfap ? "a+" : "w"), &fs)
1108                   ) == NIL){
1109                xerr = su_err_no();
1110 jefile:
1111                n_err(_("Writing message to %s failed: %s\n"),
1112                   fnameq, su_err_doc(xerr));
1113                goto jerror;
1114             }
1115 
1116             if((fs & (n_PROTO_MASK | mx_FS_OPEN_STATE_EXISTS)) ==
1117                   (n_PROTO_FILE | mx_FS_OPEN_STATE_EXISTS)){
1118                mx_file_lock(fileno(fout), mx_FILE_LOCK_TYPE_WRITE, 0,0,
1119                      UZ_MAX);
1120 
1121                if(mfap && (xerr = n_folder_mbox_prepare_append(fout, NIL)
1122                      ) != su_ERR_NONE)
1123                   goto jefile;
1124             }
1125          }
1126 
1127          rewind(fp);
1128          while((c = getc(fp)) != EOF)
1129             putc(c, fout);
1130          if(ferror(fout)){
1131             n_err(_("Writing message to %s failed: %s\n"),
1132                fnameq, _("write error"));
1133             *senderror = TRU1;
1134          }
1135 
1136          if(fout != n_stdout)
1137             mx_fs_close(fout);
1138          else
1139             clearerr(fout);
1140       }
1141    }
1142 
1143 jleave:
1144    if(fp != NIL)
1145       mx_fs_close(fp);
1146 
1147    if(fppa != NIL){
1148       for(i = 0; i < pipecnt; ++i)
1149          if((fp = fppa[i]) != NIL)
1150             mx_fs_close(fp);
1151       n_lofi_free(fppa);
1152    }
1153 
1154    NYD_OU;
1155    return names;
1156 
1157 jerror:
1158    *senderror = TRU1;
1159    while(np != NIL){
1160       if(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE)
1161          np->n_type |= GDEL;
1162       np = np->n_flink;
1163    }
1164    goto jleave;
1165 }
1166 
1167 static boole
a_sendout_mightrecord(FILE * fp,struct mx_name * to,boole resend)1168 a_sendout_mightrecord(FILE *fp, struct mx_name *to, boole resend){
1169    char const *ccp;
1170    char *cp;
1171    boole rv;
1172    NYD2_IN;
1173 
1174    rv = TRU1;
1175 
1176    if(to != NIL){
1177       ccp = cp = savestr(to->n_name);
1178       while(*cp != '\0' && *cp != '@')
1179          ++cp;
1180       *cp = '\0';
1181    }else
1182       ccp = ok_vlook(record);
1183 
1184    if(ccp == NIL)
1185       goto jleave;
1186 
1187    if((cp = fexpand(ccp, FEXP_NSHELL)) == NIL) /* TODO */
1188       goto jbail;
1189 
1190    switch(which_protocol(ccp, FAL0, FAL0, NIL)){
1191    case n_PROTO_EML:
1192    case n_PROTO_POP3:
1193    case n_PROTO_UNKNOWN:
1194       goto jbail;
1195    default:
1196       break;
1197    }
1198 
1199    switch(*(ccp = cp)){
1200    case '.':
1201       if(cp[1] != '/'){ /* DIRSEP */
1202    default:
1203          if(ok_blook(outfolder)){
1204             struct str s;
1205             char const *nccp, *folder;
1206 
1207             switch(which_protocol(ccp, TRU1, FAL0, &nccp)){
1208             case PROTO_FILE:
1209                ccp = "file://";
1210                if(0){
1211                /* FALLTHRU */
1212             case PROTO_MAILDIR:
1213 #ifdef mx_HAVE_MAILDIR
1214                   ccp = "maildir://";
1215 #else
1216                   n_err(_("*record*: *outfolder*: no Maildir directory "
1217                      "support compiled in\n"));
1218                   goto jbail;
1219 #endif
1220                }
1221                folder = n_folder_query();
1222 #ifdef mx_HAVE_IMAP
1223                if(which_protocol(folder, FAL0, FAL0, NIL) == PROTO_IMAP){
1224                   n_err(_("*record*: *outfolder* set, *folder* is IMAP "
1225                      "based: only one protocol per file is possible\n"));
1226                   goto jbail;
1227                }
1228 #endif
1229                ccp = str_concat_csvl(&s, ccp, folder, nccp, NIL)->s;
1230                /* FALLTHRU */
1231             case n_PROTO_IMAP:
1232                break;
1233             default:
1234                goto jbail;
1235             }
1236          }
1237       }
1238       /* FALLTHRU */
1239    case '/':
1240       break;
1241    }
1242 
1243    if(n_poption & n_PO_D_VV)
1244       n_err(_(">>> Writing message via %s\n"), n_shexp_quote_cp(ccp, FAL0));
1245 
1246    if(!(n_poption & n_PO_D) && !a_sendout__savemail(ccp, fp, resend)){
1247 jbail:
1248       n_err(_("Failed to save message in %s - message not sent\n"),
1249          n_shexp_quote_cp(ccp, FAL0));
1250       n_exit_status |= n_EXIT_ERR;
1251       savedeadletter(fp, 1);
1252       rv = FAL0;
1253    }
1254 
1255 jleave:
1256    NYD2_OU;
1257    return rv;
1258 }
1259 
1260 static boole
a_sendout__savemail(char const * name,FILE * fp,boole resend)1261 a_sendout__savemail(char const *name, FILE *fp, boole resend){
1262    FILE *fo;
1263    uz bufsize, buflen, cnt;
1264    enum mx_fs_open_state fs;
1265    char *buf;
1266    boole rv, emptyline;
1267    NYD_IN;
1268    UNUSED(resend);
1269 
1270    rv = FAL0;
1271    mx_fs_linepool_aquire(&buf, &bufsize);
1272 
1273    if((fo = mx_fs_open_any(name, "a+", &fs)) == NIL){
1274       n_perr(name, 0);
1275       goto j_leave;
1276    }
1277 
1278    if((fs & (n_PROTO_MASK | mx_FS_OPEN_STATE_EXISTS)) ==
1279          (n_PROTO_FILE | mx_FS_OPEN_STATE_EXISTS)){
1280       int xerr;
1281 
1282       /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()!
1283        * TODO BETTER yet: should be returned in lock state already! */
1284       mx_file_lock(fileno(fo), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);
1285 
1286       if((xerr = n_folder_mbox_prepare_append(fo, NULL)) != su_ERR_NONE){
1287          n_perr(name, xerr);
1288          goto jleave;
1289       }
1290    }
1291 
1292    if(fprintf(fo, "From %s %s", ok_vlook(LOGNAME), time_current.tc_ctime) < 0)
1293       goto jleave;
1294 
1295    rv = TRU1;
1296 
1297    fflush_rewind(fp);
1298    for(emptyline = FAL0, buflen = 0, cnt = fsize(fp);
1299          fgetline(&buf, &bufsize, &cnt, &buflen, fp, FAL0) != NIL;){
1300       /* Only if we are resending it can happen that we have to quote From_
1301        * lines here; we don't generate messages which are ambiguous ourselves.
1302        * xxx No longer true after (Reintroduce ^From_ MBOXO with
1303        * xxx *mime-encoding*=8b (too many!)[..]) */
1304       /*if(resend){*/
1305          if(emptyline && is_head(buf, buflen, FAL0)){
1306             if(putc('>', fo) == EOF){
1307                rv = FAL0;
1308                break;
1309             }
1310          }
1311       /*}su_DBG(else ASSERT(!is_head(buf, buflen, FAL0)); )*/
1312 
1313       emptyline = (buflen > 0 && *buf == '\n');
1314       if(fwrite(buf, sizeof *buf, buflen, fo) != buflen){
1315          rv = FAL0;
1316          break;
1317       }
1318    }
1319    if(rv){
1320       if(buflen > 0 && buf[buflen - 1] != '\n'){
1321          if(putc('\n', fo) == EOF)
1322             rv = FAL0;
1323       }
1324       if(rv && (putc('\n', fo) == EOF || fflush(fo)))
1325          rv = FAL0;
1326    }
1327 
1328    if(!rv){
1329       n_perr(name, su_ERR_IO);
1330       rv = FAL0;
1331    }
1332 
1333 jleave:
1334    really_rewind(fp);
1335    if(!mx_fs_close(fo))
1336       rv = FAL0;
1337 j_leave:
1338    mx_fs_linepool_release(buf, bufsize);
1339 
1340    NYD_OU;
1341    return rv;
1342 }
1343 
1344 static boole
a_sendout_transfer(struct sendbundle * sbp,boole resent,boole * senderror)1345 a_sendout_transfer(struct sendbundle *sbp, boole resent, boole *senderror){
1346    u32 cnt;
1347    struct mx_name *np;
1348    FILE *input_save;
1349    boole rv;
1350    NYD_IN;
1351 
1352    rv = FAL0;
1353 
1354    /* Do we need to create a Bcc: free overlay?
1355     * TODO In v15 we would have an object tree with dump-to-wire, we have our
1356     * TODO file stream which acts upon an I/O device that stores so-and-so-much
1357     * TODO memory, excess in a temporary file; either each object knows its
1358     * TODO offset where it placed its dump-to-wire, or we create a list overlay
1359     * TODO which records these offsets.  Then, in our non-blocking eventloop
1360     * TODO which writes data to the MTA child as it goes we simply not write
1361     * TODO the Bcc: as necessary; how about that? */
1362    input_save = sbp->sb_input;
1363    if((resent || (sbp->sb_hp != NIL && sbp->sb_hp->h_bcc != NIL)) &&
1364          !ok_blook(mta_bcc_ok)){
1365       boole inhdr, inskip;
1366       uz bufsize, bcnt, llen;
1367       char *buf;
1368       FILE *fp;
1369 
1370       if((fp = mx_fs_tmp_open("mtabccok", (mx_FS_O_RDWR | mx_FS_O_REGISTER |
1371                mx_FS_O_UNLINK), NIL)) == NIL){
1372 jewritebcc:
1373          n_perr(_("Creation of *mta-write-bcc* message"), 0);
1374          *senderror = TRU1;
1375          goto jleave;
1376       }
1377       sbp->sb_input = fp;
1378 
1379       mx_fs_linepool_aquire(&buf, &bufsize);
1380       bcnt = fsize(input_save);
1381       inhdr = TRU1;
1382       inskip = FAL0;
1383       while(fgetline(&buf, &bufsize, &bcnt, &llen, input_save, TRU1) != NIL){
1384          if(inhdr){
1385             if(llen == 1 && *buf == '\n')
1386                inhdr = FAL0;
1387             else{
1388                if(inskip && *buf == ' ')
1389                   continue;
1390                inskip = FAL0;
1391                /* (We need _case for resent only);
1392                 * xxx We do not resent that yet , but place the logic today */
1393                if(su_cs_starts_with_case(buf, "bcc:") ||
1394                      (resent && su_cs_starts_with_case(buf, "resent-bcc:"))){
1395                   inskip = TRU1;
1396                   continue;
1397                }
1398             }
1399          }
1400          if(fwrite(buf, 1, llen, fp) != llen)
1401             goto jewritebcc;
1402       }
1403       mx_fs_linepool_release(buf, bufsize);
1404 
1405       if(ferror(input_save)){
1406          *senderror = TRU1;
1407          goto jleave;
1408       }
1409       fflush_rewind(fp);
1410    }
1411 
1412    rv = TRU1;
1413 
1414    for(cnt = 0, np = sbp->sb_to; np != NIL;){
1415       FILE *ef;
1416 
1417       if((ef = mx_privacy_encrypt_try(sbp->sb_input, np->n_name)
1418             ) != R(FILE*,-1)){
1419          if(ef != NIL){
1420             struct mx_name *nsave;
1421             FILE *fisave;
1422 
1423             fisave = sbp->sb_input;
1424             nsave = sbp->sb_to;
1425 
1426             sbp->sb_to = ndup(np, np->n_type & ~(GFULL | GFULLEXTRA | GSKIN));
1427             sbp->sb_input = ef;
1428             if(!a_sendout_mta_start(sbp))
1429                rv = FAL0;
1430             sbp->sb_to = nsave;
1431             sbp->sb_input = fisave;
1432 
1433             mx_fs_close(ef);
1434          }else{
1435             n_err(_("Message not sent to: %s\n"), np->n_name);
1436             _sendout_error = TRU1;
1437          }
1438          rewind(sbp->sb_input);
1439 
1440          if(np->n_flink != NIL)
1441             np->n_flink->n_blink = np->n_blink;
1442          if(np->n_blink != NIL)
1443             np->n_blink->n_flink = np->n_flink;
1444          if(np == sbp->sb_to)
1445             sbp->sb_to = np->n_flink;
1446          np = np->n_flink;
1447       }else{
1448          ++cnt;
1449          np = np->n_flink;
1450       }
1451    }
1452 
1453    if(cnt > 0 && (mx_privacy_encrypt_is_forced() || !a_sendout_mta_start(sbp)))
1454       rv = FAL0;
1455 
1456 jleave:
1457    if(input_save != sbp->sb_input){
1458       mx_fs_close(sbp->sb_input);
1459       rewind(sbp->sb_input = input_save);
1460    }
1461 
1462    NYD_OU;
1463    return rv;
1464 }
1465 
1466 static boole
a_sendout_mta_start(struct sendbundle * sbp)1467 a_sendout_mta_start(struct sendbundle *sbp)
1468 {
1469    struct mx_child_ctx cc;
1470    sigset_t nset;
1471    char const **args, *mta;
1472    boole rv, dowait;
1473    NYD_IN;
1474 
1475    /* Let rv be: TRU1=SMTP, FAL0=file, TRUM1=test */
1476    mta = sbp->sb_urlp->url_input;
1477    if(sbp->sb_urlp->url_cproto == CPROTO_NONE){
1478       if(sbp->sb_urlp->url_portno == 0)
1479          rv = FAL0;
1480       else{
1481          ASSERT(sbp->sb_urlp->url_portno == U16_MAX);
1482          rv = TRUM1;
1483       }
1484    }else
1485       rv = TRU1;
1486 
1487    sigemptyset(&nset);
1488    sigaddset(&nset, SIGHUP);
1489    sigaddset(&nset, SIGINT);
1490    sigaddset(&nset, SIGQUIT);
1491    sigaddset(&nset, SIGTSTP);
1492    sigaddset(&nset, SIGTTIN);
1493    sigaddset(&nset, SIGTTOU);
1494    mx_child_ctx_setup(&cc);
1495    dowait = ((n_poption & n_PO_D_V) ||
1496          (a_sendout_sendwait_to_swf() & a_SENDOUT_SWF_MTA));
1497    if(rv != TRU1 || dowait)
1498       cc.cc_flags |= mx_CHILD_SPAWN_CONTROL;
1499    cc.cc_mask = &nset;
1500 
1501    if(rv != TRU1){
1502       char const *mta_base;
1503 
1504       if((mta = fexpand(mta_base = mta, (FEXP_NOPROTO | FEXP_LOCAL_FILE |
1505             FEXP_NSHELL))) == NIL){
1506          n_err(_("*mta* variable file expansion failure: %s\n"),
1507             n_shexp_quote_cp(mta_base, FAL0));
1508          goto jstop;
1509       }
1510 
1511       if(rv == TRUM1){
1512          rv = a_sendout_mta_test(sbp, mta);
1513          goto jleave;
1514       }
1515 
1516       args = a_sendout_mta_file_args(sbp->sb_to, sbp->sb_hp);
1517       if(n_poption & n_PO_D){
1518          a_sendout_mta_file_debug(sbp, mta, args);
1519          rv = TRU1;
1520          goto jleave;
1521       }
1522 
1523       /* Wait with control pipe close until after exec */
1524       ASSERT(cc.cc_flags & mx_CHILD_SPAWN_CONTROL);
1525       cc.cc_flags |= mx_CHILD_SPAWN_CONTROL_LINGER;
1526       cc.cc_fds[mx_CHILD_FD_IN] = fileno(sbp->sb_input);
1527    }else/* if(rv == TRU1)*/{
1528       UNINIT(args, NULL);
1529 #ifndef mx_HAVE_SMTP
1530       n_err(_("No SMTP support compiled in\n"));
1531       goto jstop;
1532 #else
1533       if(n_poption & n_PO_D){
1534          (void)mx_smtp_mta(sbp);
1535          rv = TRU1;
1536          goto jleave;
1537       }
1538 
1539       cc.cc_fds[mx_CHILD_FD_IN] = mx_CHILD_FD_NULL;
1540       cc.cc_fds[mx_CHILD_FD_OUT] = mx_CHILD_FD_NULL;
1541 #endif
1542    }
1543 
1544    /* Fork, set up the temporary mail file as standard input for "mail", and
1545     * exec with the user list we generated far above */
1546    if(!mx_child_fork(&cc)){
1547       if(cc.cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER){
1548          char const *ecp;
1549 
1550          ecp = (cc.cc_error != su_ERR_NOENT) ? su_err_doc(cc.cc_error)
1551                : _("executable not found (adjust *mta* variable)");
1552          n_err(_("Cannot start %s: %s\n"), n_shexp_quote_cp(mta, FAL0), ecp);
1553       }
1554 jstop:
1555       savedeadletter(sbp->sb_input, TRU1);
1556       n_err(_("... message not sent\n"));
1557       _sendout_error = TRU1;
1558    }else if(cc.cc_pid == 0)
1559       goto jkid;
1560    else if(dowait){
1561       /* TODO Now with SPAWN_CONTROL we could actually (1) handle $DEAD only
1562        * TODO in the parent, and (2) report the REAL child error status!! */
1563       rv = (mx_child_wait(&cc) && cc.cc_exit_status == 0);
1564       if(!rv)
1565          goto jstop;
1566    }else{
1567       mx_child_forget(&cc);
1568       rv = TRU1;
1569    }
1570 
1571 jleave:
1572    NYD_OU;
1573    return rv;
1574 
1575 jkid:
1576    mx_child_in_child_setup(&cc);
1577 
1578 #ifdef mx_HAVE_SMTP
1579    if(rv == TRU1){
1580       if(mx_smtp_mta(sbp))
1581          _exit(n_EXIT_OK);
1582       savedeadletter(sbp->sb_input, TRU1);
1583       if(!dowait)
1584          n_err(_("... message not sent\n"));
1585    }else
1586 #endif
1587         {
1588       execv(mta, n_UNCONST(args));
1589       mx_child_in_child_exec_failed(&cc, su_err_no());
1590    }
1591    for(;;)
1592       _exit(n_EXIT_ERR);
1593 }
1594 
1595 static char const **
a_sendout_mta_file_args(struct mx_name * to,struct header * hp)1596 a_sendout_mta_file_args(struct mx_name *to, struct header *hp)
1597 {
1598    uz vas_cnt, i, j;
1599    char **vas;
1600    char const **args, *cp, *cp_v15compat;
1601    boole snda;
1602    NYD_IN;
1603 
1604    if((cp_v15compat = ok_vlook(sendmail_arguments)) != NULL)
1605       n_OBSOLETE(_("please use *mta-arguments*, not *sendmail-arguments*"));
1606    if((cp = ok_vlook(mta_arguments)) == NULL)
1607       cp = cp_v15compat;
1608    if ((cp /* TODO v15: = ok_vlook(mta_arguments)*/) == NULL) {
1609       vas_cnt = 0;
1610       vas = NULL;
1611    } else {
1612       /* Don't assume anything on the content but do allocate exactly j slots;
1613        * like this getrawlist will never overflow (and return -1) */
1614       j = su_cs_len(cp);
1615       vas = n_lofi_alloc(sizeof(*vas) * j);
1616       vas_cnt = (uz)getrawlist(TRU1, vas, j, cp, j);
1617    }
1618 
1619    i = 4 + n_smopts_cnt + vas_cnt + 4 + 1 + count(to) + 1;
1620    args = n_autorec_alloc(i * sizeof(char*));
1621 
1622    if((cp_v15compat = ok_vlook(sendmail_progname)) != NULL)
1623       n_OBSOLETE(_("please use *mta-argv0*, not *sendmail-progname*"));
1624    cp = ok_vlook(mta_argv0);
1625    if(cp_v15compat != NULL && !su_cs_cmp(cp, VAL_MTA_ARGV0))
1626       cp = cp_v15compat;
1627    args[0] = cp/* TODO v15 only : = ok_vlook(mta_argv0) */;
1628 
1629    if ((snda = ok_blook(sendmail_no_default_arguments)))
1630       n_OBSOLETE(_("please use *mta-no-default-arguments*, "
1631          "not *sendmail-no-default-arguments*"));
1632    snda |= ok_blook(mta_no_default_arguments);
1633    if ((snda /* TODO v15: = ok_blook(mta_no_default_arguments)*/))
1634       i = 1;
1635    else {
1636       args[1] = "-i";
1637       i = 2;
1638       if (ok_blook(metoo))
1639          args[i++] = "-m";
1640       if (n_poption & n_PO_V)
1641          args[i++] = "-v";
1642    }
1643 
1644    for (j = 0; j < n_smopts_cnt; ++j, ++i)
1645       args[i] = n_smopts[j];
1646 
1647    for (j = 0; j < vas_cnt; ++j, ++i)
1648       args[i] = vas[j];
1649 
1650    /* -r option?  In conjunction with -t we act compatible to postfix(1) and
1651     * ignore it (it is -f / -F there) if the message specified From:/Sender:.
1652     * The interdependency with -t has been resolved in n_puthead() */
1653    if (!snda && ((n_poption & n_PO_r_FLAG) || ok_blook(r_option_implicit))) {
1654       struct mx_name const *np;
1655 
1656       if (hp != NULL && (np = hp->h_from) != NULL) {
1657          /* However, what wasn't resolved there was the case that the message
1658           * specified multiple From: addresses and a Sender: */
1659          if((n_poption & n_PO_t_FLAG) && hp->h_sender != NULL)
1660             np = hp->h_sender;
1661 
1662          if (np->n_fullextra != NULL) {
1663             args[i++] = "-F";
1664             args[i++] = np->n_fullextra;
1665          }
1666          cp = np->n_name;
1667       } else {
1668          ASSERT(n_poption_arg_r == NULL);
1669          cp = skin(myorigin(NULL));
1670       }
1671 
1672       if (cp != NULL) {
1673          args[i++] = "-f";
1674          args[i++] = cp;
1675       }
1676    }
1677 
1678    /* Terminate option list to avoid false interpretation of system-wide
1679     * aliases that start with hyphen */
1680    if (!snda)
1681       args[i++] = "--";
1682 
1683    /* Receivers follow */
1684    if(!ok_blook(mta_no_receiver_arguments))
1685       for (; to != NULL; to = to->n_flink)
1686          if (!(to->n_type & GDEL))
1687             args[i++] = to->n_name;
1688    args[i] = NULL;
1689 
1690    if(vas != NULL)
1691       n_lofi_free(vas);
1692    NYD_OU;
1693    return args;
1694 }
1695 
1696 static void
a_sendout_mta_file_debug(struct sendbundle * sbp,char const * mta,char const ** args)1697 a_sendout_mta_file_debug(struct sendbundle *sbp, char const *mta,
1698    char const **args)
1699 {
1700    uz cnt, bufsize, llen;
1701    char *buf;
1702    NYD_IN;
1703 
1704    n_err(_(">>> MTA: %s, arguments:"), n_shexp_quote_cp(mta, FAL0));
1705    for (; *args != NULL; ++args)
1706       n_err(" %s", n_shexp_quote_cp(*args, FAL0));
1707    n_err("\n");
1708 
1709    fflush_rewind(sbp->sb_input);
1710 
1711    cnt = fsize(sbp->sb_input);
1712    buf = NULL;
1713    bufsize = 0;
1714    while (fgetline(&buf, &bufsize, &cnt, &llen, sbp->sb_input, TRU1) != NULL) {
1715       buf[--llen] = '\0';
1716       n_err(">>> %s\n", buf);
1717    }
1718    if (buf != NULL)
1719       n_free(buf);
1720    NYD_OU;
1721 }
1722 
1723 static boole
a_sendout_mta_test(struct sendbundle * sbp,char const * mta)1724 a_sendout_mta_test(struct sendbundle *sbp, char const *mta)
1725 {
1726    enum{
1727       a_OK = 0,
1728       a_ERR = 1u<<0,
1729       a_MAFC = 1u<<1,
1730       a_ANY = 1u<<2,
1731       a_LASTNL = 1u<<3
1732    };
1733    FILE *fp;
1734    s32 f;
1735    uz bufsize, cnt, llen;
1736    char *buf;
1737    NYD_IN;
1738 
1739    mx_fs_linepool_aquire(&buf, &bufsize);
1740 
1741    if(*mta == '\0')
1742       fp = n_stdout;
1743    else{
1744       if((fp = mx_fs_open(mta, "W+")) != NIL)
1745          ;
1746       else if((fp = mx_fs_open(mta, "r+")) == NIL)
1747          goto jeno;
1748       else if(!mx_file_lock(fileno(fp), (mx_FILE_LOCK_TYPE_READ |
1749             mx_FILE_LOCK_TYPE_WRITE), 0,0, UZ_MAX)){
1750          f = su_ERR_NOLCK;
1751          goto jefo;
1752       }else if((f = n_folder_mbox_prepare_append(fp, NIL)) != su_ERR_NONE)
1753          goto jefo;
1754    }
1755 
1756    fflush_rewind(sbp->sb_input);
1757    cnt = fsize(sbp->sb_input);
1758    f = ok_blook(mbox_fcc_and_pcc) ? a_MAFC : a_OK;
1759 
1760    if((f & a_MAFC) &&
1761          fprintf(fp, "From %s %s", ok_vlook(LOGNAME), time_current.tc_ctime
1762             ) < 0)
1763       goto jeno;
1764    while(fgetline(&buf, &bufsize, &cnt, &llen, sbp->sb_input, TRU1) != NIL){
1765       if(fwrite(buf, 1, llen, fp) != llen)
1766          goto jeno;
1767       if(f & a_MAFC){
1768          f |= a_ANY;
1769          if(llen > 0 && buf[llen - 1] == '\0')
1770             f |= a_LASTNL;
1771          else
1772             f &= ~a_LASTNL;
1773       }
1774    }
1775    if(ferror(sbp->sb_input))
1776       goto jefo;
1777    if((f & (a_ANY | a_LASTNL)) == a_ANY && putc('\n', fp) == EOF)
1778       goto jeno;
1779 
1780 jdone:
1781    if(fp != n_stdout)
1782       mx_fs_close(fp);
1783    else
1784       clearerr(fp);
1785 
1786 jleave:
1787    mx_fs_linepool_release(buf, bufsize);
1788 
1789    NYD_OU;
1790    return ((f & a_ERR) == 0);
1791 
1792 jeno:
1793    f = su_err_no();
1794 jefo:
1795    n_err(_("test MTA: cannot open/prepare/write: %s: %s\n"),
1796       n_shexp_quote_cp(mta, FAL0), su_err_doc(f));
1797    f = a_ERR;
1798    if(fp != NIL)
1799       goto jdone;
1800    goto jleave;
1801 }
1802 
1803 static char const *
a_sendout_random_id(struct header * hp,boole msgid)1804 a_sendout_random_id(struct header *hp, boole msgid)
1805 {
1806    static u32 reprocnt;
1807    struct tm *tmp;
1808    char const *h;
1809    uz rl, i;
1810    char *rv, sep;
1811    NYD_IN;
1812 
1813    rv = NULL;
1814 
1815    if(msgid && hp != NULL && hp->h_message_id != NULL){
1816       rv = hp->h_message_id->n_name;
1817       goto jleave;
1818    }
1819 
1820    if(ok_blook(message_id_disable))
1821       goto jleave;
1822 
1823    sep = '%';
1824    rl = 5;
1825    if((h = __sendout_ident) != NULL)
1826       goto jgen;
1827    if(ok_vlook(hostname) != NULL){
1828       h = n_nodename(TRU1);
1829       sep = '@';
1830       rl = 8;
1831       goto jgen;
1832    }
1833    if(hp != NULL && (h = skin(myorigin(hp))) != NULL &&
1834          su_cs_find_c(h, '@') != NULL)
1835       goto jgen;
1836    goto jleave;
1837 
1838 jgen:
1839    tmp = &time_current.tc_gm;
1840    i = sizeof("%04d%02d%02d%02d%02d%02d.%s%c%s") -1 + rl + su_cs_len(h);
1841    rv = n_autorec_alloc(i +1);
1842    snprintf(rv, i, "%04d%02d%02d%02d%02d%02d.%s%c%s",
1843       tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
1844       tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
1845       mx_random_create_cp(rl, &reprocnt), sep, h);
1846    rv[i] = '\0'; /* Because we don't test snprintf(3) return */
1847 jleave:
1848    NYD_OU;
1849    return rv;
1850 }
1851 
1852 static boole
a_sendout_put_addrline(char const * hname,struct mx_name * np,FILE * fo,enum a_sendout_addrline_flags saf)1853 a_sendout_put_addrline(char const *hname, struct mx_name *np, FILE *fo,
1854    enum a_sendout_addrline_flags saf)
1855 {
1856    sz hnlen, col, len;
1857    enum{
1858       m_ERROR = 1u<<0,
1859       m_INIT = 1u<<1,
1860       m_COMMA = 1u<<2,
1861       m_NOPF = 1u<<3,
1862       m_NONAME = 1u<<4,
1863       m_CSEEN = 1u<<5
1864    } m;
1865    NYD_IN;
1866 
1867    m = (saf & GCOMMA) ? m_ERROR | m_COMMA : m_ERROR;
1868 
1869    if((col = hnlen = su_cs_len(hname)) > 0){
1870 #undef _X
1871 #define _X(S)  (col == sizeof(S) -1 && !su_cs_cmp_case(hname, S))
1872       if (saf & GFILES) {
1873          ;
1874       } else if (_X("reply-to:") || _X("mail-followup-to:") ||
1875             _X("references:") || _X("in-reply-to:") ||
1876             _X("disposition-notification-to:"))
1877          m |= m_NOPF | m_NONAME;
1878       else if (ok_blook(add_file_recipients)) {
1879          ;
1880       } else if (_X("to:") || _X("cc:") || _X("bcc:") || _X("resent-to:"))
1881          m |= m_NOPF;
1882 #undef _X
1883    }
1884 
1885    for (; np != NULL; np = np->n_flink) {
1886       if(np->n_type & GDEL)
1887          continue;
1888       if(is_addr_invalid(np,
1889                ((saf & a_SENDOUT_AL_INC_INVADDR ? 0 : EACM_NOLOG) |
1890                 (m & m_NONAME ? EACM_NONAME : EACM_NONE))) &&
1891             !(saf & a_SENDOUT_AL_INC_INVADDR))
1892          continue;
1893 
1894       /* File and pipe addresses only printed with set *add-file-recipients* */
1895       if ((m & m_NOPF) && is_fileorpipe_addr(np))
1896          continue;
1897 
1898       if ((m & (m_INIT | m_COMMA)) == (m_INIT | m_COMMA)) {
1899          if (putc(',', fo) == EOF)
1900             goto jleave;
1901          m |= m_CSEEN;
1902          ++col;
1903       }
1904 
1905       len = su_cs_len(np->n_fullname);
1906       if (np->n_type & GREF)
1907          len += 2;
1908       ++col; /* The separating space */
1909       if ((m & m_INIT) && /*col > 1 &&*/
1910             UCMP(z, col + len, >,
1911                (np->n_type & GREF ? MIME_LINELEN : 72))) {
1912          if (fputs("\n ", fo) == EOF)
1913             goto jleave;
1914          col = 1;
1915          m &= ~m_CSEEN;
1916       } else {
1917          if(!(m & m_INIT) && fwrite(hname, sizeof *hname, hnlen, fo
1918                ) != sizeof *hname * hnlen)
1919             goto jleave;
1920          if(putc(' ', fo) == EOF)
1921             goto jleave;
1922       }
1923       m = (m & ~m_CSEEN) | m_INIT;
1924 
1925       /* C99 */{
1926          char *hb;
1927 
1928          /* GREF needs to be placed in angle brackets, but which are missing */
1929          hb = np->n_fullname;
1930          if(np->n_type & GREF){
1931             ASSERT(UCMP(z, len, ==, su_cs_len(np->n_fullname) + 2));
1932             hb = n_lofi_alloc(len +1);
1933             len -= 2;
1934             hb[0] = '<';
1935             hb[len + 1] = '>';
1936             hb[len + 2] = '\0';
1937             su_mem_copy(&hb[1], np->n_fullname, len);
1938             len += 2;
1939          }
1940          len = xmime_write(hb, len, fo,
1941                ((saf & a_SENDOUT_AL_DOMIME) ? CONV_TOHDR_A : CONV_NONE),
1942                TD_ICONV, NULL, NULL);
1943          if(np->n_type & GREF)
1944             n_lofi_free(hb);
1945       }
1946       if (len < 0)
1947          goto jleave;
1948       col += len;
1949    }
1950 
1951    if(!(m & m_INIT) || putc('\n', fo) != EOF)
1952       m ^= m_ERROR;
1953 jleave:
1954    NYD_OU;
1955    return ((m & m_ERROR) == 0);
1956 }
1957 
1958 static boole
a_sendout_infix_resend(FILE * fi,FILE * fo,struct message * mp,struct mx_name * to,int add_resent)1959 a_sendout_infix_resend(FILE *fi, FILE *fo, struct message *mp,
1960    struct mx_name *to, int add_resent)
1961 {
1962    uz cnt, c, bufsize;
1963    char *buf;
1964    char const *cp;
1965    struct mx_name *fromfield = NULL, *senderfield = NULL, *mdn;
1966    boole rv;
1967    NYD_IN;
1968 
1969    rv = FAL0;
1970    mx_fs_linepool_aquire(&buf, &bufsize);
1971    cnt = mp->m_size;
1972 
1973    /* Write the Resent-Fields */
1974    if (add_resent) {
1975       if(fputs("Resent-", fo) == EOF)
1976          goto jleave;
1977       if(mkdate(fo, "Date") == -1)
1978          goto jleave;
1979       if ((cp = myaddrs(NULL)) != NULL) {
1980          if (!a_sendout_put_name(cp, GCOMMA, SEND_MBOX, "Resent-From:", fo,
1981                &fromfield))
1982             goto jleave;
1983       }
1984       /* TODO RFC 5322: Resent-Sender SHOULD NOT be used if it's EQ -From: */
1985       if ((cp = ok_vlook(sender)) != NULL) {
1986          if (!a_sendout_put_name(cp, GCOMMA | GNOT_A_LIST, SEND_MBOX,
1987                "Resent-Sender:", fo, &senderfield))
1988             goto jleave;
1989       }
1990       if (!a_sendout_put_addrline("Resent-To:", to, fo, a_SENDOUT_AL_COMMA))
1991          goto jleave;
1992       if (((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")) &&
1993             (cp = a_sendout_random_id(NULL, TRU1)) != NULL &&
1994             fprintf(fo, "Resent-Message-ID: <%s>\n", cp) < 0)
1995          goto jleave;
1996    }
1997 
1998    if((mdn = n_UNCONST(check_from_and_sender(fromfield, senderfield))) == NIL)
1999       goto jleave;
2000    if (!_check_dispo_notif(mdn, NULL, fo))
2001       goto jleave;
2002 
2003    /* Write the original headers */
2004    while (cnt > 0) {
2005       if(fgetline(&buf, &bufsize, &cnt, &c, fi, FAL0) == NIL){
2006          if(ferror(fi))
2007             goto jleave;
2008          break;
2009       }
2010       if (su_cs_cmp_case_n("status:", buf, 7) &&
2011             su_cs_cmp_case_n("disposition-notification-to:", buf, 28) &&
2012             !is_head(buf, c, FAL0)){
2013          if(fwrite(buf, sizeof *buf, c, fo) != c)
2014             goto jleave;
2015       }
2016       if (cnt > 0 && *buf == '\n')
2017          break;
2018    }
2019 
2020    /* Write the message body */
2021    while(cnt > 0){
2022       if(fgetline(&buf, &bufsize, &cnt, &c, fi, FAL0) == NIL){
2023          if(ferror(fi))
2024             goto jleave;
2025          break;
2026       }
2027       if(cnt == 0 && *buf == '\n')
2028          break;
2029       if(fwrite(buf, sizeof *buf, c, fo) != c)
2030          goto jleave;
2031    }
2032 
2033    rv = TRU1;
2034 jleave:
2035    mx_fs_linepool_release(buf, bufsize);
2036 
2037    if(!rv)
2038       n_err(_("infix_resend: creation of temporary mail file failed\n"));
2039    NYD_OU;
2040    return rv;
2041 }
2042 
2043 FL boole
mx_sendout_mta_url(struct mx_url * urlp)2044 mx_sendout_mta_url(struct mx_url *urlp){
2045    /* TODO In v15 this should simply be url_creat(,ok_vlook(mta).
2046     * TODO Register a "test" protocol handler, and if the protocol ends up
2047     * TODO as file and the path is "test", then this is also test.
2048     * TODO I.e., CPROTO_FILE and CPROTO_TEST.  Until then this is messy */
2049    char const *mta;
2050    boole rv;
2051    NYD_IN;
2052 
2053    rv = FAL0;
2054 
2055    if((mta = ok_vlook(smtp)) == NIL){
2056       boole issnd;
2057       u16 pno;
2058       char const *proto;
2059 
2060       mta = ok_vlook(mta);
2061 
2062       if((proto = ok_vlook(sendmail)) != NIL){ /* v15-compat */
2063          n_OBSOLETE(_("please use *mta* instead of *sendmail*"));
2064          /* But use it in favour of default value, then */
2065          if(!su_cs_cmp(mta, VAL_MTA))
2066             mta = proto;
2067       }
2068 
2069       if(su_cs_find_c(mta, ':') == NIL){
2070          if(su_cs_cmp(mta, "test")){
2071             pno = 0;
2072             goto jisfile;
2073          }
2074          pno = U16_MAX;
2075          goto jistest;
2076       }else if((proto = mx_url_servbyname(mta, &pno, &issnd)) == NIL){
2077          goto jemta;
2078       }else if(*proto == '\0'){
2079          if(pno == 0)
2080             mta += sizeof("file://") -1;
2081          else{
2082             /* test -> stdout, test://X -> X */
2083             ASSERT(pno == U16_MAX);
2084 jistest:
2085             mta += sizeof("test") -1;
2086             if(mta[0] == ':' && mta[1] == '/' && mta[2] == '/')
2087                mta += 3;
2088          }
2089 jisfile:
2090          su_mem_set(urlp, 0, sizeof *urlp);
2091          urlp->url_input = mta;
2092          urlp->url_portno = pno;
2093          urlp->url_cproto = CPROTO_NONE;
2094          rv = TRUM1;
2095          goto jleave;
2096       }else if(!issnd)
2097          goto jemta;
2098    }else{
2099       n_OBSOLETE(_("please do not use *smtp*, instead "
2100          "assign a smtp:// URL to *mta*!"));
2101       /* For *smtp* the smtp:// protocol was optional; be simple: do not check
2102        * that *smtp* is misused with file:// or so */
2103       if(mx_url_servbyname(mta, NIL, NIL) == NIL)
2104          mta = savecat("smtp://", mta);
2105    }
2106 
2107 #ifdef mx_HAVE_NET
2108    rv = mx_url_parse(urlp, CPROTO_SMTP, mta);
2109 #endif
2110 
2111    if(!rv)
2112 jemta:
2113       n_err(_("*mta*: invalid or unsupported value: %s\n"),
2114          n_shexp_quote_cp(mta, FAL0));
2115 jleave:
2116    NYD_OU;
2117    return rv;
2118 }
2119 
2120 FL int
n_mail(enum n_mailsend_flags msf,struct mx_name * to,struct mx_name * cc,struct mx_name * bcc,char const * subject,struct mx_attachment * attach,char const * quotefile)2121 n_mail(enum n_mailsend_flags msf, struct mx_name *to, struct mx_name *cc,
2122    struct mx_name *bcc, char const *subject, struct mx_attachment *attach,
2123    char const *quotefile)
2124 {
2125    struct header head;
2126    struct str in, out;
2127    boole fullnames;
2128    NYD_IN;
2129 
2130    su_mem_set(&head, 0, sizeof head);
2131 
2132    /* The given subject may be in RFC1522 format. */
2133    if (subject != NULL) {
2134       in.s = n_UNCONST(subject);
2135       in.l = su_cs_len(subject);
2136       mime_fromhdr(&in, &out, /* TODO ??? TD_ISPR |*/ TD_ICONV);
2137       head.h_subject = out.s;
2138    }
2139 
2140    fullnames = ok_blook(fullnames);
2141 
2142    head.h_mailx_command = "mail";
2143    if((head.h_to = to) != NULL){
2144       if(!fullnames)
2145          head.h_to = to = a_sendout_fullnames_cleanup(to);
2146       head.h_mailx_raw_to = n_namelist_dup(to, to->n_type);
2147    }
2148    if((head.h_cc = cc) != NULL){
2149       if(!fullnames)
2150          head.h_cc = cc = a_sendout_fullnames_cleanup(cc);
2151       head.h_mailx_raw_cc = n_namelist_dup(cc, cc->n_type);
2152    }
2153    if((head.h_bcc = bcc) != NULL){
2154       if(!fullnames)
2155          head.h_bcc = bcc = a_sendout_fullnames_cleanup(bcc);
2156       head.h_mailx_raw_bcc = n_namelist_dup(bcc, bcc->n_type);
2157    }
2158 
2159    head.h_attach = attach;
2160 
2161    /* TODO n_exit_status only!!?? */n_mail1(msf, &head, NULL, quotefile);
2162 
2163    if (subject != NULL)
2164       n_free(out.s);
2165    NYD_OU;
2166    return 0;
2167 }
2168 
2169 FL int
c_sendmail(void * v)2170 c_sendmail(void *v)
2171 {
2172    int rv;
2173    NYD_IN;
2174 
2175    rv = a_sendout_sendmail(v, n_MAILSEND_NONE);
2176    NYD_OU;
2177    return rv;
2178 }
2179 
2180 FL int
c_Sendmail(void * v)2181 c_Sendmail(void *v)
2182 {
2183    int rv;
2184    NYD_IN;
2185 
2186    rv = a_sendout_sendmail(v, n_MAILSEND_RECORD_RECIPIENT);
2187    NYD_OU;
2188    return rv;
2189 }
2190 
2191 FL enum okay
n_mail1(enum n_mailsend_flags msf,struct header * hp,struct message * quote,char const * quotefile)2192 n_mail1(enum n_mailsend_flags msf, struct header *hp, struct message *quote,
2193    char const *quotefile)
2194 {
2195 #ifdef mx_HAVE_NET
2196    struct mx_cred_ctx cc;
2197 #endif
2198    struct mx_url url, *urlp = &url;
2199    struct n_sigman sm;
2200    struct sendbundle sb;
2201    struct mx_name *to;
2202    boole dosign, mta_isexe;
2203    FILE * volatile mtf, *nmtf;
2204    enum okay volatile rv;
2205    NYD_IN;
2206 
2207    _sendout_error = FAL0;
2208    __sendout_ident = NULL;
2209    n_pstate_err_no = su_ERR_INVAL;
2210    rv = STOP;
2211    mtf = NULL;
2212 
2213    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL) {
2214    case 0:
2215       break;
2216    default:
2217       goto jleave;
2218    }
2219 
2220    /* Update some globals we likely need first */
2221    time_current_update(&time_current, TRU1);
2222 
2223    /* Collect user's mail from standard input.  Get the result as mtf */
2224    mtf = n_collect(msf, hp, quote, quotefile, &_sendout_error);
2225    if (mtf == NULL)
2226       goto jleave;
2227    /* TODO All custom headers should be joined here at latest
2228     * TODO In fact that should happen before we enter compose mode, so that the
2229     * TODO -C headers can be managed (removed etc.) via ~^, too, but the
2230     * TODO *customhdr* ones are fixated at this very place here, no sooner! */
2231 
2232    /* */
2233 #ifdef mx_HAVE_PRIVACY
2234    dosign = TRUM1;
2235 #else
2236    dosign = FAL0;
2237 #endif
2238 
2239    if(n_psonce & n_PSO_INTERACTIVE){
2240 #ifdef mx_HAVE_PRIVACY
2241       if(ok_blook(asksign))
2242          dosign = mx_tty_yesorno(_("Sign this message"), TRU1);
2243 #endif
2244    }
2245 
2246    if(fsize(mtf) == 0){
2247       if(n_poption & n_PO_E_FLAG){
2248          n_pstate_err_no = su_ERR_NONE;
2249          rv = OKAY;
2250          goto jleave;
2251       }
2252 
2253       if(hp->h_subject == NULL)
2254          n_err(_("No message, no subject; hope that's ok\n"));
2255       else if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
2256          n_err(_("Null message body; hope that's ok\n"));
2257    }
2258 
2259 #ifdef mx_HAVE_PRIVACY
2260    if(dosign == TRUM1)
2261       dosign = mx_privacy_sign_is_desired(); /* TODO USER@HOST, *from*++!!! */
2262 #endif
2263 
2264    /* XXX Update time_current again; once n_collect() offers editing of more
2265     * XXX headers, including Date:, this must only happen if Date: is the
2266     * XXX same that it was before n_collect() (e.g., postponing etc.).
2267     * XXX But *do* update otherwise because the mail seems to be backdated
2268     * XXX if the user edited some time, which looks odd and it happened
2269     * XXX to me that i got mis-dated response mails due to that... */
2270    time_current_update(&time_current, TRU1);
2271 
2272    /* TODO hrmpf; the MIME/send layer rewrite MUST address the init crap:
2273     * TODO setup the header ONCE; note this affects edit.c, collect.c ...,
2274     * TODO but: offer a hook that rebuilds/expands/checks/fixates all
2275     * TODO header fields ONCE, call that ONCE after user editing etc. has
2276     * TODO completed (one edit cycle) */
2277 
2278    if(!(mta_isexe = mx_sendout_mta_url(urlp)))
2279       goto jfail_dead;
2280    mta_isexe = (mta_isexe != TRU1);
2281 
2282    /* Take the user names from the combined to and cc lists and do all the
2283     * alias processing.  The POSIX standard says:
2284     *   The names shall be substituted when alias is used as a recipient
2285     *   address specified by the user in an outgoing message (that is,
2286     *   other recipients addressed indirectly through the reply command
2287     *   shall not be substituted in this manner).
2288     * XXX S-nail thus violates POSIX, as has been pointed out correctly by
2289     * XXX Martin Neitzel, but logic and usability of POSIX standards is
2290     * XXX sometimes disputable: go for user friendliness */
2291    to = n_namelist_vaporise_head(hp, TRU1, ((quote != NULL &&
2292             (msf & n_MAILSEND_IS_FWD) == 0) || !ok_blook(posix)),
2293          (EACM_NORMAL | EACM_DOMAINCHECK |
2294             (mta_isexe ? EACM_NONE : EACM_NONAME | EACM_NONAME_OR_FAIL)),
2295          &_sendout_error);
2296 
2297    if(_sendout_error < 0){
2298       n_err(_("Some addressees were classified as \"hard error\"\n"));
2299       n_pstate_err_no = su_ERR_PERM;
2300       goto jfail_dead;
2301    }
2302    if(to == NULL){
2303       n_err(_("No recipients specified\n"));
2304       n_pstate_err_no = su_ERR_DESTADDRREQ;
2305       goto jfail_dead;
2306    }
2307 
2308    /* */
2309    su_mem_set(&sb, 0, sizeof sb);
2310    sb.sb_hp = hp;
2311    sb.sb_to = to;
2312    sb.sb_input = mtf;
2313    sb.sb_urlp = urlp;
2314 #ifdef mx_HAVE_NET
2315    sb.sb_credp = &cc;
2316 #endif
2317 
2318    if((dosign || count_nonlocal(to) > 0) &&
2319          !_sendbundle_setup_creds(&sb, (dosign > 0))){
2320       /* TODO saving $DEAD and recovering etc is not yet well defined */
2321       n_pstate_err_no = su_ERR_INVAL;
2322       goto jfail_dead;
2323    }
2324 
2325    /* 'Bit ugly kind of control flow until we find a charset that does it */
2326    /* C99 */{
2327       boole any;
2328 
2329       for(any = FAL0, charset_iter_reset(hp->h_charset);;
2330             any = TRU1, charset_iter_next()){
2331          int err;
2332          boole volatile force;
2333 
2334          force = FAL0;
2335          if(!charset_iter_is_valid() &&
2336                (!any || !(force = ok_blook(mime_force_sendout))))
2337             err = su_ERR_NOENT;
2338          else if((nmtf = a_sendout_infix(hp, mtf, dosign, force)) != NULL)
2339             break;
2340 #ifdef mx_HAVE_ICONV
2341          else if((err = n_iconv_err_no) == su_ERR_ILSEQ ||
2342                err == su_ERR_INVAL || err == su_ERR_NOENT){
2343             rewind(mtf);
2344             continue;
2345          }
2346 #endif
2347 
2348          n_perr(_("Cannot find a usable character set to encode message"),
2349             err);
2350          n_pstate_err_no = su_ERR_NOTSUP;
2351          goto jfail_dead;
2352       }
2353    }
2354    mtf = nmtf;
2355 
2356    /*  */
2357 #ifdef mx_HAVE_PRIVACY
2358    if(dosign){
2359       if((nmtf = mx_privacy_sign(mtf, sb.sb_signer.s)) == NIL)
2360          goto jfail_dead;
2361       mx_fs_close(mtf);
2362       mtf = nmtf;
2363    }
2364 #endif
2365 
2366    /* TODO truly - i still don't get what follows: (1) we deliver file
2367     * TODO and pipe addressees, (2) we mightrecord() and (3) we transfer
2368     * TODO even if (1) savedeadletter() etc.  To me this doesn't make sense? */
2369 
2370    /* C99 */{
2371       u32 cnt;
2372       boole b;
2373 
2374       /* Deliver pipe and file addressees */
2375       b = (ok_blook(record_files) && count(to) > 0);
2376       to = a_sendout_file_a_pipe(to, mtf, &_sendout_error);
2377 
2378       if (_sendout_error)
2379          savedeadletter(mtf, FAL0);
2380 
2381       to = elide(to); /* XXX only to drop GDELs due a_sendout_file_a_pipe()! */
2382       cnt = count(to);
2383 
2384       if(((msf & n_MAILSEND_RECORD_RECIPIENT) || b || cnt > 0) &&
2385             !a_sendout_mightrecord(mtf,
2386                (msf & n_MAILSEND_RECORD_RECIPIENT ? to : NIL), FAL0))
2387          goto jleave;
2388       if (cnt > 0) {
2389          sb.sb_hp = hp;
2390          sb.sb_to = to;
2391          sb.sb_input = mtf;
2392          b = FAL0;
2393          if(a_sendout_transfer(&sb, FAL0, &b))
2394             rv = OKAY;
2395          else if(b && _sendout_error == 0){
2396             _sendout_error = b;
2397             savedeadletter(mtf, FAL0);
2398          }
2399       } else if (!_sendout_error)
2400          rv = OKAY;
2401    }
2402 
2403    n_sigman_cleanup_ping(&sm);
2404 jleave:
2405    if(mtf != NIL){
2406       char const *cp;
2407 
2408       mx_fs_close(mtf);
2409 
2410       if((cp = ok_vlook(on_compose_cleanup)) != NULL)
2411          temporary_compose_mode_hook_call(cp, NULL, NULL);
2412    }
2413 
2414    temporary_compose_mode_hook_unroll();
2415 
2416    if(_sendout_error){
2417       n_psonce |= n_PSO_SEND_ERROR;
2418       n_exit_status |= n_EXIT_SEND_ERROR;
2419    }
2420    if(rv == OKAY)
2421       n_pstate_err_no = su_ERR_NONE;
2422 
2423    NYD_OU;
2424    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
2425    return rv;
2426 
2427 jfail_dead:
2428    _sendout_error = TRU1;
2429    savedeadletter(mtf, TRU1);
2430    n_err(_("... message not sent\n"));
2431    goto jleave;
2432 }
2433 
2434 FL int
mkdate(FILE * fo,char const * field)2435 mkdate(FILE *fo, char const *field)
2436 {
2437    struct tm tmpgm, *tmptr;
2438    int tzdiff_hour, tzdiff_min, rv;
2439    NYD_IN;
2440 
2441    su_mem_copy(&tmpgm, &time_current.tc_gm, sizeof tmpgm);
2442    tmptr = &time_current.tc_local;
2443 
2444    tzdiff_min = S(int,n_time_tzdiff(time_current.tc_time, NIL, tmptr));
2445    tzdiff_min /= 60; /* TODO su_TIME_MIN_SECS */
2446    tzdiff_hour = tzdiff_min / 60;
2447    tzdiff_min %= 60; /* TODO su_TIME_HOUR_MINS */
2448 
2449    rv = fprintf(fo, "%s: %s, %02d %s %04d %02d:%02d:%02d %+05d\n",
2450          field,
2451          n_weekday_names[tmptr->tm_wday],
2452          tmptr->tm_mday, n_month_names[tmptr->tm_mon],
2453          tmptr->tm_year + 1900, tmptr->tm_hour,
2454          tmptr->tm_min, tmptr->tm_sec,
2455          tzdiff_hour * 100 + tzdiff_min);
2456    if(rv < 0)
2457       rv = -1;
2458 
2459    NYD_OU;
2460    return rv;
2461 }
2462 
2463 FL boole
n_puthead(boole nosend_msg,struct header * hp,FILE * fo,enum gfield w,enum sendaction action,enum conversion convert,char const * contenttype,char const * charset)2464 n_puthead(boole nosend_msg, struct header *hp, FILE *fo, enum gfield w,
2465    enum sendaction action, enum conversion convert, char const *contenttype,
2466    char const *charset)
2467 {
2468 #define a_PUT_CC_BCC_FCC()   \
2469 do {\
2470    if ((w & GCC) && (hp->h_cc != NULL || nosend_msg == TRUM1)) {\
2471       if (!a_sendout_put_addrline("Cc:", hp->h_cc, fo, saf))\
2472          goto jleave;\
2473       ++gotcha;\
2474    }\
2475    if ((w & GBCC) && (hp->h_bcc != NULL || nosend_msg == TRUM1)) {\
2476       if (!a_sendout_put_addrline("Bcc:", hp->h_bcc, fo, saf))\
2477          goto jleave;\
2478       ++gotcha;\
2479    }\
2480    if((w & GBCC_IS_FCC) && nosend_msg){\
2481       for(np = hp->h_fcc; np != NULL; np = np->n_flink){\
2482          if(fprintf(fo, "Fcc: %s\n", np->n_name) < 0)\
2483             goto jleave;\
2484          ++gotcha;\
2485       }\
2486    }\
2487 } while (0)
2488 
2489    char const *addr;
2490    uz gotcha;
2491    int stealthmua;
2492    boole nodisp;
2493    enum a_sendout_addrline_flags saf;
2494    struct mx_name *np, *fromasender, *mft, **mftp;
2495    boole rv;
2496    NYD_IN;
2497 
2498    mftp = NIL;
2499    fromasender = mft = NIL;
2500    rv = FAL0;
2501 
2502    if(nosend_msg == TRUM1 && !(hp->h_flags & HF_USER_EDITED)){
2503       if(fputs(_("# Message will be discarded unless file is saved\n"),
2504             fo) == EOF)
2505          goto jleave;
2506    }
2507 
2508    if ((addr = ok_vlook(stealthmua)) != NULL)
2509       stealthmua = !su_cs_cmp(addr, "noagent") ? -1 : 1;
2510    else
2511       stealthmua = 0;
2512    gotcha = 0;
2513    nodisp = (action != SEND_TODISP);
2514    saf = (w & (GCOMMA | GFILES)) | (nodisp ? a_SENDOUT_AL_DOMIME : 0);
2515    if(nosend_msg)
2516       saf |= a_SENDOUT_AL_INC_INVADDR;
2517 
2518    if (w & GDATE)
2519       mkdate(fo, "Date"), ++gotcha;
2520    if (w & GIDENT) {
2521       if (hp->h_from == NULL || hp->h_sender == NULL)
2522          setup_from_and_sender(hp);
2523 
2524       if (hp->h_from != NULL) {
2525          if (!a_sendout_put_addrline("From:", hp->h_from, fo, saf))
2526             goto jleave;
2527          ++gotcha;
2528       }
2529 
2530       if (hp->h_sender != NULL) {
2531          if (!a_sendout_put_addrline("Sender:", hp->h_sender, fo, saf))
2532             goto jleave;
2533          ++gotcha;
2534       }
2535 
2536       fromasender = n_UNCONST(check_from_and_sender(hp->h_from, hp->h_sender));
2537       if (fromasender == NULL)
2538          goto jleave;
2539       /* Note that fromasender is (NULL,) 0x1 or real sender here */
2540    }
2541 
2542    /* M-F-T: check this now, and possibly place us in Cc: */
2543    if((w & GIDENT) && !nosend_msg){
2544       /* Mail-Followup-To: TODO factor out this huge block of code.
2545        * TODO Also, this performs multiple expensive list operations, which
2546        * TODO hopefully can be heavily optimized later on! */
2547       /* Place ourselves in there if any non-subscribed list is an addressee */
2548       if((hp->h_flags & HF_LIST_REPLY) || hp->h_mft != NIL ||
2549             ok_blook(followup_to)){
2550          enum{
2551             a_HADMFT = 1u<<(HF__NEXT_SHIFT + 0),
2552             a_WASINMFT = 1u<<(HF__NEXT_SHIFT + 1),
2553             a_ANYLIST = 1u<<(HF__NEXT_SHIFT + 2),
2554             a_OTHER = 1u<<(HF__NEXT_SHIFT + 3)
2555          };
2556          struct mx_name *x;
2557          u32 f;
2558 
2559          f = hp->h_flags | (hp->h_mft != NIL ? a_HADMFT : 0);
2560          if(f & a_HADMFT){
2561             /* Detect whether we were part of the former MFT:.
2562              * Throw away MFT: if we were the sole member (kidding) */
2563             hp->h_mft = mft = elide(hp->h_mft);
2564             mft = mx_alternates_remove(n_namelist_dup(mft, GNONE), FAL0);
2565             if(mft == NIL)
2566                f ^= a_HADMFT;
2567             else for(x = hp->h_mft; x != NIL;
2568                   x = x->n_flink, mft = mft->n_flink){
2569                if(mft == NIL){
2570                   f |= a_WASINMFT;
2571                   break;
2572                }
2573             }
2574          }
2575 
2576          /* But for that, remove all incarnations of ourselves first.
2577           * TODO It is total crap that we have alternates_remove(), is_myname()
2578           * TODO or whatever; these work only with variables, not with data
2579           * TODO that is _currently_ in some header fields!!!  v15.0: complete
2580           * TODO rewrite, object based, lazy evaluated, on-the-fly marked.
2581           * TODO then this should be a really cheap thing in here... */
2582          np = elide(mx_alternates_remove(cat(
2583                n_namelist_dup(hp->h_to, GEXTRA | GFULL),
2584                n_namelist_dup(hp->h_cc, GEXTRA | GFULL)), FAL0));
2585          addr = hp->h_list_post;
2586          mft = NIL;
2587          mftp = &mft;
2588 
2589          while((x = np) != NIL){
2590             s8 ml;
2591 
2592             np = np->n_flink;
2593 
2594             /* Automatically make MLIST_KNOWN List-Post: address */
2595             /* XXX mx_mlist_query_mp()?? */
2596             if(((ml = mx_mlist_query(x->n_name, FAL0)) == mx_MLIST_OTHER ||
2597                      ml == mx_MLIST_POSSIBLY) &&
2598                   addr != NIL && !su_cs_cmp_case(addr, x->n_name))
2599                ml = mx_MLIST_KNOWN;
2600 
2601             /* Any non-subscribed list?  Add ourselves */
2602             switch(ml){
2603             case mx_MLIST_KNOWN:
2604                f |= HF_MFT_SENDER;
2605                /* FALLTHRU */
2606             case mx_MLIST_SUBSCRIBED:
2607                f |= a_ANYLIST;
2608                goto j_mft_add;
2609             case mx_MLIST_OTHER:
2610             case mx_MLIST_POSSIBLY:
2611                f |= a_OTHER;
2612                if(!(f & HF_LIST_REPLY)){
2613 j_mft_add:
2614                   if(!is_addr_invalid(x,
2615                         EACM_STRICT | EACM_NOLOG | EACM_NONAME)){
2616                      x->n_blink = *mftp;
2617                      x->n_flink = NIL;
2618                      *mftp = x;
2619                      mftp = &x->n_flink;
2620                   } /* XXX write some warning?  if verbose?? */
2621                   continue;
2622                }
2623                /* And if this is a reply that honoured a M-F-T: header then
2624                 * we'll also add all members of the original M-F-T: that are
2625                 * still addressed by us, regardless of other circumstances */
2626                /* TODO If the user edited this list, then we should include
2627                 * TODO whatever she did therewith, even if _LIST_REPLY! */
2628                else if(f & a_HADMFT){
2629                   struct mx_name *ox;
2630 
2631                   for(ox = hp->h_mft; ox != NIL; ox = ox->n_flink)
2632                      if(!su_cs_cmp_case(ox->n_name, x->n_name))
2633                         goto j_mft_add;
2634                }
2635                break;
2636             }
2637          }
2638 
2639          if((f & (a_ANYLIST | a_HADMFT)) && mft != NIL){
2640             if(((f & HF_MFT_SENDER) ||
2641                      ((f & (a_ANYLIST | a_HADMFT)) == a_HADMFT)) &&
2642                   (np = fromasender) != NIL && np != R(struct mx_name*,0x1)){
2643                *mftp = ndup(np, (np->n_type & ~GMASK) | GEXTRA | GFULL);
2644 
2645                /* Place ourselves in the Cc: if we will be a member of M-F-T:,
2646                 * and we are not subscribed (and are no addressee yet)? */
2647                /* TODO This entire block is much to expensive and should
2648                 * TODO be somewhere else (like name_unite(), or so) */
2649                if(ok_blook(followup_to_add_cc)){
2650                   struct mx_name **npp;
2651 
2652                   np = ndup(np, (np->n_type & ~GMASK) | GCC | GFULL);
2653                   np = cat(cat(hp->h_to, hp->h_cc), np);
2654                   np = mx_alternates_remove(np, TRU1);
2655                   np = elide(np);
2656                   hp->h_to = hp->h_cc = NIL;
2657                   for(; np != NIL; np = np->n_flink){
2658                      switch(np->n_type & (GDEL | GMASK)){
2659                      case GTO: npp = &hp->h_to; break;
2660                      case GCC: npp = &hp->h_cc; break;
2661                      default: continue;
2662                      }
2663                      *npp = cat(*npp, ndup(np, np->n_type | GFULL));
2664                   }
2665                }
2666             }
2667          }else
2668             mft = NIL;
2669       }
2670    }
2671 
2672    if(nosend_msg == TRUM1 && !(hp->h_flags & HF_USER_EDITED)){
2673       if(fputs(_("# To:, Cc: and Bcc: support a ?single modifier: "
2674             "To?: exa, <m@ple>\n"), fo) == EOF)
2675          goto jleave;
2676    }
2677 
2678 #if 1
2679    if ((w & GTO) && (hp->h_to != NULL || nosend_msg == TRUM1)) {
2680       if (!a_sendout_put_addrline("To:", hp->h_to, fo, saf))
2681          goto jleave;
2682       ++gotcha;
2683    }
2684 #else
2685    /* TODO Thought about undisclosed recipients:;, but would be such a fake
2686     * TODO given that we cannot handle group addresses.  Ridiculous */
2687    if (w & GTO) {
2688       struct mx_name *xto;
2689 
2690       if ((xto = hp->h_to) != NULL) {
2691          char const ud[] = "To: Undisclosed recipients:;\n" /* TODO groups */;
2692 
2693          if (count_nonlocal(xto) != 0 || ok_blook(add_file_recipients) ||
2694                (hp->h_cc != NULL && count_nonlocal(hp->h_cc) > 0))
2695             goto jto_fmt;
2696          if (fwrite(ud, 1, sizeof(ud) -1, fo) != sizeof(ud) -1)
2697             goto jleave;
2698          ++gotcha;
2699       } else if (nosend_msg == TRUM1) {
2700 jto_fmt:
2701          if (!a_sendout_put_addrline("To:", hp->h_to, fo, saf))
2702             goto jleave;
2703          ++gotcha;
2704       }
2705    }
2706 #endif
2707 
2708    if(!ok_blook(bsdcompat) && !ok_blook(bsdorder))
2709       a_PUT_CC_BCC_FCC();
2710 
2711    if ((w & GSUBJECT) && (hp->h_subject != NULL || nosend_msg == TRUM1)) {
2712       if (fwrite("Subject: ", sizeof(char), 9, fo) != 9)
2713          goto jleave;
2714       if (hp->h_subject != NULL) {
2715          uz sublen;
2716          char const *sub;
2717 
2718          sublen = su_cs_len(sub = subject_re_trim(hp->h_subject));
2719 
2720          /* Trimmed something, (re-)add Re: */
2721          if (sub != hp->h_subject) {
2722             if (fwrite("Re: ", 1, 4, fo) != 4) /* RFC mandates eng. "Re: " */
2723                goto jleave;
2724             if (sublen > 0 &&
2725                   xmime_write(sub, sublen, fo,
2726                      (!nodisp ? CONV_NONE : CONV_TOHDR),
2727                      (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NIL,NIL) < 0)
2728                goto jleave;
2729          }
2730          /* This may be, e.g., a Fwd: XXX yes, unfortunately we do like that */
2731          else if (*sub != '\0') {
2732             if(xmime_write(sub, sublen, fo, (!nodisp ? CONV_NONE : CONV_TOHDR),
2733                   (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NIL,NIL) < 0)
2734                goto jleave;
2735          }
2736       }
2737       ++gotcha;
2738       putc('\n', fo);
2739    }
2740 
2741    if (ok_blook(bsdcompat) || ok_blook(bsdorder))
2742       a_PUT_CC_BCC_FCC();
2743 
2744    if ((w & GMSGID) && stealthmua <= 0 &&
2745          (addr = a_sendout_random_id(hp, TRU1)) != NULL) {
2746       if (fprintf(fo, "Message-ID: <%s>\n", addr) < 0)
2747          goto jleave;
2748       ++gotcha;
2749    }
2750 
2751    if(w & (GREF | GREF_IRT)){
2752       if((np = hp->h_in_reply_to) == NULL)
2753          hp->h_in_reply_to = np = n_header_setup_in_reply_to(hp);
2754       if(np != NULL){
2755          if(nosend_msg == TRUM1 && !(hp->h_flags & HF_USER_EDITED)){
2756             if(fputs(_("# Removing or modifying In-Reply-To: "
2757                      "breaks the old, and starts a new thread.\n"
2758                   "# Assigning hyphen-minus - creates a thread of only the "
2759                      "replied-to message\n"), fo) == EOF)
2760                goto jleave;
2761          }
2762          if(!a_sendout_put_addrline("In-Reply-To:", np, fo, 0))
2763             goto jleave;
2764          ++gotcha;
2765       }
2766 
2767       if((w & GREF) && (np = hp->h_ref) != NULL){
2768          if(!a_sendout_put_addrline("References:", np, fo, 0))
2769             goto jleave;
2770          ++gotcha;
2771       }
2772    }
2773 
2774    if (w & GIDENT) {
2775       /* Reply-To:.  Be careful not to destroy a possible user input, duplicate
2776        * the list first.. TODO it is a terrible codebase.. */
2777       if((np = hp->h_reply_to) != NULL)
2778          np = n_namelist_dup(np, np->n_type);
2779       else{
2780          char const *v15compat;
2781 
2782          if((v15compat = ok_vlook(replyto)) != NULL)
2783             n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2784          if((addr = ok_vlook(reply_to)) == NULL)
2785             addr = v15compat;
2786          np = lextract(addr, GEXTRA |
2787                (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN));
2788       }
2789       if (np != NULL &&
2790             (np = elide(
2791                checkaddrs(usermap(np, TRU1), EACM_STRICT | EACM_NOLOG,
2792                   NULL))) != NULL) {
2793          if (!a_sendout_put_addrline("Reply-To:", np, fo, saf))
2794             goto jleave;
2795          ++gotcha;
2796       }
2797    }
2798 
2799    if((w & GIDENT) && !nosend_msg){
2800       if(mft != NIL){
2801          if(!a_sendout_put_addrline("Mail-Followup-To:", mft, fo, saf))
2802             goto jleave;
2803          ++gotcha;
2804       }
2805 
2806       if(!_check_dispo_notif(fromasender, hp, fo))
2807          goto jleave;
2808    }
2809 
2810    if ((w & GUA) && stealthmua == 0) {
2811       if (fprintf(fo, "User-Agent: %s %s\n", n_uagent,
2812             (su_state_has(su_STATE_REPRODUCIBLE)
2813                ? su_reproducible_build : ok_vlook(version))) < 0)
2814          goto jleave;
2815       ++gotcha;
2816    }
2817 
2818    /* Custom headers, as via -C and *customhdr* TODO JOINED AFTER COMPOSE! */
2819    if(!nosend_msg){
2820       struct n_header_field *chlp[2], *hfp;
2821       u32 i;
2822 
2823       chlp[0] = n_poption_arg_C;
2824       chlp[1] = n_customhdr_list;
2825 
2826       for(i = 0; i < NELEM(chlp); ++i)
2827          if((hfp = chlp[i]) != NULL){
2828             if(!_sendout_header_list(fo, hfp, nodisp))
2829                goto jleave;
2830             ++gotcha;
2831          }
2832    }
2833 
2834    /* The user may have placed headers when editing */
2835    if(1){
2836       struct n_header_field *hfp;
2837 
2838       if((hfp = hp->h_user_headers) != NULL){
2839          if(!_sendout_header_list(fo, hfp, nodisp))
2840             goto jleave;
2841          ++gotcha;
2842       }
2843    }
2844 
2845    /* We don't need MIME unless.. we need MIME?! */
2846    if ((w & GMIME) && ((n_pstate & n_PS_HEADER_NEEDED_MIME) ||
2847          hp->h_attach != NULL ||
2848          ((n_poption & n_PO_Mm_FLAG) && n_poption_arg_Mm != NULL) ||
2849          convert != CONV_7BIT || !n_iconv_name_is_ascii(charset))) {
2850       ++gotcha;
2851       if (fputs("MIME-Version: 1.0\n", fo) == EOF)
2852          goto jleave;
2853       if (hp->h_attach != NULL) {
2854          _sendout_boundary = mime_param_boundary_create();/*TODO carrier*/
2855          if (fprintf(fo,
2856                "Content-Type: multipart/mixed;\n boundary=\"%s\"\n",
2857                _sendout_boundary) < 0)
2858             goto jleave;
2859       } else {
2860          if(a_sendout_put_ct(fo, contenttype, charset) < 0 ||
2861                a_sendout_put_cte(fo, convert) < 0)
2862             goto jleave;
2863       }
2864    }
2865 
2866    if (gotcha && (w & GNL))
2867       if (putc('\n', fo) == EOF)
2868          goto jleave;
2869    rv = TRU1;
2870 jleave:
2871    if(nosend_msg == TRUM1)
2872       hp->h_flags |= HF_USER_EDITED;
2873 
2874    NYD_OU;
2875    return rv;
2876 #undef a_PUT_CC_BCC_FCC
2877 }
2878 
2879 FL enum okay
n_resend_msg(struct message * mp,struct mx_url * urlp,struct header * hp,boole add_resent)2880 n_resend_msg(struct message *mp, struct mx_url *urlp, struct header *hp,
2881    boole add_resent)
2882 {
2883 #ifdef mx_HAVE_NET
2884    struct mx_cred_ctx cc;
2885 #endif
2886    struct n_sigman sm;
2887    struct sendbundle sb;
2888    FILE * volatile ibuf, *nfo, * volatile nfi;
2889    struct mx_fs_tmp_ctx *fstcp;
2890    struct mx_name *to;
2891    enum okay volatile rv;
2892    NYD_IN;
2893 
2894    _sendout_error = FAL0;
2895    __sendout_ident = NULL;
2896    n_pstate_err_no = su_ERR_INVAL;
2897 
2898    rv = STOP;
2899    to = hp->h_to;
2900    ASSERT(hp->h_cc == NIL);
2901    ASSERT(hp->h_bcc == NIL);
2902    nfi = ibuf = NULL;
2903 
2904    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL) {
2905    case 0:
2906       break;
2907    default:
2908       goto jleave;
2909    }
2910 
2911    /* Update some globals we likely need first */
2912    time_current_update(&time_current, TRU1);
2913 
2914    if((nfo = mx_fs_tmp_open("resend", (mx_FS_O_WRONLY | mx_FS_O_HOLDSIGS |
2915             mx_FS_O_REGISTER), &fstcp)) == NIL){
2916       _sendout_error = TRU1;
2917       n_perr(_("resend_msg: temporary mail file"), 0);
2918       n_pstate_err_no = su_ERR_IO;
2919       goto jleave;
2920    }
2921 
2922    if((nfi = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
2923       n_perr(fstcp->fstc_filename, 0);
2924       n_pstate_err_no = su_ERR_IO;
2925    }
2926 
2927    mx_fs_tmp_release(fstcp);
2928 
2929    if(nfi == NIL)
2930       goto jerr_o;
2931 
2932    if((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL){
2933       n_pstate_err_no = su_ERR_IO;
2934       goto jerr_io;
2935    }
2936 
2937    /* C99 */{
2938       char const *cp;
2939 
2940       if((cp = ok_vlook(on_resend_enter)) != NULL){
2941          /*setup_from_and_sender(hp);*/
2942          temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
2943             hp);
2944       }
2945    }
2946 
2947    su_mem_set(&sb, 0, sizeof sb);
2948    sb.sb_to = to;
2949    sb.sb_input = nfi;
2950    sb.sb_urlp = urlp;
2951 #ifdef mx_HAVE_NET
2952    sb.sb_credp = &cc;
2953 #endif
2954 
2955    /* All the complicated address massage things happened in the callee(s) */
2956    if(!_sendout_error &&
2957          count_nonlocal(to) > 0 && !_sendbundle_setup_creds(&sb, FAL0)){
2958       /* ..wait until we can write DEAD */
2959       n_pstate_err_no = su_ERR_INVAL;
2960       _sendout_error = -1;
2961    }
2962 
2963    if(!a_sendout_infix_resend(ibuf, nfo, mp, to, add_resent)){
2964 jfail_dead:
2965       savedeadletter(nfi, TRU1);
2966       n_err(_("... message not sent\n"));
2967 jerr_io:
2968       mx_fs_close(nfi);
2969       nfi = NIL;
2970 jerr_o:
2971       mx_fs_close(nfo);
2972       _sendout_error = TRU1;
2973       goto jleave;
2974    }
2975 
2976    if(_sendout_error < 0)
2977       goto jfail_dead;
2978 
2979    mx_fs_close(nfo);
2980    rewind(nfi);
2981 
2982    /* C99 */{
2983       boole b, c;
2984 
2985       /* Deliver pipe and file addressees */
2986       b = (ok_blook(record_files) && count(to) > 0);
2987       to = a_sendout_file_a_pipe(to, nfi, &_sendout_error);
2988 
2989       if(_sendout_error)
2990          savedeadletter(nfi, FAL0);
2991 
2992       to = elide(to); /* XXX only to drop GDELs due a_sendout_file_a_pipe()! */
2993       c = (count(to) > 0);
2994 
2995       if(b || c){
2996          if(!ok_blook(record_resent) || a_sendout_mightrecord(nfi, NIL, TRU1)){
2997             sb.sb_to = to;
2998             /*sb.sb_input = nfi;*/
2999             b = FAL0;
3000             if(!c || a_sendout_transfer(&sb, TRU1, &b))
3001                rv = OKAY;
3002             else if(b && _sendout_error == 0){
3003                _sendout_error = b;
3004                savedeadletter(nfi, FAL0);
3005             }
3006          }
3007       }else if(!_sendout_error)
3008          rv = OKAY;
3009    }
3010 
3011    n_sigman_cleanup_ping(&sm);
3012 jleave:
3013    if(nfi != NIL){
3014       char const *cp;
3015 
3016       mx_fs_close(nfi);
3017 
3018       if(ibuf != NULL){
3019          if((cp = ok_vlook(on_resend_cleanup)) != NULL)
3020             temporary_compose_mode_hook_call(cp, NULL, NULL);
3021 
3022          temporary_compose_mode_hook_unroll();
3023       }
3024    }
3025 
3026    if(_sendout_error){
3027       n_psonce |= n_PSO_SEND_ERROR;
3028       n_exit_status |= n_EXIT_SEND_ERROR;
3029    }
3030    if(rv == OKAY)
3031       n_pstate_err_no = su_ERR_NONE;
3032 
3033    NYD_OU;
3034    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
3035    return rv;
3036 }
3037 
3038 FL void
savedeadletter(FILE * fp,boole fflush_rewind_first)3039 savedeadletter(FILE *fp, boole fflush_rewind_first){
3040    struct n_string line;
3041    int c;
3042    enum {a_NONE, a_INIT = 1<<0, a_BODY = 1<<1, a_NL = 1<<2} flags;
3043    ul bytes, lines;
3044    FILE *dbuf;
3045    char const *cp, *cpq;
3046    NYD_IN;
3047 
3048    if(!ok_blook(save))
3049       goto jleave;
3050 
3051    if(fflush_rewind_first){
3052       fflush(fp);
3053       rewind(fp);
3054    }
3055    if(fsize(fp) == 0)
3056       goto jleave;
3057 
3058    cp = n_getdeadletter();
3059    cpq = n_shexp_quote_cp(cp, FAL0);
3060 
3061    if(n_poption & n_PO_D){
3062       n_err(_(">>> Would (try to) write $DEAD %s\n"), cpq);
3063       goto jleave;
3064    }
3065 
3066    if((dbuf = mx_fs_open(cp, "w")) == NIL){
3067       n_perr(_("Cannot save to $DEAD"), 0);
3068       goto jleave;
3069    }
3070    /* XXX Natomic */
3071    mx_file_lock(fileno(dbuf), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);
3072 
3073    fprintf(n_stdout, "%s ", cpq);
3074    fflush(n_stdout);
3075 
3076    /* TODO savedeadletter() non-conforming: should check whether we have any
3077     * TODO headers, if not we need to place "something", anything will do.
3078     * TODO MIME is completely missing, we use MBOXO quoting!!  Yuck.
3079     * TODO I/O error handling missing.  Yuck! */
3080    n_string_reserve(n_string_creat_auto(&line), 2 * SEND_LINESIZE);
3081    bytes = (ul)fprintf(dbuf, "From %s %s",
3082          ok_vlook(LOGNAME), time_current.tc_ctime);
3083    lines = 1;
3084    for(flags = a_NONE, c = '\0'; c != EOF; bytes += line.s_len, ++lines){
3085       n_string_trunc(&line, 0);
3086       while((c = getc(fp)) != EOF && c != '\n')
3087          n_string_push_c(&line, c);
3088 
3089       /* TODO It may be that we have only some plain text.  It may be that we
3090        * TODO have a complete MIME encoded message.  We don't know, and we
3091        * TODO have no usable mechanism to dig it!!  We need v15! */
3092       if(!(flags & a_INIT)){
3093          uz i;
3094 
3095          /* Throw away leading empty lines! */
3096          if(line.s_len == 0)
3097             continue;
3098          for(i = 0; i < line.s_len; ++i){
3099             if(fieldnamechar(line.s_dat[i]))
3100                continue;
3101             if(line.s_dat[i] == ':'){
3102                flags |= a_INIT;
3103                break;
3104             }else{
3105                /* We have no headers, this is already a body line! */
3106                flags |= a_INIT | a_BODY;
3107                break;
3108             }
3109          }
3110          /* Well, i had to check whether the RFC allows this.  Assume we've
3111           * passed the headers, too, then! */
3112          if(i == line.s_len)
3113             flags |= a_INIT | a_BODY;
3114       }
3115       if(flags & a_BODY){
3116          if(line.s_len >= 5 && !su_mem_cmp(line.s_dat, "From ", 5))
3117             n_string_unshift_c(&line, '>');
3118       }
3119       if(line.s_len == 0)
3120          flags |= a_BODY | a_NL;
3121       else
3122          flags &= ~a_NL;
3123 
3124       n_string_push_c(&line, '\n');
3125       fwrite(line.s_dat, sizeof *line.s_dat, line.s_len, dbuf);
3126    }
3127    if(!(flags & a_NL)){
3128       putc('\n', dbuf);
3129       ++bytes;
3130       ++lines;
3131    }
3132    n_string_gut(&line);
3133 
3134    mx_fs_close(dbuf);
3135    fprintf(n_stdout, "%lu/%lu\n", lines, bytes);
3136    fflush(n_stdout);
3137 
3138    rewind(fp);
3139 jleave:
3140    NYD_OU;
3141 }
3142 
3143 #undef SEND_LINESIZE
3144 
3145 #include "su/code-ou.h"
3146 /* s-it-mode */
3147