SMTP: add some missing translation hooks
[s-mailx.git] / sendout.c
blob1484ca7260a661fe88e6577d18c8b3fc1ae4d310
1 /*
2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #ifndef lint
41 #ifdef DOSCCS
42 static char sccsid[] = "@(#)sendout.c 2.100 (gritter) 3/1/09";
43 #endif
44 #endif /* not lint */
46 #include "rcv.h"
47 #include "extern.h"
48 #include <errno.h>
49 #include <sys/stat.h>
50 #include <fcntl.h>
51 #include <unistd.h>
52 #include <time.h>
53 #include "md5.h"
56 * Mail -- a mail program
58 * Mail to others.
61 static char *send_boundary;
63 static char *getencoding(enum conversion convert);
64 static struct name *fixhead(struct header *hp, struct name *tolist);
65 static int put_signature(FILE *fo, int convert);
66 static int attach_file1(struct attachment *ap, FILE *fo, int dosign);
67 static int attach_file(struct attachment *ap, FILE *fo, int dosign);
68 static int attach_message(struct attachment *ap, FILE *fo, int dosign);
69 static int make_multipart(struct header *hp, int convert, FILE *fi, FILE *fo,
70 const char *contenttype, const char *charset, int dosign);
71 static FILE *infix(struct header *hp, FILE *fi, int dosign);
72 static int savemail(char *name, FILE *fi);
73 static int sendmail_internal(void *v, int recipient_record);
74 static enum okay transfer(struct name *to, struct name *mailargs, FILE *input,
75 struct header *hp);
76 static enum okay start_mta(struct name *to, struct name *mailargs, FILE *input,
77 struct header *hp);
78 static void message_id(FILE *fo, struct header *hp);
79 static int fmt(char *str, struct name *np, FILE *fo, int comma,
80 int dropinvalid, int domime);
81 static int infix_resend(FILE *fi, FILE *fo, struct message *mp,
82 struct name *to, int add_resent);
85 * Generate a boundary for MIME multipart messages.
87 char *
88 makeboundary(void)
90 static char bound[70];
91 time_t now;
93 time(&now);
94 snprintf(bound, sizeof bound, "=_%lx.%s", (long)now, getrandstring(48));
95 send_boundary = bound;
96 return send_boundary;
100 * Get an encoding flag based on the given string.
102 static char *
103 getencoding(enum conversion convert)
105 switch (convert) {
106 case CONV_7BIT:
107 return "7bit";
108 case CONV_8BIT:
109 return "8bit";
110 case CONV_TOQP:
111 return "quoted-printable";
112 case CONV_TOB64:
113 return "base64";
114 default:
115 break;
117 /*NOTREACHED*/
118 return NULL;
122 * Fix the header by glopping all of the expanded names from
123 * the distribution list into the appropriate fields.
125 static struct name *
126 fixhead(struct header *hp, struct name *tolist)
128 struct name *np;
130 hp->h_to = NULL;
131 hp->h_cc = NULL;
132 hp->h_bcc = NULL;
133 for (np = tolist; np != NULL; np = np->n_flink)
134 if ((np->n_type & GMASK) == GTO)
135 hp->h_to = cat(hp->h_to, ndup(np, GFULL));
136 else if ((np->n_type & GMASK) == GCC)
137 hp->h_cc = cat(hp->h_cc, ndup(np, GFULL));
138 else if ((np->n_type & GMASK) == GBCC)
139 hp->h_bcc = cat(hp->h_bcc, ndup(np, GFULL));
140 return tolist;
145 * Do not change, you get incorrect base64 encodings else!
147 #define INFIX_BUF 972
150 * Put the signature file at fo.
152 static int
153 put_signature(FILE *fo, int convert)
155 char *sig, buf[INFIX_BUF], c = '\n';
156 FILE *fsig;
157 size_t sz;
159 sig = value("signature");
160 if (sig == NULL || *sig == '\0')
161 return 0;
162 else
163 sig = expand(sig);
164 if ((fsig = Fopen(sig, "r")) == NULL) {
165 perror(sig);
166 return -1;
168 while ((sz = fread(buf, sizeof *buf, INFIX_BUF, fsig)) != 0) {
169 c = buf[sz - 1];
170 if (mime_write(buf, sz, fo, convert, TD_NONE,
171 NULL, (size_t)0, NULL, NULL)
172 == 0) {
173 perror(sig);
174 Fclose(fsig);
175 return -1;
178 if (ferror(fsig)) {
179 perror(sig);
180 Fclose(fsig);
181 return -1;
183 Fclose(fsig);
184 if (c != '\n')
185 putc('\n', fo);
186 return 0;
190 * Write an attachment to the file buffer, converting to MIME.
192 static int
193 attach_file1(struct attachment *ap, FILE *fo, int dosign)
195 FILE *fi;
196 char *charset = NULL, *contenttype = NULL, *basename;
197 enum conversion convert = CONV_TOB64;
198 int err = 0;
199 enum mimeclean isclean;
200 size_t sz;
201 char *buf;
202 size_t bufsize, count;
203 int lastc = EOF;
204 #ifdef HAVE_ICONV
205 char *tcs;
206 #endif
208 if ((fi = Fopen(ap->a_name, "r")) == NULL) {
209 perror(ap->a_name);
210 return -1;
212 if ((basename = strrchr(ap->a_name, '/')) == NULL)
213 basename = ap->a_name;
214 else
215 basename++;
216 if (ap->a_content_type)
217 contenttype = ap->a_content_type;
218 else
219 contenttype = mime_filecontent(basename);
220 if (ap->a_charset)
221 charset = ap->a_charset;
222 convert = get_mime_convert(fi, &contenttype, &charset, &isclean,
223 dosign);
224 fprintf(fo,
225 "\n--%s\n"
226 "Content-Type: %s",
227 send_boundary, contenttype);
228 if (charset == NULL)
229 putc('\n', fo);
230 else
231 fprintf(fo, ";\n charset=%s\n", charset);
232 if (ap->a_content_disposition == NULL)
233 ap->a_content_disposition = "attachment";
234 fprintf(fo, "Content-Transfer-Encoding: %s\n"
235 "Content-Disposition: %s;\n"
236 " filename=\"",
237 getencoding(convert),
238 ap->a_content_disposition);
239 mime_write(basename, strlen(basename), fo,
240 CONV_TOHDR, TD_NONE, NULL, (size_t)0, NULL, NULL);
241 fwrite("\"\n", sizeof (char), 2, fo);
242 if (ap->a_content_id)
243 fprintf(fo, "Content-ID: %s\n", ap->a_content_id);
244 if (ap->a_content_description)
245 fprintf(fo, "Content-Description: %s\n",
246 ap->a_content_description);
247 putc('\n', fo);
248 #ifdef HAVE_ICONV
249 if (iconvd != (iconv_t)-1) {
250 iconv_close(iconvd);
251 iconvd = (iconv_t)-1;
253 tcs = gettcharset();
254 if ((isclean & (MIME_HASNUL|MIME_CTRLCHAR)) == 0 &&
255 ascncasecmp(contenttype, "text/", 5) == 0 &&
256 isclean & MIME_HIGHBIT &&
257 charset != NULL) {
258 if ((iconvd = iconv_open_ft(charset, tcs)) == (iconv_t)-1 &&
259 errno != 0) {
260 if (errno == EINVAL)
261 fprintf(stderr, catgets(catd, CATSET, 179,
262 "Cannot convert from %s to %s\n"), tcs, charset);
263 else
264 perror("iconv_open");
265 Fclose(fi);
266 return -1;
269 #endif /* HAVE_ICONV */
270 buf = smalloc(bufsize = INFIX_BUF);
271 if (convert == CONV_TOQP
272 #ifdef HAVE_ICONV
273 || iconvd != (iconv_t)-1
274 #endif
276 fflush(fi);
277 count = fsize(fi);
279 for (;;) {
280 if (convert == CONV_TOQP
281 #ifdef HAVE_ICONV
282 || iconvd != (iconv_t)-1
283 #endif
285 if (fgetline(&buf, &bufsize, &count, &sz, fi, 0)
286 == NULL)
287 break;
288 } else {
289 if ((sz = fread(buf, sizeof *buf, bufsize, fi)) == 0)
290 break;
292 lastc = buf[sz-1];
293 if (mime_write(buf, sz, fo, convert, TD_ICONV,
294 NULL, (size_t)0, NULL, NULL) == 0)
295 err = -1;
297 if (convert == CONV_TOQP && lastc != '\n')
298 fwrite("=\n", 1, 2, fo);
299 if (ferror(fi))
300 err = -1;
301 Fclose(fi);
302 free(buf);
303 return err;
307 * Try out different character set conversions to attach a file.
309 static int
310 attach_file(struct attachment *ap, FILE *fo, int dosign)
312 char *_wantcharset, *charsets, *ncs;
313 size_t offs = ftell(fo);
315 if (ap->a_charset || (charsets = value("sendcharsets")) == NULL)
316 return attach_file1(ap, fo, dosign);
317 _wantcharset = wantcharset;
318 wantcharset = savestr(charsets);
319 loop: if ((ncs = strchr(wantcharset, ',')) != NULL)
320 *ncs++ = '\0';
321 try: if (attach_file1(ap, fo, dosign) != 0) {
322 if (errno == EILSEQ || errno == EINVAL) {
323 if (ncs && *ncs) {
324 wantcharset = ncs;
325 clearerr(fo);
326 fseek(fo, offs, SEEK_SET);
327 goto loop;
329 if (wantcharset) {
330 if (wantcharset == (char *)-1)
331 wantcharset = NULL;
332 else {
333 wantcharset = (char *)-1;
334 clearerr(fo);
335 fseek(fo, offs, SEEK_SET);
336 goto try;
341 wantcharset = _wantcharset;
342 return 0;
346 * Attach a message to the file buffer.
348 static int
349 attach_message(struct attachment *ap, FILE *fo, int dosign)
351 struct message *mp;
352 (void)dosign;
354 fprintf(fo, "\n--%s\n"
355 "Content-Type: message/rfc822\n"
356 "Content-Disposition: inline\n\n", send_boundary);
357 mp = &message[ap->a_msgno - 1];
358 touch(mp);
359 if (send(mp, fo, 0, NULL, SEND_RFC822, NULL) < 0)
360 return -1;
361 return 0;
365 * Generate the body of a MIME multipart message.
367 static int
368 make_multipart(struct header *hp, int convert, FILE *fi, FILE *fo,
369 const char *contenttype, const char *charset, int dosign)
371 struct attachment *att;
373 fputs("This is a multi-part message in MIME format.\n", fo);
374 if (fsize(fi) != 0) {
375 char *buf, c = '\n';
376 size_t sz, bufsize, count;
378 fprintf(fo, "\n--%s\n", send_boundary);
379 fprintf(fo, "Content-Type: %s", contenttype);
380 if (charset)
381 fprintf(fo, "; charset=%s", charset);
382 fprintf(fo, "\nContent-Transfer-Encoding: %s\n"
383 "Content-Disposition: inline\n\n",
384 getencoding(convert));
385 buf = smalloc(bufsize = INFIX_BUF);
386 if (convert == CONV_TOQP
387 #ifdef HAVE_ICONV
388 || iconvd != (iconv_t)-1
389 #endif /* HAVE_ICONV */
391 fflush(fi);
392 count = fsize(fi);
394 for (;;) {
395 if (convert == CONV_TOQP
396 #ifdef HAVE_ICONV
397 || iconvd != (iconv_t)-1
398 #endif /* HAVE_ICONV */
400 if (fgetline(&buf, &bufsize, &count, &sz, fi, 0)
401 == NULL)
402 break;
403 } else {
404 sz = fread(buf, sizeof *buf, bufsize, fi);
405 if (sz == 0)
406 break;
408 c = buf[sz - 1];
409 if (mime_write(buf, sz, fo, convert,
410 TD_ICONV, NULL, (size_t)0,
411 NULL, NULL) == 0) {
412 free(buf);
413 return -1;
416 free(buf);
417 if (ferror(fi))
418 return -1;
419 if (c != '\n')
420 putc('\n', fo);
421 if (charset != NULL)
422 put_signature(fo, convert);
424 for (att = hp->h_attach; att != NULL; att = att->a_flink) {
425 if (att->a_msgno) {
426 if (attach_message(att, fo, dosign) != 0)
427 return -1;
428 } else {
429 if (attach_file(att, fo, dosign) != 0)
430 return -1;
433 /* the final boundary with two attached dashes */
434 fprintf(fo, "\n--%s--\n", send_boundary);
435 return 0;
439 * Prepend a header in front of the collected stuff
440 * and return the new file.
442 static FILE *
443 infix(struct header *hp, FILE *fi, int dosign)
445 FILE *nfo, *nfi;
446 char *tempMail;
447 #ifdef HAVE_ICONV
448 char *tcs, *convhdr = NULL;
449 #endif
450 enum mimeclean isclean;
451 enum conversion convert;
452 char *charset = NULL, *contenttype = NULL;
453 int lastc = EOF;
455 if ((nfo = Ftemp(&tempMail, "Rs", "w", 0600, 1)) == NULL) {
456 perror(catgets(catd, CATSET, 178, "temporary mail file"));
457 return(NULL);
459 if ((nfi = Fopen(tempMail, "r")) == NULL) {
460 perror(tempMail);
461 Fclose(nfo);
462 return(NULL);
464 rm(tempMail);
465 Ftfree(&tempMail);
466 convert = get_mime_convert(fi, &contenttype, &charset,
467 &isclean, dosign);
468 #ifdef HAVE_ICONV
469 tcs = gettcharset();
470 if ((convhdr = need_hdrconv(hp, GTO|GSUBJECT|GCC|GBCC|GIDENT)) != 0) {
471 if (iconvd != (iconv_t)-1)
472 iconv_close(iconvd);
473 if ((iconvd = iconv_open_ft(convhdr, tcs)) == (iconv_t)-1
474 && errno != 0) {
475 if (errno == EINVAL)
476 fprintf(stderr, catgets(catd, CATSET, 179,
477 "Cannot convert from %s to %s\n"), tcs, convhdr);
478 else
479 perror("iconv_open");
480 Fclose(nfo);
481 return NULL;
484 #endif /* HAVE_ICONV */
485 if (puthead(hp, nfo,
486 GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA|GUA|GMIME
487 |GMSGID|GIDENT|GREF|GDATE,
488 SEND_MBOX, convert, contenttype, charset)) {
489 Fclose(nfo);
490 Fclose(nfi);
491 #ifdef HAVE_ICONV
492 if (iconvd != (iconv_t)-1) {
493 iconv_close(iconvd);
494 iconvd = (iconv_t)-1;
496 #endif
497 return NULL;
499 #ifdef HAVE_ICONV
500 if (convhdr && iconvd != (iconv_t)-1) {
501 iconv_close(iconvd);
502 iconvd = (iconv_t)-1;
504 if ((isclean & (MIME_HASNUL|MIME_CTRLCHAR)) == 0 &&
505 ascncasecmp(contenttype, "text/", 5) == 0 &&
506 isclean & MIME_HIGHBIT &&
507 charset != NULL) {
508 if (iconvd != (iconv_t)-1)
509 iconv_close(iconvd);
510 if ((iconvd = iconv_open_ft(charset, tcs)) == (iconv_t)-1
511 && errno != 0) {
512 if (errno == EINVAL)
513 fprintf(stderr, catgets(catd, CATSET, 179,
514 "Cannot convert from %s to %s\n"), tcs, charset);
515 else
516 perror("iconv_open");
517 Fclose(nfo);
518 return NULL;
521 #endif
522 if (hp->h_attach != NULL) {
523 if (make_multipart(hp, convert, fi, nfo,
524 contenttype, charset, dosign) != 0) {
525 Fclose(nfo);
526 Fclose(nfi);
527 #ifdef HAVE_ICONV
528 if (iconvd != (iconv_t)-1) {
529 iconv_close(iconvd);
530 iconvd = (iconv_t)-1;
532 #endif
533 return NULL;
535 } else {
536 size_t sz, bufsize, count;
537 char *buf;
539 if (convert == CONV_TOQP
540 #ifdef HAVE_ICONV
541 || iconvd != (iconv_t)-1
542 #endif /* HAVE_ICONV */
544 fflush(fi);
545 count = fsize(fi);
547 buf = smalloc(bufsize = INFIX_BUF);
548 for (;;) {
549 if (convert == CONV_TOQP
550 #ifdef HAVE_ICONV
551 || iconvd != (iconv_t)-1
552 #endif /* HAVE_ICONV */
554 if (fgetline(&buf, &bufsize, &count, &sz, fi, 0)
555 == NULL)
556 break;
557 } else {
558 sz = fread(buf, sizeof *buf, bufsize, fi);
559 if (sz == 0)
560 break;
562 lastc = buf[sz - 1];
563 if (mime_write(buf, sz, nfo, convert,
564 TD_ICONV, NULL, (size_t)0,
565 NULL, NULL) == 0) {
566 Fclose(nfo);
567 Fclose(nfi);
568 #ifdef HAVE_ICONV
569 if (iconvd != (iconv_t)-1) {
570 iconv_close(iconvd);
571 iconvd = (iconv_t)-1;
573 #endif
574 free(buf);
575 return NULL;
578 if (convert == CONV_TOQP && lastc != '\n')
579 fwrite("=\n", 1, 2, nfo);
580 free(buf);
581 if (ferror(fi)) {
582 Fclose(nfo);
583 Fclose(nfi);
584 #ifdef HAVE_ICONV
585 if (iconvd != (iconv_t)-1) {
586 iconv_close(iconvd);
587 iconvd = (iconv_t)-1;
589 #endif
590 return NULL;
592 if (charset != NULL)
593 put_signature(nfo, convert);
595 #ifdef HAVE_ICONV
596 if (iconvd != (iconv_t)-1) {
597 iconv_close(iconvd);
598 iconvd = (iconv_t)-1;
600 #endif
601 fflush(nfo);
602 if (ferror(nfo)) {
603 perror(catgets(catd, CATSET, 180, "temporary mail file"));
604 Fclose(nfo);
605 Fclose(nfi);
606 return NULL;
608 Fclose(nfo);
609 Fclose(fi);
610 fflush(nfi);
611 rewind(nfi);
612 return(nfi);
616 * Save the outgoing mail on the passed file.
619 /*ARGSUSED*/
620 static int
621 savemail(char *name, FILE *fi)
623 FILE *fo;
624 char *buf;
625 size_t bufsize, buflen, count;
626 char *p;
627 time_t now;
628 int prependnl = 0;
629 int error = 0;
631 buf = smalloc(bufsize = LINESIZE);
632 time(&now);
633 if ((fo = Zopen(name, "a+", NULL)) == NULL) {
634 if ((fo = Zopen(name, "wx", NULL)) == NULL) {
635 perror(name);
636 free(buf);
637 return (-1);
639 } else {
640 if (fseek(fo, -2L, SEEK_END) == 0) {
641 switch (fread(buf, sizeof *buf, 2, fo)) {
642 case 2:
643 if (buf[1] != '\n') {
644 prependnl = 1;
645 break;
647 /*FALLTHRU*/
648 case 1:
649 if (buf[0] != '\n')
650 prependnl = 1;
651 break;
652 default:
653 if (ferror(fo)) {
654 perror(name);
655 free(buf);
656 return -1;
659 fflush(fo);
660 if (prependnl) {
661 putc('\n', fo);
662 fflush(fo);
666 fprintf(fo, "From %s %s", myname, ctime(&now));
667 buflen = 0;
668 fflush(fi);
669 rewind(fi);
670 count = fsize(fi);
671 while (fgetline(&buf, &bufsize, &count, &buflen, fi, 0) != NULL) {
672 if (*buf == '>') {
673 p = buf + 1;
674 while (*p == '>')
675 p++;
676 if (strncmp(p, "From ", 5) == 0)
677 /* we got a masked From line */
678 putc('>', fo);
679 } else if (strncmp(buf, "From ", 5) == 0)
680 putc('>', fo);
681 fwrite(buf, sizeof *buf, buflen, fo);
683 if (buflen && *(buf + buflen - 1) != '\n')
684 putc('\n', fo);
685 putc('\n', fo);
686 fflush(fo);
687 if (ferror(fo)) {
688 perror(name);
689 error = -1;
691 if (Fclose(fo) != 0)
692 error = -1;
693 fflush(fi);
694 rewind(fi);
695 free(buf);
696 return error;
700 * Interface between the argument list and the mail1 routine
701 * which does all the dirty work.
703 int
704 mail(struct name *to, struct name *cc, struct name *bcc,
705 struct name *smopts, char *subject, struct attachment *attach,
706 char *quotefile, int recipient_record, int tflag, int Eflag)
708 struct header head;
709 struct str in, out;
711 memset(&head, 0, sizeof head);
712 /* The given subject may be in RFC1522 format. */
713 if (subject != NULL) {
714 in.s = subject;
715 in.l = strlen(subject);
716 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
717 head.h_subject = out.s;
719 if (tflag == 0) {
720 head.h_to = to;
721 head.h_cc = cc;
722 head.h_bcc = bcc;
724 head.h_attach = attach;
725 head.h_smopts = smopts;
726 mail1(&head, 0, NULL, quotefile, recipient_record, 0, tflag, Eflag);
727 if (subject != NULL)
728 free(out.s);
729 return(0);
733 * Send mail to a bunch of user names. The interface is through
734 * the mail routine below.
736 static int
737 sendmail_internal(void *v, int recipient_record)
739 int Eflag;
740 char *str = v;
741 struct header head;
743 memset(&head, 0, sizeof head);
744 head.h_to = extract(str, GTO|GFULL);
745 Eflag = value("skipemptybody") != NULL;
746 mail1(&head, 0, NULL, NULL, recipient_record, 0, 0, Eflag);
747 return(0);
750 int
751 sendmail(void *v)
753 return sendmail_internal(v, 0);
756 int
757 Sendmail(void *v)
759 return sendmail_internal(v, 1);
762 static enum okay
763 transfer(struct name *to, struct name *mailargs, FILE *input, struct header *hp)
765 char o[LINESIZE], *cp;
766 struct name *np, *nt;
767 int cnt = 0;
768 FILE *ef;
769 enum okay ok = OKAY;
771 np = to;
772 while (np) {
773 snprintf(o, sizeof o, "smime-encrypt-%s", np->n_name);
774 if ((cp = value(o)) != NULL) {
775 if ((ef = smime_encrypt(input, cp, np->n_name)) != 0) {
776 nt = nalloc(np->n_name,
777 np->n_type & ~(GFULL|GSKIN));
778 if (start_mta(nt, mailargs, ef, hp) != OKAY)
779 ok = STOP;
780 Fclose(ef);
781 } else {
782 fprintf(stderr, "Message not sent to <%s>\n",
783 np->n_name);
784 senderr++;
786 rewind(input);
787 if (np->n_flink)
788 np->n_flink->n_blink = np->n_blink;
789 if (np->n_blink)
790 np->n_blink->n_flink = np->n_flink;
791 if (np == to)
792 to = np->n_flink;
793 np = np->n_flink;
794 } else {
795 cnt++;
796 np = np->n_flink;
799 if (cnt) {
800 if (value("smime-force-encryption") ||
801 start_mta(to, mailargs, input, hp) != OKAY)
802 ok = STOP;
804 return ok;
808 * Start the Mail Transfer Agent
809 * mailing to namelist and stdin redirected to input.
811 static enum okay
812 start_mta(struct name *to, struct name *mailargs, FILE *input,
813 struct header *hp)
815 char **args = NULL, **t;
816 pid_t pid;
817 sigset_t nset;
818 char *cp, *smtp;
819 char *user = NULL, *password = NULL, *skinned = NULL;
820 enum okay ok = STOP;
821 #ifdef USE_SMTP
822 struct termios otio;
823 int reset_tio;
824 #endif
826 if ((smtp = value("smtp")) == NULL) {
827 args = unpack(cat(mailargs, to));
828 if (debug || value("debug")) {
829 printf(catgets(catd, CATSET, 181,
830 "Sendmail arguments:"));
831 for (t = args; *t != NULL; t++)
832 printf(" \"%s\"", *t);
833 printf("\n");
834 return OKAY;
837 #ifdef USE_SMTP
838 if (smtp != NULL) {
839 skinned = skin(myorigin(hp));
840 if ((user = smtp_auth_var("-user", skinned)) != NULL &&
841 (password = smtp_auth_var("-password",
842 skinned)) == NULL)
843 password = getpassword(&otio, &reset_tio, NULL);
845 #endif
847 * Fork, set up the temporary mail file as standard
848 * input for "mail", and exec with the user list we generated
849 * far above.
851 if ((pid = fork()) == -1) {
852 perror("fork");
853 savedeadletter(input);
854 senderr++;
855 return STOP;
857 if (pid == 0) {
858 sigemptyset(&nset);
859 sigaddset(&nset, SIGHUP);
860 sigaddset(&nset, SIGINT);
861 sigaddset(&nset, SIGQUIT);
862 sigaddset(&nset, SIGTSTP);
863 sigaddset(&nset, SIGTTIN);
864 sigaddset(&nset, SIGTTOU);
865 freopen("/dev/null", "r", stdin);
866 if (smtp != NULL) {
867 prepare_child(&nset, 0, 1);
868 if (smtp_mta(smtp, to, input, hp,
869 user, password, skinned) == 0)
870 _exit(0);
871 } else {
872 prepare_child(&nset, fileno(input), -1);
873 if ((cp = value("sendmail")) != NULL)
874 cp = expand(cp);
875 else
876 cp = SENDMAIL;
877 execv(cp, args);
878 perror(cp);
880 savedeadletter(input);
881 fputs(catgets(catd, CATSET, 182,
882 ". . . message not sent.\n"), stderr);
883 _exit(1);
885 if (value("verbose") != NULL || value("sendwait") || debug
886 || value("debug")) {
887 if (wait_child(pid) == 0)
888 ok = OKAY;
889 else
890 senderr++;
891 } else {
892 ok = OKAY;
893 free_child(pid);
895 return ok;
899 * Record outgoing mail if instructed to do so.
901 static enum okay
902 mightrecord(FILE *fp, struct name *to, int recipient_record)
904 char *cp, *cq, *ep;
906 if (recipient_record) {
907 cq = skin(to->n_name);
908 cp = salloc(strlen(cq) + 1);
909 strcpy(cp, cq);
910 for (cq = cp; *cq && *cq != '@'; cq++);
911 *cq = '\0';
912 } else
913 cp = value("record");
914 if (cp != NULL) {
915 ep = expand(cp);
916 if (value("outfolder") && *ep != '/' && *ep != '+' &&
917 which_protocol(ep) == PROTO_FILE) {
918 cq = salloc(strlen(cp) + 2);
919 cq[0] = '+';
920 strcpy(&cq[1], cp);
921 cp = cq;
922 ep = expand(cp);
924 if (savemail(ep, fp) != 0) {
925 fprintf(stderr,
926 "Error while saving message to %s - "
927 "message not sent\n", ep);
928 rewind(fp);
929 exit_status |= 1;
930 savedeadletter(fp);
931 return STOP;
934 return OKAY;
938 * Mail a message on standard input to the people indicated
939 * in the passed header. (Internal interface).
941 enum okay
942 mail1(struct header *hp, int printheaders, struct message *quote,
943 char *quotefile, int recipient_record, int doprefix, int tflag,
944 int Eflag)
946 struct name *to;
947 FILE *mtf, *nmtf;
948 enum okay ok = STOP;
949 int dosign = -1;
950 char *charsets, *ncs = NULL, *cp;
952 #ifdef notdef
953 if ((hp->h_to = checkaddrs(hp->h_to)) == NULL) {
954 senderr++;
955 return STOP;
957 #endif
958 if ((cp = value("autocc")) != NULL && *cp)
959 hp->h_cc = cat(hp->h_cc, checkaddrs(sextract(cp, GCC|GFULL)));
960 if ((cp = value("autobcc")) != NULL && *cp)
961 hp->h_bcc = cat(hp->h_bcc,
962 checkaddrs(sextract(cp, GBCC|GFULL)));
964 * Collect user's mail from standard input.
965 * Get the result as mtf.
967 if ((mtf = collect(hp, printheaders, quote, quotefile, doprefix,
968 tflag)) == NULL)
969 return STOP;
970 if (value("interactive") != NULL) {
971 if (((value("bsdcompat") || value("askatend"))
972 && (value("askcc") != NULL ||
973 value("askbcc") != NULL)) ||
974 value("askattach") != NULL ||
975 value("asksign") != NULL) {
976 if (value("askcc") != NULL)
977 grabh(hp, GCC, 1);
978 if (value("askbcc") != NULL)
979 grabh(hp, GBCC, 1);
980 if (value("askattach") != NULL)
981 hp->h_attach = edit_attachments(hp->h_attach);
982 if (value("asksign") != NULL)
983 dosign = yorn("Sign this message (y/n)? ");
984 } else {
985 printf(catgets(catd, CATSET, 183, "EOT\n"));
986 fflush(stdout);
989 if (fsize(mtf) == 0) {
990 if (Eflag)
991 goto out;
992 if (hp->h_subject == NULL)
993 printf(catgets(catd, CATSET, 184,
994 "No message, no subject; hope that's ok\n"));
995 else if (value("bsdcompat") || value("bsdmsgs"))
996 printf(catgets(catd, CATSET, 185,
997 "Null message body; hope that's ok\n"));
999 if (dosign < 0) {
1000 if (value("smime-sign") != NULL)
1001 dosign = 1;
1002 else
1003 dosign = 0;
1006 * Now, take the user names from the combined
1007 * to and cc lists and do all the alias
1008 * processing.
1010 senderr = 0;
1011 if ((to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)))) == NULL) {
1012 printf(catgets(catd, CATSET, 186, "No recipients specified\n"));
1013 senderr++;
1015 to = fixhead(hp, to);
1016 if (hp->h_charset) {
1017 wantcharset = hp->h_charset;
1018 goto try;
1020 hloop: wantcharset = NULL;
1021 if ((charsets = value("sendcharsets")) != NULL) {
1022 wantcharset = savestr(charsets);
1023 loop: if ((ncs = strchr(wantcharset, ',')) != NULL)
1024 *ncs++ = '\0';
1026 try: if ((nmtf = infix(hp, mtf, dosign)) == NULL) {
1027 if (hp->h_charset && (errno == EILSEQ || errno == EINVAL)) {
1028 rewind(mtf);
1029 hp->h_charset = NULL;
1030 goto hloop;
1032 if (errno == EILSEQ || errno == EINVAL) {
1033 if (ncs && *ncs) {
1034 rewind(mtf);
1035 wantcharset = ncs;
1036 goto loop;
1038 if (wantcharset && value("interactive") == NULL) {
1039 if (wantcharset == (char *)-1)
1040 wantcharset = NULL;
1041 else {
1042 rewind(mtf);
1043 wantcharset = (char *)-1;
1044 goto try;
1048 /* fprintf(stderr, ". . . message lost, sorry.\n"); */
1049 perror("");
1050 fail: senderr++;
1051 rewind(mtf);
1052 savedeadletter(mtf);
1053 fputs(catgets(catd, CATSET, 187,
1054 ". . . message not sent.\n"), stderr);
1055 return STOP;
1057 mtf = nmtf;
1058 if (dosign) {
1059 if ((nmtf = smime_sign(mtf, hp)) == NULL)
1060 goto fail;
1061 Fclose(mtf);
1062 mtf = nmtf;
1065 * Look through the recipient list for names with /'s
1066 * in them which we write to as files directly.
1068 to = outof(to, mtf, hp);
1069 if (senderr)
1070 savedeadletter(mtf);
1071 to = elide(to);
1072 if (count(to) == 0) {
1073 if (senderr == 0)
1074 ok = OKAY;
1075 goto out;
1077 if (mightrecord(mtf, to, recipient_record) != OKAY)
1078 goto out;
1079 ok = transfer(to, hp->h_smopts, mtf, hp);
1080 out:
1081 Fclose(mtf);
1082 return ok;
1086 * Create a Message-Id: header field.
1087 * Use either the host name or the from address.
1089 static void
1090 message_id(FILE *fo, struct header *hp)
1092 char *cp;
1093 time_t now;
1095 time(&now);
1096 if ((cp = value("hostname")) != NULL)
1097 fprintf(fo, "Message-ID: <%lx.%s@%s>\n",
1098 (long)now, getrandstring(24), cp);
1099 else if ((cp = skin(myorigin(hp))) != NULL && strchr(cp, '@') != NULL)
1100 fprintf(fo, "Message-ID: <%lx.%s%%%s>\n",
1101 (long)now, getrandstring(16), cp);
1104 static const char *weekday_names[] = {
1105 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1108 const char *month_names[] = {
1109 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1110 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
1114 * Create a Date: header field.
1115 * We compare the localtime() and gmtime() results to get the timezone,
1116 * because numeric timezones are easier to read and because $TZ is
1117 * not set on most GNU systems.
1120 mkdate(FILE *fo, const char *field)
1122 time_t t;
1123 struct tm *tmptr;
1124 int tzdiff, tzdiff_hour, tzdiff_min;
1126 time(&t);
1127 tzdiff = t - mktime(gmtime(&t));
1128 tzdiff_hour = (int)(tzdiff / 60);
1129 tzdiff_min = tzdiff_hour % 60;
1130 tzdiff_hour /= 60;
1131 tmptr = localtime(&t);
1132 if (tmptr->tm_isdst > 0)
1133 tzdiff_hour++;
1134 return fprintf(fo, "%s: %s, %02d %s %04d %02d:%02d:%02d %+05d\n",
1135 field,
1136 weekday_names[tmptr->tm_wday],
1137 tmptr->tm_mday, month_names[tmptr->tm_mon],
1138 tmptr->tm_year + 1900, tmptr->tm_hour,
1139 tmptr->tm_min, tmptr->tm_sec,
1140 tzdiff_hour * 100 + tzdiff_min);
1143 static enum okay
1144 putname(char *line, enum gfield w, enum sendaction action, int *gotcha,
1145 char *prefix, FILE *fo, struct name **xp)
1147 struct name *np;
1149 np = sextract(line, GEXTRA|GFULL);
1150 if (xp)
1151 *xp = np;
1152 if (np == NULL)
1153 return 0;
1154 if (fmt(prefix, np, fo, w&(GCOMMA|GFILES), 0, action != SEND_TODISP))
1155 return 1;
1156 if (gotcha)
1157 (*gotcha)++;
1158 return 0;
1161 #define FMT_CC_AND_BCC { \
1162 if (hp->h_cc != NULL && w & GCC) { \
1163 if (fmt("Cc:", hp->h_cc, fo, \
1164 w&(GCOMMA|GFILES), 0, \
1165 action!=SEND_TODISP)) \
1166 return 1; \
1167 gotcha++; \
1169 if (hp->h_bcc != NULL && w & GBCC) { \
1170 if (fmt("Bcc:", hp->h_bcc, fo, \
1171 w&(GCOMMA|GFILES), 0, \
1172 action!=SEND_TODISP)) \
1173 return 1; \
1174 gotcha++; \
1178 * Dump the to, subject, cc header on the
1179 * passed file buffer.
1182 puthead(struct header *hp, FILE *fo, enum gfield w,
1183 enum sendaction action, enum conversion convert,
1184 char *contenttype, char *charset)
1186 int gotcha;
1187 char *addr/*, *cp*/;
1188 int stealthmua;
1189 struct name *np, *fromfield = NULL, *senderfield = NULL;
1192 if (value("stealthmua"))
1193 stealthmua = 1;
1194 else
1195 stealthmua = 0;
1196 gotcha = 0;
1197 if (w & GDATE) {
1198 mkdate(fo, "Date"), gotcha++;
1200 if (w & GIDENT) {
1201 if (hp->h_from != NULL) {
1202 if (fmt("From:", hp->h_from, fo, w&(GCOMMA|GFILES), 0,
1203 action!=SEND_TODISP))
1204 return 1;
1205 gotcha++;
1206 fromfield = hp->h_from;
1207 } else if ((addr = myaddrs(hp)) != NULL)
1208 if (putname(addr, w, action, &gotcha, "From:", fo,
1209 &fromfield))
1210 return 1;
1211 if (((addr = hp->h_organization) != NULL ||
1212 (addr = value("ORGANIZATION")) != NULL)
1213 && strlen(addr) > 0) {
1214 fwrite("Organization: ", sizeof (char), 14, fo);
1215 if (mime_write(addr, strlen(addr), fo,
1216 action == SEND_TODISP ?
1217 CONV_NONE:CONV_TOHDR,
1218 action == SEND_TODISP ?
1219 TD_ISPR|TD_ICONV:TD_ICONV,
1220 NULL, (size_t)0,
1221 NULL, NULL) == 0)
1222 return 1;
1223 gotcha++;
1224 putc('\n', fo);
1226 if (hp->h_replyto != NULL) {
1227 if (fmt("Reply-To:", hp->h_replyto, fo,
1228 w&(GCOMMA|GFILES), 0,
1229 action!=SEND_TODISP))
1230 return 1;
1231 gotcha++;
1232 } else if ((addr = value("replyto")) != NULL)
1233 if (putname(addr, w, action, &gotcha, "Reply-To:", fo,
1234 NULL))
1235 return 1;
1236 if (hp->h_sender != NULL) {
1237 if (fmt("Sender:", hp->h_sender, fo,
1238 w&(GCOMMA|GFILES), 0,
1239 action!=SEND_TODISP))
1240 return 1;
1241 gotcha++;
1242 senderfield = hp->h_sender;
1243 } else if ((addr = value("sender")) != NULL)
1244 if (putname(addr, w, action, &gotcha, "Sender:", fo,
1245 &senderfield))
1246 return 1;
1247 if (check_from_and_sender(fromfield, senderfield))
1248 return 1;
1250 if (hp->h_to != NULL && w & GTO) {
1251 if (fmt("To:", hp->h_to, fo, w&(GCOMMA|GFILES), 0,
1252 action!=SEND_TODISP))
1253 return 1;
1254 gotcha++;
1256 if (value("bsdcompat") == NULL && value("bsdorder") == NULL)
1257 FMT_CC_AND_BCC
1258 if (hp->h_subject != NULL && w & GSUBJECT) {
1259 fwrite("Subject: ", sizeof (char), 9, fo);
1260 if (ascncasecmp(hp->h_subject, "re: ", 4) == 0) {
1261 fwrite("Re: ", sizeof (char), 4, fo);
1262 if (strlen(hp->h_subject + 4) > 0 &&
1263 mime_write(hp->h_subject + 4,
1264 strlen(hp->h_subject + 4),
1265 fo, action == SEND_TODISP ?
1266 CONV_NONE:CONV_TOHDR,
1267 action == SEND_TODISP ?
1268 TD_ISPR|TD_ICONV:TD_ICONV,
1269 NULL, (size_t)0,
1270 NULL, NULL) == 0)
1271 return 1;
1272 } else if (*hp->h_subject) {
1273 if (mime_write(hp->h_subject,
1274 strlen(hp->h_subject),
1275 fo, action == SEND_TODISP ?
1276 CONV_NONE:CONV_TOHDR,
1277 action == SEND_TODISP ?
1278 TD_ISPR|TD_ICONV:TD_ICONV,
1279 NULL, (size_t)0,
1280 NULL, NULL) == 0)
1281 return 1;
1283 gotcha++;
1284 fwrite("\n", sizeof (char), 1, fo);
1286 if (value("bsdcompat") || value("bsdorder"))
1287 FMT_CC_AND_BCC
1288 if (w & GMSGID && stealthmua == 0)
1289 message_id(fo, hp), gotcha++;
1290 if (hp->h_ref != NULL && w & GREF) {
1291 fmt("References:", hp->h_ref, fo, 0, 1, 0);
1292 if ((np = hp->h_ref) != NULL && np->n_name) {
1293 while (np->n_flink)
1294 np = np->n_flink;
1295 if (mime_name_invalid(np, 0) == 0) {
1296 fprintf(fo, "In-Reply-To: %s\n", np->n_name);
1297 gotcha++;
1301 if (w & GUA && stealthmua == 0)
1302 fprintf(fo, "User-Agent: S-nail %s\n",
1303 version), gotcha++;
1304 if (w & GMIME) {
1305 fputs("MIME-Version: 1.0\n", fo), gotcha++;
1306 if (hp->h_attach != NULL) {
1307 makeboundary();
1308 fprintf(fo, "Content-Type: multipart/mixed;\n"
1309 " boundary=\"%s\"\n", send_boundary);
1310 } else {
1311 fprintf(fo, "Content-Type: %s", contenttype);
1312 if (charset)
1313 fprintf(fo, "; charset=%s", charset);
1314 fprintf(fo, "\nContent-Transfer-Encoding: %s\n",
1315 getencoding(convert));
1318 if (gotcha && w & GNL)
1319 putc('\n', fo);
1320 return(0);
1324 * Format the given header line to not exceed 72 characters.
1326 static int
1327 fmt(char *str, struct name *np, FILE *fo, int flags, int dropinvalid,
1328 int domime)
1330 int col, len, count = 0;
1331 int is_to = 0, comma;
1333 comma = flags&GCOMMA ? 1 : 0;
1334 col = strlen(str);
1335 if (col) {
1336 fwrite(str, sizeof *str, strlen(str), fo);
1337 if (((flags&GFILES) == 0 &&
1338 col == 3 && asccasecmp(str, "to:") == 0) ||
1339 (col == 3 && asccasecmp(str, "cc:") == 0) ||
1340 (col == 4 && asccasecmp(str, "bcc:") == 0) ||
1341 (col == 10 &&
1342 asccasecmp(str, "Resent-To:") == 0))
1343 is_to = 1;
1345 for (; np != NULL; np = np->n_flink) {
1346 if (is_to && is_fileaddr(np->n_name))
1347 continue;
1348 if (np->n_flink == NULL)
1349 comma = 0;
1350 if (mime_name_invalid(np, !dropinvalid)) {
1351 if (dropinvalid)
1352 continue;
1353 else
1354 return 1;
1356 len = strlen(np->n_fullname);
1357 col++; /* for the space */
1358 if (count && col + len + comma > 72 && col > 1) {
1359 fputs("\n ", fo);
1360 col = 1;
1361 } else
1362 putc(' ', fo);
1363 len = mime_write(np->n_fullname,
1364 len, fo,
1365 domime?CONV_TOHDR_A:CONV_NONE,
1366 TD_ICONV, NULL, (size_t)0,
1367 NULL, NULL);
1368 if (comma && !(is_to && is_fileaddr(np->n_flink->n_name)))
1369 putc(',', fo);
1370 col += len + comma;
1371 count++;
1373 putc('\n', fo);
1374 return 0;
1378 * Rewrite a message for resending, adding the Resent-Headers.
1380 static int
1381 infix_resend(FILE *fi, FILE *fo, struct message *mp, struct name *to,
1382 int add_resent)
1384 size_t count;
1385 char *buf = NULL, *cp/*, *cp2*/;
1386 size_t c, bufsize = 0;
1387 struct name *fromfield = NULL, *senderfield = NULL;
1389 count = mp->m_size;
1391 * Write the Resent-Fields.
1393 if (add_resent) {
1394 fputs("Resent-", fo);
1395 mkdate(fo, "Date");
1396 if ((cp = myaddrs(NULL)) != NULL) {
1397 if (putname(cp, GCOMMA, SEND_MBOX, NULL,
1398 "Resent-From:", fo, &fromfield))
1399 return 1;
1401 if ((cp = value("sender")) != NULL) {
1402 if (putname(cp, GCOMMA, SEND_MBOX, NULL,
1403 "Resent-Sender:", fo, &senderfield))
1404 return 1;
1406 if (fmt("Resent-To:", to, fo, 1, 1, 0)) {
1407 if (buf)
1408 free(buf);
1409 return 1;
1411 if (value("stealthmua") == NULL) {
1412 fputs("Resent-", fo);
1413 message_id(fo, NULL);
1416 if (check_from_and_sender(fromfield, senderfield))
1417 return 1;
1419 * Write the original headers.
1421 while (count > 0) {
1422 if ((cp = foldergets(&buf, &bufsize, &count, &c, fi)) == NULL)
1423 break;
1424 if (ascncasecmp("status: ", buf, 8) != 0
1425 && strncmp("From ", buf, 5) != 0) {
1426 fwrite(buf, sizeof *buf, c, fo);
1428 if (count > 0 && *buf == '\n')
1429 break;
1432 * Write the message body.
1434 while (count > 0) {
1435 if (foldergets(&buf, &bufsize, &count, &c, fi) == NULL)
1436 break;
1437 if (count == 0 && *buf == '\n')
1438 break;
1439 fwrite(buf, sizeof *buf, c, fo);
1441 if (buf)
1442 free(buf);
1443 if (ferror(fo)) {
1444 perror(catgets(catd, CATSET, 188, "temporary mail file"));
1445 return 1;
1447 return 0;
1450 enum okay
1451 resend_msg(struct message *mp, struct name *to, int add_resent)
1453 FILE *ibuf, *nfo, *nfi;
1454 char *tempMail;
1455 struct header head;
1456 enum okay ok = STOP;
1458 memset(&head, 0, sizeof head);
1459 if ((to = checkaddrs(to)) == NULL) {
1460 senderr++;
1461 return STOP;
1463 if ((nfo = Ftemp(&tempMail, "Rs", "w", 0600, 1)) == NULL) {
1464 senderr++;
1465 perror(catgets(catd, CATSET, 189, "temporary mail file"));
1466 return STOP;
1468 if ((nfi = Fopen(tempMail, "r")) == NULL) {
1469 senderr++;
1470 perror(tempMail);
1471 return STOP;
1473 rm(tempMail);
1474 Ftfree(&tempMail);
1475 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1476 return STOP;
1477 head.h_to = to;
1478 to = fixhead(&head, to);
1479 if (infix_resend(ibuf, nfo, mp, head.h_to, add_resent) != 0) {
1480 senderr++;
1481 rewind(nfo);
1482 savedeadletter(nfi);
1483 fputs(catgets(catd, CATSET, 190,
1484 ". . . message not sent.\n"), stderr);
1485 Fclose(nfo);
1486 Fclose(nfi);
1487 return STOP;
1489 fflush(nfo);
1490 rewind(nfo);
1491 Fclose(nfo);
1492 to = outof(to, nfi, &head);
1493 if (senderr)
1494 savedeadletter(nfi);
1495 to = elide(to);
1496 if (count(to) != 0) {
1497 if (value("record-resent") == NULL ||
1498 mightrecord(nfi, to, 0) == OKAY)
1499 ok = transfer(to, head.h_smopts, nfi, NULL);
1500 } else if (senderr == 0)
1501 ok = OKAY;
1502 Fclose(nfi);
1503 return ok;