1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
47 ((1024 / B64_ENCODE_INPUT_PER_LINE) * B64_ENCODE_INPUT_PER_LINE)
49 static char *send_boundary
;
50 static bool_t _senderror
;
52 static enum okay
_putname(char const *line
, enum gfield w
,
53 enum sendaction action
, size_t *gotcha
,
54 char const *prefix
, FILE *fo
, struct name
**xp
);
56 /* Get an encoding flag based on the given string */
57 static char const * _get_encoding(const enum conversion convert
);
59 /* Write an attachment to the file buffer, converting to MIME */
60 static int _attach_file(struct attachment
*ap
, FILE *fo
);
61 static int __attach_file(struct attachment
*ap
, FILE *fo
);
63 static char const ** _prepare_mta_args(struct name
*to
, struct header
*hp
);
65 static struct name
*fixhead(struct header
*hp
, struct name
*tolist
);
66 static int put_signature(FILE *fo
, int convert
);
67 static int attach_message(struct attachment
*ap
, FILE *fo
);
68 static int make_multipart(struct header
*hp
, int convert
, FILE *fi
, FILE *fo
,
69 const char *contenttype
, const char *charset
);
70 static FILE *infix(struct header
*hp
, FILE *fi
);
71 static int savemail(char const *name
, FILE *fi
);
72 static int sendmail_internal(void *v
, int recipient_record
);
73 static enum okay
transfer(struct name
*to
, FILE *input
, struct header
*hp
);
74 static enum okay
start_mta(struct name
*to
, FILE *input
, struct header
*hp
);
75 static void message_id(FILE *fo
, struct header
*hp
);
76 static int fmt(char const *str
, struct name
*np
, FILE *fo
, int comma
,
77 int dropinvalid
, int domime
);
78 static int infix_resend(FILE *fi
, FILE *fo
, struct message
*mp
,
79 struct name
*to
, int add_resent
);
82 _putname(char const *line
, enum gfield w
, enum sendaction action
,
83 size_t *gotcha
, char const *prefix
, FILE *fo
, struct name
**xp
)
88 np
= lextract(line
, GEXTRA
|GFULL
);
93 else if (fmt(prefix
, np
, fo
, w
& GCOMMA
, 0, action
!= SEND_TODISP
))
101 _get_encoding(enum conversion
const convert
)
105 case CONV_7BIT
: ret
= "7bit"; break;
106 case CONV_8BIT
: ret
= "8bit"; break;
107 case CONV_TOQP
: ret
= "quoted-printable"; break;
108 case CONV_TOB64
: ret
= "base64"; break;
109 default: ret
= NULL
; break;
115 _attach_file(struct attachment
*ap
, FILE *fo
)
117 /* TODO of course, the MIME classification needs to performed once
118 * only, not for each and every charset anew ... ;-// */
120 char *charset_iter_orig
[2];
123 /* Is this already in target charset? */
124 if (ap
->a_conv
== AC_TMPFILE
) {
125 err
= __attach_file(ap
, fo
);
130 /* We "consume" *ap*, so directly adjust it as we need it */
131 if (ap
->a_conv
== AC_FIX_INCS
)
132 ap
->a_charset
= ap
->a_input_charset
;
134 if ((offs
= ftell(fo
)) < 0) {
139 charset_iter_recurse(charset_iter_orig
);
140 for (charset_iter_reset(NULL
); charset_iter_next() != NULL
;) {
141 err
= __attach_file(ap
, fo
);
142 if (err
== 0 || (err
!= EILSEQ
&& err
!= EINVAL
))
145 if (fseek(fo
, offs
, SEEK_SET
) < 0) {
149 if (ap
->a_conv
!= AC_DEFAULT
) {
153 ap
->a_charset
= NULL
;
155 charset_iter_restore(charset_iter_orig
);
161 __attach_file(struct attachment
*ap
, FILE *fo
) /* XXX linelength */
163 int err
= 0, do_iconv
;
166 enum conversion convert
;
168 size_t bufsize
, lncnt
, inlen
;
170 /* Either charset-converted temporary file, or plain path */
171 if (ap
->a_conv
== AC_TMPFILE
) {
173 assert(ftell(fi
) == 0x0l
);
174 } else if ((fi
= Fopen(ap
->a_name
, "r")) == NULL
) {
180 /* MIME part header for attachment */
181 { char const *bn
= ap
->a_name
, *ct
;
183 if ((ct
= strrchr(bn
, '/')) != NULL
)
185 ct
= ap
->a_content_type
;
186 charset
= ap
->a_charset
;
187 convert
= mime_classify_file(fi
, (char const**)&ct
, &charset
,
189 if (charset
== NULL
|| ap
->a_conv
== AC_FIX_INCS
||
190 ap
->a_conv
== AC_TMPFILE
)
193 if (fprintf(fo
, "\n--%s\nContent-Type: %s", send_boundary
, ct
)
197 if (charset
== NULL
) {
198 if (putc('\n', fo
) == EOF
)
200 } else if (fprintf(fo
, "; charset=%s\n", charset
) < 0)
203 if (fprintf(fo
, "Content-Transfer-Encoding: %s\n"
204 "Content-Disposition: %s;\n filename=\"",
205 _get_encoding(convert
),
206 ap
->a_content_disposition
) < 0)
208 if (xmime_write(bn
, strlen(bn
), fo
, CONV_TOHDR
, TD_NONE
, NULL
)
211 if (fwrite("\"\n", sizeof(char), 2, fo
) != 2 * sizeof(char))
214 if (ap
->a_content_id
!= NULL
&& fprintf(fo
, "Content-ID: %s\n",
215 ap
->a_content_id
) < 0)
218 if (ap
->a_content_description
!= NULL
&& fprintf(fo
,
219 "Content-Description: %s\n",
220 ap
->a_content_description
) < 0)
223 if (putc('\n', fo
) == EOF
) {
224 jerr_header
: err
= errno
;
230 if (iconvd
!= (iconv_t
)-1)
231 n_iconv_close(iconvd
);
233 char const *tcs
= charset_get_lc();
234 if (asccasecmp(charset
, tcs
) != 0 &&
235 (iconvd
= n_iconv_open(charset
, tcs
))
236 == (iconv_t
)-1 && (err
= errno
) != 0) {
238 fprintf(stderr
, tr(179,
239 "Cannot convert from %s to %s\n"),
242 perror("iconv_open");
249 buf
= smalloc(bufsize
);
250 if (convert
== CONV_TOQP
252 || iconvd
!= (iconv_t
)-1
257 if (convert
== CONV_TOQP
259 || iconvd
!= (iconv_t
)-1
262 if (fgetline(&buf
, &bufsize
, &lncnt
, &inlen
, fi
, 0)
265 } else if ((inlen
= fread(buf
, sizeof *buf
, bufsize
, fi
)) == 0)
267 if (xmime_write(buf
, inlen
, fo
, convert
, TD_ICONV
, NULL
) < 0) {
277 if (ap
->a_conv
!= AC_TMPFILE
)
284 _prepare_mta_args(struct name
*to
, struct header
*hp
)
286 size_t j
, i
= 4 + smopts_count
+ 2 + count(to
) + 1;
287 char const **args
= salloc(i
* sizeof(char*));
289 args
[0] = value("sendmail-progname");
290 if (args
[0] == NULL
|| *args
[0] == '\0')
291 args
[0] = SENDMAIL_PROGNAME
;
297 if (options
& OPT_VERBOSE
)
300 for (j
= 0; j
< smopts_count
; ++j
, ++i
)
303 /* -r option? We may only pass skinned addresses, which is why we do
304 * not simply call myorigin() (TODO myorigin shouldn't fullname!) */
305 if (options
& OPT_r_FLAG
) {
308 if (option_r_arg
[0] != '\0')
309 froma
= option_r_arg
;
310 else if (hp
->h_from
!= NULL
)
311 froma
= hp
->h_from
->n_name
;
313 froma
= myorigin(hp
);
320 /* Receivers follow */
321 for (; to
!= NULL
; to
= to
->n_flink
)
322 if ((to
->n_type
& GDEL
) == 0)
323 args
[i
++] = to
->n_name
;
329 * Fix the header by glopping all of the expanded names from
330 * the distribution list into the appropriate fields.
333 fixhead(struct header
*hp
, struct name
*tolist
)
337 tolist
= elide(tolist
);
339 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= NULL
;
340 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
)
341 if (np
->n_type
& GDEL
) {
342 #ifdef HAVE_DEBUG /* TODO LEGACY CODE */
343 assert(0); /* Shouldn't happen here, but later on :)) */
347 } else switch (np
->n_type
& GMASK
) {
349 hp
->h_to
= cat(hp
->h_to
, ndup(np
, np
->n_type
|GFULL
));
352 hp
->h_cc
= cat(hp
->h_cc
, ndup(np
, np
->n_type
|GFULL
));
355 hp
->h_bcc
= cat(hp
->h_bcc
, ndup(np
, np
->n_type
|GFULL
));
364 * Put the signature file at fo. TODO send layer rewrite: *integrate in body*!!
367 put_signature(FILE *fo
, int convert
)
369 char *sig
, buf
[INFIX_BUF
], c
= '\n';
373 sig
= value("signature");
374 if (sig
== NULL
|| *sig
== '\0')
376 else if ((sig
= file_expand(sig
)) == NULL
)
378 if ((fsig
= Fopen(sig
, "r")) == NULL
) {
382 while ((sz
= fread(buf
, sizeof *buf
, INFIX_BUF
, fsig
)) != 0) {
384 if (xmime_write(buf
, sz
, fo
, convert
, TD_NONE
, NULL
) < 0) {
402 * Attach a message to the file buffer.
405 attach_message(struct attachment
*ap
, FILE *fo
)
409 fprintf(fo
, "\n--%s\n"
410 "Content-Type: message/rfc822\n"
411 "Content-Disposition: inline\n", send_boundary
);
412 if (ap
->a_content_description
!= NULL
)
413 fprintf(fo
, "Content-Description: %s\n",
414 ap
->a_content_description
);
417 mp
= &message
[ap
->a_msgno
- 1];
419 if (sendmp(mp
, fo
, 0, NULL
, SEND_RFC822
, NULL
) < 0)
425 * Generate the body of a MIME multipart message.
428 make_multipart(struct header
*hp
, int convert
, FILE *fi
, FILE *fo
,
429 const char *contenttype
, const char *charset
)
431 struct attachment
*att
;
433 fputs("This is a multi-part message in MIME format.\n", fo
);
434 if (fsize(fi
) != 0) {
436 size_t sz
, bufsize
, cnt
;
438 fprintf(fo
, "\n--%s\n", send_boundary
);
439 fprintf(fo
, "Content-Type: %s", contenttype
);
441 fprintf(fo
, "; charset=%s", charset
);
442 fprintf(fo
, "\nContent-Transfer-Encoding: %s\n"
443 "Content-Disposition: inline\n\n",
444 _get_encoding(convert
));
445 buf
= smalloc(bufsize
= INFIX_BUF
);
446 if (convert
== CONV_TOQP
448 || iconvd
!= (iconv_t
)-1
456 if (convert
== CONV_TOQP
458 || iconvd
!= (iconv_t
)-1
461 if (fgetline(&buf
, &bufsize
, &cnt
, &sz
, fi
, 0)
465 sz
= fread(buf
, sizeof *buf
, bufsize
, fi
);
470 if (xmime_write(buf
, sz
, fo
, convert
, TD_ICONV
, NULL
)
480 put_signature(fo
, convert
);
482 for (att
= hp
->h_attach
; att
!= NULL
; att
= att
->a_flink
) {
484 if (attach_message(att
, fo
) != 0)
487 if (_attach_file(att
, fo
) != 0)
491 /* the final boundary with two attached dashes */
492 fprintf(fo
, "\n--%s--\n", send_boundary
);
497 * Prepend a header in front of the collected stuff
498 * and return the new file.
501 infix(struct header
*hp
, FILE *fi
) /* TODO check */
506 char const *tcs
, *convhdr
= NULL
;
508 enum conversion convert
;
509 char const *contenttype
, *charset
= NULL
;
512 if ((nfo
= Ftemp(&tempMail
, "Rs", "w", 0600, 1)) == NULL
) {
513 perror(tr(178, "temporary mail file"));
516 if ((nfi
= Fopen(tempMail
, "r")) == NULL
) {
525 contenttype
= "text/plain"; /* XXX mail body - always text/plain */
526 convert
= mime_classify_file(fi
, &contenttype
, &charset
, &do_iconv
);
529 tcs
= charset_get_lc();
530 if ((convhdr
= need_hdrconv(hp
, GTO
|GSUBJECT
|GCC
|GBCC
|GIDENT
)) != 0) {
531 if (iconvd
!= (iconv_t
)-1)
532 n_iconv_close(iconvd
);
533 if (asccasecmp(convhdr
, tcs
) != 0 &&
534 (iconvd
= n_iconv_open(convhdr
, tcs
))
535 == (iconv_t
)-1 && errno
!= 0) {
537 fprintf(stderr
, tr(179,
538 "Cannot convert from %s to %s\n"),
541 perror("iconv_open");
548 GTO
|GSUBJECT
|GCC
|GBCC
|GNL
|GCOMMA
|GUA
|GMIME
549 |GMSGID
|GIDENT
|GREF
|GDATE
,
550 SEND_MBOX
, convert
, contenttype
, charset
)) {
554 if (iconvd
!= (iconv_t
)-1)
555 n_iconv_close(iconvd
);
560 if (iconvd
!= (iconv_t
)-1)
561 n_iconv_close(iconvd
);
562 if (do_iconv
&& charset
!= NULL
) { /*TODO charset->mime_classify_file*/
564 if (asccasecmp(charset
, tcs
) != 0 &&
565 (iconvd
= n_iconv_open(charset
, tcs
))
566 == (iconv_t
)-1 && (err
= errno
) != 0) {
568 fprintf(stderr
, tr(179,
569 "Cannot convert from %s to %s\n"),
572 perror("iconv_open");
578 if (hp
->h_attach
!= NULL
) {
579 if (make_multipart(hp
, convert
, fi
, nfo
, contenttype
, charset
)
584 if (iconvd
!= (iconv_t
)-1)
585 n_iconv_close(iconvd
);
590 size_t sz
, bufsize
, cnt
;
593 if (convert
== CONV_TOQP
595 || iconvd
!= (iconv_t
)-1
601 buf
= smalloc(bufsize
= INFIX_BUF
);
603 if (convert
== CONV_TOQP
605 || iconvd
!= (iconv_t
)-1
608 if (fgetline(&buf
, &bufsize
, &cnt
, &sz
, fi
, 0)
612 sz
= fread(buf
, sizeof *buf
, bufsize
, fi
);
616 if (xmime_write(buf
, sz
, nfo
, convert
, TD_ICONV
, NULL
)
621 if (iconvd
!= (iconv_t
)-1)
622 n_iconv_close(iconvd
);
633 if (iconvd
!= (iconv_t
)-1)
634 n_iconv_close(iconvd
);
639 put_signature(nfo
, convert
); /* XXX if (text/) !! */
642 if (iconvd
!= (iconv_t
)-1)
643 n_iconv_close(iconvd
);
647 perror(tr(180, "temporary mail file"));
660 * Save the outgoing mail on the passed file.
665 savemail(char const *name
, FILE *fi
)
669 size_t bufsize
, buflen
, cnt
;
670 int prependnl
= 0, error
= 0;
672 buf
= smalloc(bufsize
= LINESIZE
);
673 if ((fo
= Zopen(name
, "a+", NULL
)) == NULL
) {
674 if ((fo
= Zopen(name
, "wx", NULL
)) == NULL
) {
680 if (fseek(fo
, -2L, SEEK_END
) == 0) {
681 switch (fread(buf
, sizeof *buf
, 2, fo
)) {
683 if (buf
[1] != '\n') {
707 fprintf(fo
, "From %s %s", myname
, time_current
.tc_ctime
);
712 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fi
, 0) != NULL
) {
713 #ifdef HAVE_DEBUG /* TODO assert legacy */
714 assert(!is_head(buf
, buflen
));
716 if (is_head(buf
, buflen
))
719 fwrite(buf
, sizeof *buf
, buflen
, fo
);
721 if (buflen
&& *(buf
+ buflen
- 1) != '\n')
738 * Interface between the argument list and the mail1 routine
739 * which does all the dirty work.
742 mail(struct name
*to
, struct name
*cc
, struct name
*bcc
,
743 char *subject
, struct attachment
*attach
,
744 char *quotefile
, int recipient_record
)
749 memset(&head
, 0, sizeof head
);
750 /* The given subject may be in RFC1522 format. */
751 if (subject
!= NULL
) {
753 in
.l
= strlen(subject
);
754 mime_fromhdr(&in
, &out
, /* TODO ??? TD_ISPR |*/ TD_ICONV
);
755 head
.h_subject
= out
.s
;
757 if (! (options
& OPT_t_FLAG
)) {
762 head
.h_attach
= attach
;
763 mail1(&head
, 0, NULL
, quotefile
, recipient_record
, 0);
770 * Send mail to a bunch of user names. The interface is through
771 * the mail routine below.
774 sendmail_internal(void *v
, int recipient_record
)
779 memset(&head
, 0, sizeof head
);
780 head
.h_to
= lextract(str
, GTO
|GFULL
);
781 mail1(&head
, 0, NULL
, NULL
, recipient_record
, 0);
788 return sendmail_internal(v
, 0);
794 return sendmail_internal(v
, 1);
798 transfer(struct name
*to
, FILE *input
, struct header
*hp
)
800 char o
[LINESIZE
], *cp
;
807 snprintf(o
, sizeof o
, "smime-encrypt-%s", np
->n_name
);
808 if ((cp
= value(o
)) != NULL
) {
812 if ((ef
= smime_encrypt(input
, cp
, np
->n_name
)) != 0) {
813 nt
= ndup(np
, np
->n_type
& ~(GFULL
|GSKIN
));
814 if (start_mta(nt
, ef
, hp
) != OKAY
)
819 fprintf(stderr
, tr(225,
820 "No SSL support compiled in.\n"));
823 fprintf(stderr
, tr(38,
824 "Message not sent to <%s>\n"),
832 np
->n_flink
->n_blink
= np
->n_blink
;
834 np
->n_blink
->n_flink
= np
->n_flink
;
844 if (value("smime-force-encryption") ||
845 start_mta(to
, input
, hp
) != OKAY
)
852 * Start the Mail Transfer Agent
853 * mailing to namelist and stdin redirected to input.
856 start_mta(struct name
*to
, FILE *input
, struct header
*hp
)
859 char *user
= NULL
, *password
= NULL
, *skinned
= NULL
;
861 char const **args
= NULL
, **t
, *mta
;
868 if ((smtp
= value("smtp")) == NULL
) {
869 if ((mta
= value("sendmail")) != NULL
) {
870 if ((mta
= file_expand(mta
)) == NULL
)
875 args
= _prepare_mta_args(to
, hp
);
876 if (options
& OPT_DEBUG
) {
877 printf(tr(181, "Sendmail arguments:"));
878 for (t
= args
; *t
!= NULL
; t
++)
879 printf(" \"%s\"", *t
);
885 mta
= NULL
; /* Silence cc */
887 fputs(tr(194, "No SMTP support compiled in.\n"), stderr
);
890 skinned
= skin(myorigin(hp
));
891 if ((user
= smtp_auth_var("-user", skinned
)) != NULL
&&
892 (password
= smtp_auth_var("-password",
894 password
= getpassword(NULL
);
899 * Fork, set up the temporary mail file as standard
900 * input for "mail", and exec with the user list we generated
903 if ((pid
= fork()) == -1) {
905 jstop
: savedeadletter(input
, 0);
911 sigaddset(&nset
, SIGHUP
);
912 sigaddset(&nset
, SIGINT
);
913 sigaddset(&nset
, SIGQUIT
);
914 sigaddset(&nset
, SIGTSTP
);
915 sigaddset(&nset
, SIGTTIN
);
916 sigaddset(&nset
, SIGTTOU
);
917 freopen("/dev/null", "r", stdin
);
920 prepare_child(&nset
, 0, 1);
921 if (smtp_mta(smtp
, to
, input
, hp
,
922 user
, password
, skinned
) == 0)
926 prepare_child(&nset
, fileno(input
), -1);
927 /* If *record* is set then savemail() will move the
928 * file position; it'll call rewind(), but that may
929 * optimize away the systemcall if possible, and since
930 * dup2() shares the position with the original FD the
931 * MTA may end up reading nothing */
932 lseek(0, 0, SEEK_SET
);
933 execv(mta
, UNCONST(args
));
938 savedeadletter(input
, 1);
939 fputs(tr(182, ". . . message not sent.\n"), stderr
);
942 if ((options
& (OPT_DEBUG
|OPT_VERBOSE
|OPT_BATCH_FLAG
)) ||
944 if (wait_child(pid
, NULL
))
957 * Record outgoing mail if instructed to do so.
960 mightrecord(FILE *fp
, struct name
*to
, int recipient_record
)
965 if (recipient_record
) {
966 size_t i
= strlen(cq
= skinned_name(to
)) + 1;
969 for (cq
= cp
; *cq
&& *cq
!= '@'; cq
++)
973 cp
= value("record");
980 if (value("outfolder") && *ep
!= '/' && *ep
!= '+' &&
981 which_protocol(ep
) == PROTO_FILE
) {
982 size_t i
= strlen(cp
);
985 memcpy(cq
+ 1, cp
, i
+ 1);
987 ep
= expand(cp
); /* TODO file_expand() possible? */
993 if (savemail(ep
, fp
) != 0) {
994 jbail
: fprintf(stderr
, tr(285,
995 "Failed to save message in %s - "
996 "message not sent\n"), ep
);
998 savedeadletter(fp
, 1);
1006 * Mail a message on standard input to the people indicated
1007 * in the passed header. (Internal interface).
1010 mail1(struct header
*hp
, int printheaders
, struct message
*quote
,
1011 char *quotefile
, int recipient_record
, int doprefix
)
1013 enum okay ok
= STOP
;
1016 int dosign
= -1, err
;
1021 /* Update some globals we likely need first */
1022 time_current_update(&time_current
, TRU1
);
1025 if ((cp
= value("autocc")) != NULL
&& *cp
)
1026 hp
->h_cc
= cat(hp
->h_cc
, checkaddrs(lextract(cp
, GCC
|GFULL
)));
1027 if ((cp
= value("autobcc")) != NULL
&& *cp
)
1028 hp
->h_bcc
= cat(hp
->h_bcc
, checkaddrs(lextract(cp
,GBCC
|GFULL
)));
1031 * Collect user's mail from standard input.
1032 * Get the result as mtf.
1034 mtf
= collect(hp
, printheaders
, quote
, quotefile
, doprefix
);
1038 if (options
& OPT_INTERACTIVE
) {
1039 err
= (value("bsdcompat") || value("askatend"));
1043 ++err
, grab_headers(hp
, GCC
, 1);
1044 if (value("askbcc"))
1045 ++err
, grab_headers(hp
, GBCC
, 1);
1046 if (value("askattach"))
1047 ++err
, hp
->h_attach
= edit_attachments(hp
->h_attach
);
1048 if (value("asksign"))
1049 ++err
, dosign
= yorn(tr(35,
1050 "Sign this message (y/n)? "));
1053 printf(tr(183, "EOT\n"));
1058 if (fsize(mtf
) == 0) {
1059 if (options
& OPT_E_FLAG
)
1061 if (hp
->h_subject
== NULL
)
1063 "No message, no subject; hope that's ok\n"));
1064 else if (value("bsdcompat") || value("bsdmsgs"))
1065 printf(tr(185, "Null message body; hope that's ok\n"));
1069 dosign
= (value("smime-sign") != NULL
);
1072 fprintf(stderr
, tr(225, "No SSL support compiled in.\n"));
1077 /* XXX Update time_current again; once collect() offers editing of more
1078 * XXX headers, including Date:, this must only happen if Date: is the
1079 * XXX same that it was before collect() (e.g., postponing etc.).
1080 * XXX But *do* update otherwise because the mail seems to be backdated
1081 * XXX if the user edited some time, which looks odd and it happened
1082 * XXX to me that i got mis-dated response mails due to that... */
1083 time_current_update(&time_current
, TRU1
);
1085 /* TODO hrmpf; the MIME/send layer rewrite MUST address the init crap:
1086 * TODO setup the header ONCE; note this affects edit.c, collect.c ...,
1087 * TODO but: offer a hook that rebuilds/expands/checks/fixates all
1088 * TODO header fields ONCE, call that ONCE after user editing etc. has
1089 * TODO completed (one edit cycle) */
1092 * Take the user names from the combined to and cc lists and do all the
1093 * alias processing. The POSIX standard says:
1094 * The names shall be substituted when alias is used as a recipient
1095 * address specified by the user in an outgoing message (that is,
1096 * other recipients addressed indirectly through the reply command
1097 * shall not be substituted in this manner).
1098 * S-nail thus violates POSIX, as has been pointed out correctly by
1099 * Martin Neitzel, but logic, usability und intellectual penetration of
1100 * POSIX standards is disputable anyway. Go for user friendliness.
1103 /* Do alias expansion on Reply-To: members, too (Martin Neitzel) */
1104 /* TODO puthead() YET (!!! see ONCE note above) expands the value, but
1105 * TODO doesn't perform alias expansion; encapsulate in the ONCE-o */
1106 if (hp
->h_replyto
== NULL
&& (cp
= value("replyto")) != NULL
)
1107 hp
->h_replyto
= checkaddrs(lextract(cp
, GEXTRA
|GFULL
));
1108 if (hp
->h_replyto
!= NULL
)
1109 hp
->h_replyto
= elide(usermap(hp
->h_replyto
, TRU1
));
1112 * TODO what happens now is that all recipients are merged into
1113 * TODO a duplicated list with expanded aliases, then this list is
1114 * TODO splitted again into the three individual recipient lists (with
1115 * TODO duplicates removed).
1116 * TODO later on we use the merged list for outof() pipe/file saving,
1117 * TODO then we eliminate duplicates (again) and then we use that one
1118 * TODO for mightrecord() and transfer(), and again. ... Please ...
1121 * NOTE: Due to elide() in fixhead(), ENSURE to,cc,bcc order of to!,
1122 * because otherwise the recipients will be "degraded" if they occur
1125 to
= usermap(cat(hp
->h_to
, cat(hp
->h_cc
, hp
->h_bcc
)), FAL0
);
1127 fprintf(stderr
, tr(186, "No recipients specified\n"));
1130 to
= fixhead(hp
, to
);
1133 * 'Bit ugly kind of control flow until we find a charset that does it.
1134 * XXX Can maybe be done nicer once we have a carrier struct instead
1137 for (charset_iter_reset(hp
->h_charset
);;) {
1138 if (charset_iter_next() == NULL
)
1140 else if ((nmtf
= infix(hp
, mtf
)) != NULL
)
1142 else if ((err
= errno
) == EILSEQ
|| err
== EINVAL
) {
1152 savedeadletter(mtf
, 1);
1153 fputs(tr(187, ". . . message not sent.\n"), stderr
);
1160 if ((nmtf
= smime_sign(mtf
, hp
)) == NULL
)
1168 * Look through the recipient list for names with /'s
1169 * in them which we write to as files directly.
1171 to
= outof(to
, mtf
, hp
, &_senderror
);
1173 savedeadletter(mtf
, 0);
1174 to
= elide(to
); /* XXX needed only to drop GDELs due to outof()! */
1175 if (count(to
) == 0) {
1181 if (mightrecord(mtf
, to
, recipient_record
) != OKAY
)
1183 ok
= transfer(to
, mtf
, hp
);
1189 exit_status
|= EXIT_SEND_ERROR
;
1194 * Create a Message-Id: header field.
1195 * Use either the host name or the from address.
1198 message_id(FILE *fo
, struct header
*hp
)
1204 if (boption("message-id-disable"))
1206 if ((h
= voption("hostname")) != NULL
)
1208 else if ((h
= skin(myorigin(hp
))) != NULL
&& strchr(h
, '@') != NULL
)
1214 tmp
= &time_current
.tc_gm
;
1215 fprintf(fo
, "Message-ID: <%04d%02d%02d%02d%02d%02d.%s%c%s>\n",
1216 tmp
->tm_year
+ 1900, tmp
->tm_mon
+ 1, tmp
->tm_mday
,
1217 tmp
->tm_hour
, tmp
->tm_min
, tmp
->tm_sec
,
1218 getrandstring(rl
), (rl
== 16 ? '%' : '@'), h
);
1224 * Create a Date: header field.
1225 * We compare the localtime() and gmtime() results to get the timezone,
1226 * because numeric timezones are easier to read and because $TZ is
1227 * not set on most GNU systems.
1230 mkdate(FILE *fo
, const char *field
)
1233 int tzdiff
, tzdiff_hour
, tzdiff_min
;
1235 tzdiff
= time_current
.tc_time
- mktime(&time_current
.tc_gm
);
1236 tzdiff_hour
= (int)(tzdiff
/ 60);
1237 tzdiff_min
= tzdiff_hour
% 60;
1239 tmptr
= &time_current
.tc_local
;
1240 if (tmptr
->tm_isdst
> 0)
1242 return fprintf(fo
, "%s: %s, %02d %s %04d %02d:%02d:%02d %+05d\n",
1244 weekday_names
[tmptr
->tm_wday
],
1245 tmptr
->tm_mday
, month_names
[tmptr
->tm_mon
],
1246 tmptr
->tm_year
+ 1900, tmptr
->tm_hour
,
1247 tmptr
->tm_min
, tmptr
->tm_sec
,
1248 tzdiff_hour
* 100 + tzdiff_min
);
1251 #define FMT_CC_AND_BCC { \
1252 if (hp->h_cc != NULL && w & GCC) { \
1253 if (fmt("Cc:", hp->h_cc, fo, \
1254 w&(GCOMMA|GFILES), 0, \
1255 action!=SEND_TODISP)) \
1259 if (hp->h_bcc != NULL && w & GBCC) { \
1260 if (fmt("Bcc:", hp->h_bcc, fo, \
1261 w&(GCOMMA|GFILES), 0, \
1262 action!=SEND_TODISP)) \
1268 * Dump the to, subject, cc header on the
1269 * passed file buffer.
1272 puthead(struct header
*hp
, FILE *fo
, enum gfield w
,
1273 enum sendaction action
, enum conversion convert
,
1274 char const *contenttype
, char const *charset
)
1279 struct name
*np
, *fromfield
= NULL
, *senderfield
= NULL
;
1281 if ((addr
= value("stealthmua")) != NULL
) {
1282 stealthmua
= (strcmp(addr
, "noagent") == 0) ? -1 : 1;
1287 mkdate(fo
, "Date"), gotcha
++;
1290 if (hp
->h_from
!= NULL
) {
1291 if (fmt("From:", hp
->h_from
, fo
, w
&(GCOMMA
|GFILES
), 0,
1292 action
!=SEND_TODISP
))
1295 fromfield
= hp
->h_from
;
1296 } else if ((addr
= myaddrs(hp
)) != NULL
) {
1297 if (_putname(addr
, w
, action
, &gotcha
, "From:", fo
,
1300 hp
->h_from
= fromfield
;
1302 if (((addr
= hp
->h_organization
) != NULL
||
1303 (addr
= value("ORGANIZATION")) != NULL
) &&
1304 (l
= strlen(addr
)) > 0) {
1305 fwrite("Organization: ", sizeof (char), 14, fo
);
1306 if (xmime_write(addr
, l
, fo
,
1307 action
== SEND_TODISP
?
1308 CONV_NONE
:CONV_TOHDR
,
1309 action
== SEND_TODISP
?
1310 TD_ISPR
|TD_ICONV
:TD_ICONV
,
1316 /* TODO see the ONCE TODO note somewhere around this file;
1317 * TODO but anyway, do NOT perform alias expansion UNLESS
1318 * TODO we are actually sending out! */
1319 if (hp
->h_replyto
!= NULL
) {
1320 if (fmt("Reply-To:", hp
->h_replyto
, fo
, w
& GCOMMA
, 0,
1321 action
!=SEND_TODISP
))
1324 } else if ((addr
= value("replyto")) != NULL
)
1325 if (_putname(addr
, w
, action
, &gotcha
, "Reply-To:", fo
,
1328 if (hp
->h_sender
!= NULL
) {
1329 if (fmt("Sender:", hp
->h_sender
, fo
, w
& GCOMMA
, 0,
1330 action
!=SEND_TODISP
))
1333 senderfield
= hp
->h_sender
;
1334 } else if ((addr
= value("sender")) != NULL
)
1335 if (_putname(addr
, w
, action
, &gotcha
, "Sender:", fo
,
1338 if (check_from_and_sender(fromfield
, senderfield
))
1341 if (hp
->h_to
!= NULL
&& w
& GTO
) {
1342 if (fmt("To:", hp
->h_to
, fo
, w
&(GCOMMA
|GFILES
), 0,
1343 action
!=SEND_TODISP
))
1347 if (value("bsdcompat") == NULL
&& value("bsdorder") == NULL
)
1349 if (hp
->h_subject
!= NULL
&& w
& GSUBJECT
) {
1350 fwrite("Subject: ", sizeof (char), 9, fo
);
1351 if (ascncasecmp(hp
->h_subject
, "re: ", 4) == 0) {
1352 fwrite("Re: ", sizeof (char), 4, fo
);
1353 if (strlen(hp
->h_subject
+ 4) > 0 &&
1354 xmime_write(hp
->h_subject
+ 4,
1355 strlen(hp
->h_subject
+ 4), fo
,
1356 action
== SEND_TODISP
?
1357 CONV_NONE
:CONV_TOHDR
,
1358 action
== SEND_TODISP
?
1359 TD_ISPR
|TD_ICONV
:TD_ICONV
,
1362 } else if (*hp
->h_subject
) {
1363 if (xmime_write(hp
->h_subject
, strlen(hp
->h_subject
),
1364 fo
, action
== SEND_TODISP
?
1365 CONV_NONE
:CONV_TOHDR
,
1366 action
== SEND_TODISP
?
1367 TD_ISPR
|TD_ICONV
:TD_ICONV
,
1372 fwrite("\n", sizeof (char), 1, fo
);
1374 if (value("bsdcompat") || value("bsdorder"))
1376 if (w
& GMSGID
&& stealthmua
<= 0)
1377 message_id(fo
, hp
), gotcha
++;
1378 if ((np
= hp
->h_ref
) != NULL
&& w
& GREF
) {
1379 fmt("References:", np
, fo
, 0, 1, 0);
1383 if (is_addr_invalid(np
, 0) == 0) {
1384 fprintf(fo
, "In-Reply-To: %s\n", np
->n_name
);
1389 if (w
& GUA
&& stealthmua
== 0)
1390 fprintf(fo
, "User-Agent: %s %s\n", uagent
, version
), gotcha
++;
1392 fputs("MIME-Version: 1.0\n", fo
), gotcha
++;
1393 if (hp
->h_attach
!= NULL
) {
1394 send_boundary
= mime_create_boundary();/*TODO carrier*/
1395 fprintf(fo
, "Content-Type: multipart/mixed;\n"
1396 " boundary=\"%s\"\n", send_boundary
);
1398 fprintf(fo
, "Content-Type: %s", contenttype
);
1400 fprintf(fo
, "; charset=%s", charset
);
1401 fprintf(fo
, "\nContent-Transfer-Encoding: %s\n",
1402 _get_encoding(convert
));
1405 if (gotcha
&& w
& GNL
)
1411 * Format the given header line to not exceed 72 characters.
1414 fmt(char const *str
, struct name
*np
, FILE *fo
, int flags
, int dropinvalid
,
1422 } m
= (flags
& GCOMMA
) ? m_COMMA
: 0;
1427 fwrite(str
, sizeof *str
, col
, fo
);
1430 if (col
== 9 && asccasecmp(str
, "reply-to:") == 0) {
1434 if (value("add-file-recipients"))
1436 if ((col
== 3 && ((asccasecmp(str
, "to:") == 0) ||
1437 asccasecmp(str
, "cc:") == 0)) ||
1438 (col
== 4 && asccasecmp(str
, "bcc:") == 0) ||
1440 asccasecmp(str
, "Resent-To:") == 0))
1445 for (; np
!= NULL
; np
= np
->n_flink
) {
1446 if ((m
& m_NOPF
) && is_fileorpipe_addr(np
))
1448 if (is_addr_invalid(np
, ! dropinvalid
)) {
1454 if ((m
& (m_INIT
| m_COMMA
)) == (m_INIT
| m_COMMA
)) {
1459 len
= strlen(np
->n_fullname
);
1460 ++col
; /* The separating space */
1461 if ((m
& m_INIT
) && col
> 1 && col
+ len
> 72) {
1467 m
= (m
& ~m_CSEEN
) | m_INIT
;
1468 len
= xmime_write(np
->n_fullname
, len
, fo
,
1469 domime
?CONV_TOHDR_A
:CONV_NONE
,
1480 * Rewrite a message for resending, adding the Resent-Headers.
1483 infix_resend(FILE *fi
, FILE *fo
, struct message
*mp
, struct name
*to
,
1486 size_t cnt
, c
, bufsize
= 0;
1489 struct name
*fromfield
= NULL
, *senderfield
= NULL
;
1493 * Write the Resent-Fields.
1496 fputs("Resent-", fo
);
1498 if ((cp
= myaddrs(NULL
)) != NULL
) {
1499 if (_putname(cp
, GCOMMA
, SEND_MBOX
, NULL
,
1500 "Resent-From:", fo
, &fromfield
))
1503 if ((cp
= value("sender")) != NULL
) {
1504 if (_putname(cp
, GCOMMA
, SEND_MBOX
, NULL
,
1505 "Resent-Sender:", fo
, &senderfield
))
1508 if (fmt("Resent-To:", to
, fo
, 1, 1, 0))
1510 if ((cp
= value("stealthmua")) == NULL
||
1511 strcmp(cp
, "noagent") == 0) {
1512 fputs("Resent-", fo
);
1513 message_id(fo
, NULL
);
1516 if (check_from_and_sender(fromfield
, senderfield
))
1519 * Write the original headers.
1522 if (fgetline(&buf
, &bufsize
, &cnt
, &c
, fi
, 0) == NULL
)
1524 /* XXX more checks: The From_ line may be seen when resending */
1525 if (ascncasecmp("status: ", buf
, 8) != 0 &&
1526 strncmp("From ", buf
, 5) != 0
1527 /* In the headers, is_head() is actually
1528 * overkill, so a simple ^From_ is sufficient.
1529 * ! is_head(buf, c) */
1531 fwrite(buf
, sizeof *buf
, c
, fo
);
1532 if (cnt
> 0 && *buf
== '\n')
1536 * Write the message body.
1539 if (fgetline(&buf
, &bufsize
, &cnt
, &c
, fi
, 0) == NULL
)
1541 if (cnt
== 0 && *buf
== '\n')
1543 fwrite(buf
, sizeof *buf
, c
, fo
);
1548 perror(tr(188, "temporary mail file"));
1555 resend_msg(struct message
*mp
, struct name
*to
, int add_resent
) /* TODO check */
1557 enum okay ok
= STOP
;
1558 FILE *ibuf
, *nfo
, *nfi
;
1564 /* Update some globals we likely need first */
1565 time_current_update(&time_current
, TRU1
);
1567 memset(&head
, 0, sizeof head
);
1569 if ((to
= checkaddrs(to
)) == NULL
) {
1574 if ((nfo
= Ftemp(&tempMail
, "Rs", "w", 0600, 1)) == NULL
) {
1576 perror(tr(189, "temporary mail file"));
1579 if ((nfi
= Fopen(tempMail
, "r")) == NULL
) {
1588 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
)
1591 to
= fixhead(&head
, to
);
1592 if (infix_resend(ibuf
, nfo
, mp
, head
.h_to
, add_resent
) != 0) {
1593 savedeadletter(nfi
, 1);
1594 fputs(tr(190, ". . . message not sent.\n"), stderr
);
1604 to
= outof(to
, nfi
, &head
, &_senderror
);
1606 savedeadletter(nfi
, 0);
1607 to
= elide(to
); /* TODO should have been done in fixhead()? */
1608 if (count(to
) != 0) {
1609 if (value("record-resent") == NULL
||
1610 mightrecord(nfi
, to
, 0) == OKAY
)
1611 ok
= transfer(to
, nfi
, NULL
);
1612 } else if (! _senderror
)
1617 exit_status
|= EXIT_SEND_ERROR
;