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