1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 1985, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright (c) 2016 by Delphix. All rights reserved.
25 */
26
27/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28/*	  All Rights Reserved  	*/
29
30/*
31 * University Copyright- Copyright (c) 1982, 1986, 1988
32 * The Regents of the University of California
33 * All Rights Reserved
34 *
35 * University Acknowledgment- Portions of this document are derived from
36 * software developed by the University of California, Berkeley, and its
37 * contributors.
38 */
39
40#include "rcv.h"
41#include <locale.h>
42
43/*
44 * mailx -- a modified version of a University of California at Berkeley
45 *	mail program
46 *
47 * Mail to others.
48 */
49
50static void		fmt(register char *str, register FILE *fo);
51static FILE		*infix(struct header *hp, FILE *fi);
52static void		statusput(register struct message *mp, register FILE *obuf, int doign, int (*fp)(const char *, FILE *));
53static int		savemail(char name[], struct header *hp, FILE *fi);
54static int		sendmail(char *str);
55static int		Sendmail(char *str);
56
57static off_t textpos;
58
59/*
60 * Send message described by the passed pointer to the
61 * passed output buffer.  Return -1 on error, but normally
62 * the number of lines written.  Adjust the status: field
63 * if need be.  If doign is set, suppress ignored header fields.
64 * Call (*fp)(line, obuf) to print the line.
65 */
66long
67msend(
68	struct message *mailp,
69	FILE *obuf,
70	int flag,
71	int (*fp)(const char *, FILE *))
72{
73	register struct message *mp;
74	long clen, n, c;
75	FILE *ibuf;
76	char line[LINESIZE], field[BUFSIZ];
77	int ishead, infld, fline, dostat, doclen, nread, unused;
78	char *cp, *cp2;
79	int doign = flag & M_IGNORE;
80	int oldign = 0;	/* previous line was ignored */
81	long lc;
82
83	mp = mailp;
84	if (mp->m_clen == 0)
85		setclen(mp);
86	ibuf = setinput(mp);
87	c = mp->m_size;
88	ishead = 1;
89	dostat = 1;
90	doclen = 1;
91	infld = 0;
92	fline = 1;
93	lc = 0;
94	clearerr(obuf);
95	while (c > 0L) {
96		nread = getaline(line, LINESIZE, ibuf, &unused);
97		c -= nread;
98		lc++;
99		if (ishead) {
100			/*
101			 * First line is the From line, so no headers
102			 * there to worry about
103			 */
104			if (fline) {
105				fline = 0;
106				goto writeit;
107			}
108			/*
109			 * If line is blank, we've reached end of
110			 * headers, so force out status: field
111			 * and note that we are no longer in header
112			 * fields.  Also force out Content-Length: field.
113			 */
114			if (line[0] == '\n') {
115				if (dostat) {
116					statusput(mailp, obuf, doign, fp);
117					dostat = 0;
118				}
119				if (doclen &&
120				    !isign("content-length", flag&M_SAVING)) {
121					snprintf(field, sizeof (field),
122						"Content-Length: %ld\n",
123						mp->m_clen - 1);
124					(*fp)(field, obuf);
125					if (ferror(obuf))
126						return(-1);
127					doclen = 0;
128				}
129				ishead = 0;
130				goto writeit;
131			}
132			/*
133			 * If this line is a continuation
134			 * of a previous header field, just echo it.
135			 */
136			if (isspace(line[0]) && infld)
137				if (oldign)
138					continue;
139				else
140					goto writeit;
141			infld = 0;
142			/*
143			 * If we are no longer looking at real
144			 * header lines, force out status:
145			 * This happens in uucp style mail where
146			 * there are no headers at all.
147			 */
148			if (!headerp(line)) {
149				if (dostat) {
150					statusput(mailp, obuf, doign, fp);
151					dostat = 0;
152				}
153				(*fp)("\n", obuf);
154				ishead = 0;
155				goto writeit;
156			}
157			infld++;
158			/*
159			 * Pick up the header field.
160			 * If it is an ignored field and
161			 * we care about such things, skip it.
162			 */
163			cp = line;
164			cp2 = field;
165			while (*cp && *cp != ':' && !isspace(*cp))
166				*cp2++ = *cp++;
167			*cp2 = 0;
168			oldign = doign && isign(field, flag&M_SAVING);
169			if (oldign)
170				continue;
171			/*
172			 * If the field is "status," go compute and print the
173			 * real Status: field
174			 */
175			if (icequal(field, "status")) {
176				if (dostat) {
177					statusput(mailp, obuf, doign, fp);
178					dostat = 0;
179				}
180				continue;
181			}
182			if (icequal(field, "content-length")) {
183				if (doclen) {
184					snprintf(line, sizeof (line),
185						"Content-Length: %ld\n",
186						mp->m_clen - 1);
187					(*fp)(line, obuf);
188					if (ferror(obuf))
189						return(-1);
190					doclen = 0;
191				}
192				continue;
193			}
194		}
195writeit:
196		if (!ishead && !mp->m_text && mp->m_clen != 0) {
197			if (line[0] == '\n')
198				putc('\n', obuf);
199			clen = mp->m_clen-1;
200			for (;;) {
201				n = clen < sizeof line ? clen : sizeof line;
202				if ((n = fread(line, 1, (int)n, ibuf)) <= 0) {
203					fprintf(stderr, gettext(
204					    "\t(Unexpected end-of-file).\n"));
205					clen = 0;
206				} else {
207					if (fwrite(line, 1, (int)n, obuf) != n) {
208						fprintf(stderr, gettext(
209					"\tError writing to the new file.\n"));
210						fflush(obuf);
211						if (fferror(obuf))
212							return (-1);
213					}
214				}
215				clen -= n;
216				if (clen <= 0) {
217					break;
218				}
219			}
220			c = 0L;
221		} else {
222			(*fp)(line, obuf);
223			if (ferror(obuf))
224				return(-1);
225		}
226	}
227	fflush(obuf);
228	if (ferror(obuf))
229		return(-1);
230	if (ishead && (mailp->m_flag & MSTATUS))
231		printf(gettext("failed to fix up status field\n"));
232	return(lc);
233}
234
235/*
236 * Test if the passed line is a header line, RFC 822 style.
237 */
238int
239headerp(register char *line)
240{
241	register char *cp = line;
242
243	while (*cp && *cp != ' ' && *cp != '\t' && *cp != ':')
244		cp++;
245	return(*cp == ':');
246}
247
248/*
249 * Output a reasonable looking status field.
250 * But if "status" is ignored and doign, forget it.
251 */
252static void
253statusput(
254	register struct message *mp,
255	register FILE *obuf,
256	int doign,
257	int (*fp)(const char *, FILE *))
258{
259	char statout[12];
260
261	if (doign && isign("status", 0))
262		return;
263	if ((mp->m_flag & (MNEW|MREAD)) == MNEW)
264		return;
265	strcpy(statout, "Status: ");
266	if (mp->m_flag & MREAD)
267		strcat(statout, "R");
268	if ((mp->m_flag & MNEW) == 0)
269		strcat(statout, "O");
270	strcat(statout, "\n");
271	(*fp)(statout, obuf);
272}
273
274/*
275 * Interface between the argument list and the mail1 routine
276 * which does all the dirty work.
277 */
278
279int
280mail(char **people)
281{
282	register char *cp2, *cp3;
283	register int s;
284	char *buf, **ap;
285	struct header head;
286
287	for (s = 0, ap = people; *ap; ap++)
288		s += strlen(*ap) + 2;
289	buf = (char *)salloc((unsigned)(s+1));
290	cp2 = buf;
291	for (ap = people; *ap; ap++) {
292		for (cp3 = *ap; *cp3; ) {
293			if (*cp3 == ' ' || *cp3 == '\t') {
294				*cp3++ = ',';
295				while (*cp3 == ' ' || *cp3 == '\t')
296					cp3++;
297			} else
298				cp3++;
299		}
300		cp2 = copy(*ap, cp2);
301		*cp2++ = ',';
302		*cp2++ = ' ';
303	}
304	*cp2 = '\0';
305	head.h_to = buf;
306	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
307	head.h_others = NOSTRPTR;
308	head.h_seq = 0;
309	mail1(&head, Fflag, NOSTR);
310	return(0);
311}
312
313int
314sendm(char *str)
315{
316	if (value("flipm") != NOSTR)
317		return(Sendmail(str));
318	else return(sendmail(str));
319}
320
321int
322Sendm(char *str)
323{
324	if (value("flipm") != NOSTR)
325		return(sendmail(str));
326	else return(Sendmail(str));
327}
328
329/*
330 * Interface to the mail1 routine for the -t flag
331 * (read headers from text).
332 */
333int
334tmail(void)
335{
336	struct header head;
337
338	head.h_to = NOSTR;
339	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
340	head.h_others = NOSTRPTR;
341	head.h_seq = 0;
342	mail1(&head, Fflag, NOSTR);
343	return(0);
344}
345
346/*
347 * Send mail to a bunch of user names.  The interface is through
348 * the mail routine below.
349 */
350static int
351sendmail(char *str)
352{
353	struct header head;
354
355	if (blankline(str))
356		head.h_to = NOSTR;
357	else
358		head.h_to = addto(NOSTR, str);
359	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
360	head.h_others = NOSTRPTR;
361	head.h_seq = 0;
362	mail1(&head, 0, NOSTR);
363	return(0);
364}
365
366/*
367 * Send mail to a bunch of user names.  The interface is through
368 * the mail routine below.
369 * save a copy of the letter
370 */
371static int
372Sendmail(char *str)
373{
374	struct header head;
375
376	if (blankline(str))
377		head.h_to = NOSTR;
378	else
379		head.h_to = addto(NOSTR, str);
380	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
381	head.h_others = NOSTRPTR;
382	head.h_seq = 0;
383	mail1(&head, 1, NOSTR);
384	return(0);
385}
386
387/*
388 * Walk the list of fds, closing all but one.
389 */
390static int
391closefd_walk(void *special_fd, int fd)
392{
393	if (fd > STDERR_FILENO && fd != *(int *)special_fd)
394		(void) close(fd);
395	return (0);
396}
397
398/*
399 * Mail a message on standard input to the people indicated
400 * in the passed header.  (Internal interface).
401 */
402void
403mail1(struct header *hp, int use_to, char *orig_to)
404{
405	pid_t p, pid;
406	int i, s, gotcha;
407	char **namelist, *deliver;
408	struct name *to, *np;
409	FILE *mtf, *fp;
410	int remote = rflag != NOSTR || rmail;
411	char **t;
412	char *deadletter;
413	char recfile[PATHSIZE];
414
415	/*
416	 * Collect user's mail from standard input.
417	 * Get the result as mtf.
418	 */
419
420	pid = (pid_t)-1;
421	if ((mtf = collect(hp)) == NULL)
422		return;
423	hp->h_seq = 1;
424	if (hp->h_subject == NOSTR)
425		hp->h_subject = sflag;
426	if (fsize(mtf) == 0 && hp->h_subject == NOSTR) {
427		printf(gettext("No message !?!\n"));
428		goto out;
429	}
430	if (intty) {
431		printf(gettext("EOT\n"));
432		flush();
433	}
434
435	/*
436	 * If we need to use the To: line to determine the record
437	 * file, save a copy of it before it's sorted below.
438	 */
439
440	if (use_to && orig_to == NOSTR && hp->h_to != NOSTR)
441		orig_to = strcpy((char *)salloc(strlen(hp->h_to)+1), hp->h_to);
442	else if (orig_to == NOSTR)
443		orig_to = "";
444
445	/*
446	 * Now, take the user names from the combined
447	 * to and cc lists and do all the alias
448	 * processing.
449	 */
450
451	senderr = 0;
452	to = cat(extract(hp->h_bcc, GBCC),
453	     cat(extract(hp->h_to, GTO),
454	     extract(hp->h_cc, GCC)));
455	to = translate(outpre(elide(usermap(to))));
456	if (!senderr)
457		mapf(to, myname);
458	mechk(to);
459	for (gotcha = 0, np = to; np != NIL; np = np->n_flink)
460		if ((np->n_type & GDEL) == 0)
461			gotcha++;
462	hp->h_to = detract(to, GTO);
463	hp->h_cc = detract(to, GCC);
464	hp->h_bcc = detract(to, GBCC);
465	if ((mtf = infix(hp, mtf)) == NULL) {
466		fprintf(stderr, gettext(". . . message lost, sorry.\n"));
467		return;
468	}
469	rewind(mtf);
470	if (askme && isatty(0)) {
471		char ans[64];
472		puthead(hp, stdout, GTO|GCC|GBCC, 0);
473		printf(gettext("Send? "));
474		printf("[yes] ");
475		if (fgets(ans, sizeof(ans), stdin) && ans[0] &&
476				(tolower(ans[0]) != 'y' && ans[0] != '\n'))
477			goto dead;
478	}
479	if (senderr)
480		goto dead;
481	/*
482	 * Look through the recipient list for names with /'s
483	 * in them which we write to as files directly.
484	 */
485	i = outof(to, mtf);
486	rewind(mtf);
487	if (!gotcha && !i) {
488		printf(gettext("No recipients specified\n"));
489		goto dead;
490	}
491	if (senderr)
492		goto dead;
493
494	getrecf(orig_to, recfile, use_to, sizeof (recfile));
495	if (recfile != NOSTR && *recfile)
496		savemail(safeexpand(recfile), hp, mtf);
497	if (!gotcha)
498		goto out;
499	namelist = unpack(to);
500	if (debug) {
501		fprintf(stderr, "Recipients of message:\n");
502		for (t = namelist; *t != NOSTR; t++)
503			fprintf(stderr, " \"%s\"", *t);
504		fprintf(stderr, "\n");
505		return;
506	}
507
508	/*
509	 * Wait, to absorb a potential zombie, then
510	 * fork, set up the temporary mail file as standard
511	 * input for "mail" and exec with the user list we generated
512	 * far above. Return the process id to caller in case it
513	 * wants to await the completion of mail.
514	 */
515
516#ifdef VMUNIX
517	while (wait3((int *)0, WNOHANG, (struct rusage *)0) > 0)
518		;
519#else
520#ifdef preSVr4
521	wait((int *)0);
522#else
523	while (waitpid((pid_t)-1, (int *)0, WNOHANG) > 0)
524		;
525#endif
526#endif
527	rewind(mtf);
528	pid = fork();
529	if (pid == (pid_t)-1) {
530		perror("fork");
531dead:
532		deadletter = Getf("DEAD");
533		if (fp = fopen(deadletter,
534		    value("appenddeadletter") == NOSTR ? "w" : "a")) {
535			chmod(deadletter, DEADPERM);
536			puthead(hp, fp, GMASK|GCLEN, fsize(mtf) - textpos);
537			fseek(mtf, textpos, 0);
538			lcwrite(deadletter, mtf, fp,
539			    value("appenddeadletter") != NOSTR);
540			fclose(fp);
541		} else
542			perror(deadletter);
543		goto out;
544	}
545	if (pid == 0) {
546		sigchild();
547#ifdef SIGTSTP
548		if (remote == 0) {
549			sigset(SIGTSTP, SIG_IGN);
550			sigset(SIGTTIN, SIG_IGN);
551			sigset(SIGTTOU, SIG_IGN);
552		}
553#endif
554		sigset(SIGHUP, SIG_IGN);
555		sigset(SIGINT, SIG_IGN);
556		sigset(SIGQUIT, SIG_IGN);
557		s = fileno(mtf);
558		(void) fdwalk(closefd_walk, &s);
559		close(0);
560		dup(s);
561		close(s);
562#ifdef CC
563		submit(getpid());
564#endif /* CC */
565		if ((deliver = value("sendmail")) == NOSTR)
566#ifdef SENDMAIL
567			deliver = SENDMAIL;
568#else
569			deliver = MAIL;
570#endif
571		execvp(safeexpand(deliver), namelist);
572		perror(deliver);
573		exit(1);
574	}
575
576	if (value("sendwait")!=NOSTR)
577		remote++;
578out:
579	if (remote) {
580		while ((p = wait(&s)) != pid && p != (pid_t)-1)
581			;
582		if (s != 0)
583			senderr++;
584		pid = 0;
585	}
586	fclose(mtf);
587	return;
588}
589
590/*
591 * Prepend a header in front of the collected stuff
592 * and return the new file.
593 */
594
595static FILE *
596infix(struct header *hp, FILE *fi)
597{
598	register FILE *nfo, *nfi;
599	register int c;
600	char *postmark, *returnaddr;
601	int fd = -1;
602
603	rewind(fi);
604	if ((fd = open(tempMail, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
605	(nfo = fdopen(fd, "w")) == NULL) {
606		perror(tempMail);
607		return(fi);
608	}
609	if ((nfi = fopen(tempMail, "r")) == NULL) {
610		perror(tempMail);
611		fclose(nfo);
612		return(fi);
613	}
614	removefile(tempMail);
615	postmark = value("postmark");
616	returnaddr = value("returnaddr");
617	if ((postmark != NOSTR) || (returnaddr != NOSTR)) {
618		if (returnaddr && *returnaddr)
619			fprintf(nfo, "From: %s", returnaddr);
620		else
621			fprintf(nfo, "From: %s@%s", myname, host);
622		if (postmark && *postmark)
623			fprintf(nfo, " (%s)", postmark);
624		putc('\n', nfo);
625	}
626	puthead(hp, nfo, (GMASK & ~GBCC) | GCLEN, fsize(fi));
627	textpos = ftell(nfo);
628	while ((c = getc(fi)) != EOF)
629		putc(c, nfo);
630	if (ferror(fi)) {
631		perror("read");
632		return(fi);
633	}
634	fflush(nfo);
635	if (fferror(nfo)) {
636		perror(tempMail);
637		fclose(nfo);
638		fclose(nfi);
639		return(fi);
640	}
641	fclose(nfo);
642	fclose(fi);
643	rewind(nfi);
644	return(nfi);
645}
646
647/*
648 * Dump the message header on the
649 * passed file buffer.
650 */
651
652int
653puthead(struct header *hp, FILE *fo, int w, long clen)
654{
655	register int gotcha;
656
657	gotcha = 0;
658	if (hp->h_to != NOSTR && (w & GTO))
659		fprintf(fo, "To: "), fmt(hp->h_to, fo), gotcha++;
660	if ((w & GSUBJECT) && (int)value("bsdcompat"))
661		if (hp->h_subject != NOSTR && *hp->h_subject)
662			fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
663		else
664			if (sflag && *sflag)
665				fprintf(fo, "Subject: %s\n", sflag), gotcha++;
666	if (hp->h_cc != NOSTR && (w & GCC))
667		fprintf(fo, "Cc: "), fmt(hp->h_cc, fo), gotcha++;
668	if (hp->h_bcc != NOSTR && (w & GBCC))
669		fprintf(fo, "Bcc: "), fmt(hp->h_bcc, fo), gotcha++;
670	if (hp->h_defopt != NOSTR && (w & GDEFOPT))
671		if (receipt_flg)
672			fprintf(fo, "Return-Receipt-To: %s\n",
673				hp->h_defopt), gotcha++;
674		else
675		fprintf(fo, "Default-Options: %s\n", hp->h_defopt), gotcha++;
676	if ((w & GSUBJECT) && !(int)value("bsdcompat"))
677		if (hp->h_subject != NOSTR && *hp->h_subject)
678			fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
679		else
680			if (sflag && *sflag)
681				fprintf(fo, "Subject: %s\n", sflag), gotcha++;
682	if (hp->h_others != NOSTRPTR && (w & GOTHER)) {
683		char **p;
684		for (p = hp->h_others; *p; p++)
685			fprintf(fo, "%s\n", *p);
686		gotcha++;
687	}
688#ifndef preSVr4
689	if (w & GCLEN)
690		fprintf(fo, "Content-Length: %ld\n", clen), gotcha++;
691#endif
692	if (gotcha && (w & GNL))
693		putc('\n', fo);
694	return(0);
695}
696
697/*
698 * Format the given text to not exceed 78 characters.
699 */
700static void
701fmt(register char *str, register FILE *fo)
702{
703	register int col = 4;
704	char name[256];
705	int len;
706
707	str = strcpy((char *)salloc(strlen(str)+1), str);
708	while (str = yankword(str, name, sizeof (name), 1)) {
709		len = strlen(name);
710		if (col > 4) {
711			if (col + len > 76) {
712				fputs(",\n    ", fo);
713				col = 4;
714			} else {
715				fputs(", ", fo);
716				col += 2;
717			}
718		}
719		fputs(name, fo);
720		col += len;
721	}
722	putc('\n', fo);
723}
724
725/*
726 * Save the outgoing mail on the passed file.
727 */
728static int
729savemail(char name[], struct header *hp, FILE *fi)
730{
731	register FILE *fo;
732	time_t now;
733	char *n;
734#ifdef preSVr4
735	char line[BUFSIZ];
736#else
737	int c;
738#endif
739
740	if (debug)
741		fprintf(stderr, gettext("save in '%s'\n"), name);
742	if ((fo = fopen(name, "a")) == NULL) {
743		perror(name);
744		return(-1);
745	}
746	time(&now);
747	n = rflag;
748	if (n == NOSTR)
749		n = myname;
750	fprintf(fo, "From %s %s", n, ctime(&now));
751	puthead(hp, fo, GMASK|GCLEN, fsize(fi) - textpos);
752	fseek(fi, textpos, 0);
753#ifdef preSVr4
754	while (fgets(line, sizeof line, fi)) {
755		if (!strncmp(line, "From ", 5))
756			putc('>', fo);
757		fputs(line, fo);
758	}
759#else
760	while ((c = getc(fi)) != EOF)
761		putc(c, fo);
762#endif
763	putc('\n', fo);
764	fflush(fo);
765	if (fferror(fo))
766		perror(name);
767	fclose(fo);
768	return(0);
769}
770