1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * Mail -- a mail program
34 *
35 * Handle name lists.
36 */
37
38#include "rcv.h"
39#include <fcntl.h>
40#include "extern.h"
41
42/*
43 * Allocate a single element of a name list,
44 * initialize its name field to the passed
45 * name and return it.
46 */
47struct name *
48nalloc(char str[], int ntype)
49{
50	struct name *np;
51
52	np = (struct name *)salloc(sizeof(*np));
53	np->n_flink = NULL;
54	np->n_blink = NULL;
55	np->n_type = ntype;
56	np->n_name = savestr(str);
57	return (np);
58}
59
60/*
61 * Find the tail of a list and return it.
62 */
63struct name *
64tailof(struct name *name)
65{
66	struct name *np;
67
68	np = name;
69	if (np == NULL)
70		return (NULL);
71	while (np->n_flink != NULL)
72		np = np->n_flink;
73	return (np);
74}
75
76/*
77 * Extract a list of names from a line,
78 * and make a list of names from it.
79 * Return the list or NULL if none found.
80 */
81struct name *
82extract(char *line, int ntype)
83{
84	char *cp, *nbuf;
85	struct name *top, *np, *t;
86
87	if (line == NULL || *line == '\0')
88		return (NULL);
89	if ((nbuf = malloc(strlen(line) + 1)) == NULL)
90		err(1, "Out of memory");
91	top = NULL;
92	np = NULL;
93	cp = line;
94	while ((cp = yankword(cp, nbuf)) != NULL) {
95		t = nalloc(nbuf, ntype);
96		if (top == NULL)
97			top = t;
98		else
99			np->n_flink = t;
100		t->n_blink = np;
101		np = t;
102	}
103	(void)free(nbuf);
104	return (top);
105}
106
107/*
108 * Turn a list of names into a string of the same names.
109 */
110char *
111detract(struct name *np, int ntype)
112{
113	int s, comma;
114	char *cp, *top;
115	struct name *p;
116
117	comma = ntype & GCOMMA;
118	if (np == NULL)
119		return (NULL);
120	ntype &= ~GCOMMA;
121	s = 0;
122	if (debug && comma)
123		fprintf(stderr, "detract asked to insert commas\n");
124	for (p = np; p != NULL; p = p->n_flink) {
125		if (ntype && (p->n_type & GMASK) != ntype)
126			continue;
127		s += strlen(p->n_name) + 1;
128		if (comma)
129			s++;
130	}
131	if (s == 0)
132		return (NULL);
133	s += 2;
134	top = salloc(s);
135	cp = top;
136	for (p = np; p != NULL; p = p->n_flink) {
137		if (ntype && (p->n_type & GMASK) != ntype)
138			continue;
139		cp += strlcpy(cp, p->n_name, strlen(p->n_name) + 1);
140		if (comma && p->n_flink != NULL)
141			*cp++ = ',';
142		*cp++ = ' ';
143	}
144	*--cp = '\0';
145	if (comma && *--cp == ',')
146		*cp = '\0';
147	return (top);
148}
149
150/*
151 * Grab a single word (liberal word)
152 * Throw away things between ()'s, and take anything between <>.
153 */
154char *
155yankword(char *ap, char *wbuf)
156{
157	char *cp, *cp2;
158
159	cp = ap;
160	for (;;) {
161		if (*cp == '\0')
162			return (NULL);
163		if (*cp == '(') {
164			int nesting = 0;
165
166			while (*cp != '\0') {
167				switch (*cp++) {
168				case '(':
169					nesting++;
170					break;
171				case ')':
172					--nesting;
173					break;
174				}
175				if (nesting <= 0)
176					break;
177			}
178		} else if (*cp == ' ' || *cp == '\t' || *cp == ',')
179			cp++;
180		else
181			break;
182	}
183	if (*cp ==  '<')
184		for (cp2 = wbuf; *cp && (*cp2++ = *cp++) != '>';)
185			;
186	else
187		for (cp2 = wbuf; *cp != '\0' && strchr(" \t,(", *cp) == NULL;
188		    *cp2++ = *cp++)
189			;
190	*cp2 = '\0';
191	return (cp);
192}
193
194/*
195 * Grab a single login name (liberal word)
196 * Throw away things between ()'s, take anything between <>,
197 * and look for words before metacharacters %, @, !.
198 */
199char *
200yanklogin(char *ap, char *wbuf)
201{
202	char *cp, *cp2, *cp_temp;
203	int n;
204
205	cp = ap;
206	for (;;) {
207		if (*cp == '\0')
208			return (NULL);
209		if (*cp == '(') {
210			int nesting = 0;
211
212			while (*cp != '\0') {
213				switch (*cp++) {
214				case '(':
215					nesting++;
216					break;
217				case ')':
218					--nesting;
219					break;
220				}
221				if (nesting <= 0)
222					break;
223			}
224		} else if (*cp == ' ' || *cp == '\t' || *cp == ',')
225			cp++;
226		else
227			break;
228	}
229
230	/*
231	 * Now, let's go forward till we meet the needed character,
232	 * and step one word back.
233	 */
234
235	/* First, remember current point. */
236	cp_temp = cp;
237	n = 0;
238
239	/*
240	 * Note that we look ahead in a cycle. This is safe, since
241	 * non-end of string is checked first.
242	 */
243	while(*cp != '\0' && strchr("@%!", *(cp + 1)) == NULL)
244		cp++;
245
246	/*
247	 * Now, start stepping back to the first non-word character,
248	 * while counting the number of symbols in a word.
249	 */
250	while(cp != cp_temp && strchr(" \t,<>", *(cp - 1)) == NULL) {
251		n++;
252		cp--;
253	}
254
255	/* Finally, grab the word forward. */
256	cp2 = wbuf;
257	while(n >= 0) {
258		*cp2++=*cp++;
259		n--;
260	}
261
262	*cp2 = '\0';
263	return (cp);
264}
265
266/*
267 * For each recipient in the passed name list with a /
268 * in the name, append the message to the end of the named file
269 * and remove him from the recipient list.
270 *
271 * Recipients whose name begins with | are piped through the given
272 * program and removed.
273 */
274struct name *
275outof(struct name *names, FILE *fo, struct header *hp)
276{
277	int c, ispipe;
278	struct name *np, *top;
279	time_t now;
280	char *date, *fname;
281	FILE *fout, *fin;
282
283	top = names;
284	np = names;
285	(void)time(&now);
286	date = ctime(&now);
287	while (np != NULL) {
288		if (!isfileaddr(np->n_name) && np->n_name[0] != '|') {
289			np = np->n_flink;
290			continue;
291		}
292		ispipe = np->n_name[0] == '|';
293		if (ispipe)
294			fname = np->n_name+1;
295		else
296			fname = expand(np->n_name);
297
298		/*
299		 * See if we have copied the complete message out yet.
300		 * If not, do so.
301		 */
302
303		if (image < 0) {
304			int fd;
305			char tempname[PATHSIZE];
306
307			(void)snprintf(tempname, sizeof(tempname),
308			    "%s/mail.ReXXXXXXXXXX", tmpdir);
309			if ((fd = mkstemp(tempname)) == -1 ||
310			    (fout = Fdopen(fd, "a")) == NULL) {
311				warn("%s", tempname);
312				senderr++;
313				goto cant;
314			}
315			image = open(tempname, O_RDWR);
316			(void)rm(tempname);
317			if (image < 0) {
318				warn("%s", tempname);
319				senderr++;
320				(void)Fclose(fout);
321				goto cant;
322			}
323			(void)fcntl(image, F_SETFD, 1);
324			fprintf(fout, "From %s %s", myname, date);
325			puthead(hp, fout,
326			    GTO|GSUBJECT|GCC|GREPLYTO|GINREPLYTO|GNL);
327			while ((c = getc(fo)) != EOF)
328				(void)putc(c, fout);
329			rewind(fo);
330			fprintf(fout, "\n");
331			(void)fflush(fout);
332			if (ferror(fout)) {
333				warn("%s", tempname);
334				senderr++;
335				(void)Fclose(fout);
336				goto cant;
337			}
338			(void)Fclose(fout);
339		}
340
341		/*
342		 * Now either copy "image" to the desired file
343		 * or give it as the standard input to the desired
344		 * program as appropriate.
345		 */
346
347		if (ispipe) {
348			int pid;
349			char *sh;
350			sigset_t nset;
351
352			/*
353			 * XXX
354			 * We can't really reuse the same image file,
355			 * because multiple piped recipients will
356			 * share the same lseek location and trample
357			 * on one another.
358			 */
359			if ((sh = value("SHELL")) == NULL)
360				sh = _PATH_CSHELL;
361			(void)sigemptyset(&nset);
362			(void)sigaddset(&nset, SIGHUP);
363			(void)sigaddset(&nset, SIGINT);
364			(void)sigaddset(&nset, SIGQUIT);
365			pid = start_command(sh, &nset, image, -1, "-c", fname,
366			    NULL);
367			if (pid < 0) {
368				senderr++;
369				goto cant;
370			}
371			free_child(pid);
372		} else {
373			int f;
374			if ((fout = Fopen(fname, "a")) == NULL) {
375				warn("%s", fname);
376				senderr++;
377				goto cant;
378			}
379			if ((f = dup(image)) < 0) {
380				warn("dup");
381				fin = NULL;
382			} else
383				fin = Fdopen(f, "r");
384			if (fin == NULL) {
385				fprintf(stderr, "Can't reopen image\n");
386				(void)Fclose(fout);
387				senderr++;
388				goto cant;
389			}
390			rewind(fin);
391			while ((c = getc(fin)) != EOF)
392				(void)putc(c, fout);
393			if (ferror(fout)) {
394				warnx("%s", fname);
395				senderr++;
396				(void)Fclose(fout);
397				(void)Fclose(fin);
398				goto cant;
399			}
400			(void)Fclose(fout);
401			(void)Fclose(fin);
402		}
403cant:
404		/*
405		 * In days of old we removed the entry from the
406		 * the list; now for sake of header expansion
407		 * we leave it in and mark it as deleted.
408		 */
409		np->n_type |= GDEL;
410		np = np->n_flink;
411	}
412	if (image >= 0) {
413		(void)close(image);
414		image = -1;
415	}
416	return (top);
417}
418
419/*
420 * Determine if the passed address is a local "send to file" address.
421 * If any of the network metacharacters precedes any slashes, it can't
422 * be a filename.  We cheat with .'s to allow path names like ./...
423 */
424int
425isfileaddr(char *name)
426{
427	char *cp;
428
429	if (*name == '+')
430		return (1);
431	for (cp = name; *cp != '\0'; cp++) {
432		if (*cp == '!' || *cp == '%' || *cp == '@')
433			return (0);
434		if (*cp == '/')
435			return (1);
436	}
437	return (0);
438}
439
440/*
441 * Map all of the aliased users in the invoker's mailrc
442 * file and insert them into the list.
443 * Changed after all these months of service to recursively
444 * expand names (2/14/80).
445 */
446
447struct name *
448usermap(struct name *names)
449{
450	struct name *new, *np, *cp;
451	struct grouphead *gh;
452	int metoo;
453
454	new = NULL;
455	np = names;
456	metoo = (value("metoo") != NULL);
457	while (np != NULL) {
458		if (np->n_name[0] == '\\') {
459			cp = np->n_flink;
460			new = put(new, np);
461			np = cp;
462			continue;
463		}
464		gh = findgroup(np->n_name);
465		cp = np->n_flink;
466		if (gh != NULL)
467			new = gexpand(new, gh, metoo, np->n_type);
468		else
469			new = put(new, np);
470		np = cp;
471	}
472	return (new);
473}
474
475/*
476 * Recursively expand a group name.  We limit the expansion to some
477 * fixed level to keep things from going haywire.
478 * Direct recursion is not expanded for convenience.
479 */
480
481struct name *
482gexpand(struct name *nlist, struct grouphead *gh, int metoo, int ntype)
483{
484	struct group *gp;
485	struct grouphead *ngh;
486	struct name *np;
487	static int depth;
488	char *cp;
489
490	if (depth > MAXEXP) {
491		printf("Expanding alias to depth larger than %d\n", MAXEXP);
492		return (nlist);
493	}
494	depth++;
495	for (gp = gh->g_list; gp != NULL; gp = gp->ge_link) {
496		cp = gp->ge_name;
497		if (*cp == '\\')
498			goto quote;
499		if (strcmp(cp, gh->g_name) == 0)
500			goto quote;
501		if ((ngh = findgroup(cp)) != NULL) {
502			nlist = gexpand(nlist, ngh, metoo, ntype);
503			continue;
504		}
505quote:
506		np = nalloc(cp, ntype);
507		/*
508		 * At this point should allow to expand
509		 * to self if only person in group
510		 */
511		if (gp == gh->g_list && gp->ge_link == NULL)
512			goto skip;
513		if (!metoo && strcmp(cp, myname) == 0)
514			np->n_type |= GDEL;
515skip:
516		nlist = put(nlist, np);
517	}
518	depth--;
519	return (nlist);
520}
521
522/*
523 * Concatenate the two passed name lists, return the result.
524 */
525struct name *
526cat(struct name *n1, struct name *n2)
527{
528	struct name *tail;
529
530	if (n1 == NULL)
531		return (n2);
532	if (n2 == NULL)
533		return (n1);
534	tail = tailof(n1);
535	tail->n_flink = n2;
536	n2->n_blink = tail;
537	return (n1);
538}
539
540/*
541 * Unpack the name list onto a vector of strings.
542 * Return an error if the name list won't fit.
543 */
544char **
545unpack(struct name *np)
546{
547	char **ap, **top;
548	struct name *n;
549	int t, extra, metoo, verbose;
550
551	n = np;
552	if ((t = count(n)) == 0)
553		errx(1, "No names to unpack");
554	/*
555	 * Compute the number of extra arguments we will need.
556	 * We need at least two extra -- one for "mail" and one for
557	 * the terminating 0 pointer.  Additional spots may be needed
558	 * to pass along -f to the host mailer.
559	 */
560	extra = 2;
561	extra++;
562	metoo = value("metoo") != NULL;
563	if (metoo)
564		extra++;
565	verbose = value("verbose") != NULL;
566	if (verbose)
567		extra++;
568	top = (char **)salloc((t + extra) * sizeof(*top));
569	ap = top;
570	*ap++ = "sendmail";
571	*ap++ = "-i";
572	if (metoo)
573		*ap++ = "-m";
574	if (verbose)
575		*ap++ = "-v";
576	for (; n != NULL; n = n->n_flink)
577		if ((n->n_type & GDEL) == 0)
578			*ap++ = n->n_name;
579	*ap = NULL;
580	return (top);
581}
582
583/*
584 * Remove all of the duplicates from the passed name list by
585 * insertion sorting them, then checking for dups.
586 * Return the head of the new list.
587 */
588struct name *
589elide(struct name *names)
590{
591	struct name *np, *t, *new;
592	struct name *x;
593
594	if (names == NULL)
595		return (NULL);
596	new = names;
597	np = names;
598	np = np->n_flink;
599	if (np != NULL)
600		np->n_blink = NULL;
601	new->n_flink = NULL;
602	while (np != NULL) {
603		t = new;
604		while (strcasecmp(t->n_name, np->n_name) < 0) {
605			if (t->n_flink == NULL)
606				break;
607			t = t->n_flink;
608		}
609
610		/*
611		 * If we ran out of t's, put the new entry after
612		 * the current value of t.
613		 */
614
615		if (strcasecmp(t->n_name, np->n_name) < 0) {
616			t->n_flink = np;
617			np->n_blink = t;
618			t = np;
619			np = np->n_flink;
620			t->n_flink = NULL;
621			continue;
622		}
623
624		/*
625		 * Otherwise, put the new entry in front of the
626		 * current t.  If at the front of the list,
627		 * the new guy becomes the new head of the list.
628		 */
629
630		if (t == new) {
631			t = np;
632			np = np->n_flink;
633			t->n_flink = new;
634			new->n_blink = t;
635			t->n_blink = NULL;
636			new = t;
637			continue;
638		}
639
640		/*
641		 * The normal case -- we are inserting into the
642		 * middle of the list.
643		 */
644
645		x = np;
646		np = np->n_flink;
647		x->n_flink = t;
648		x->n_blink = t->n_blink;
649		t->n_blink->n_flink = x;
650		t->n_blink = x;
651	}
652
653	/*
654	 * Now the list headed up by new is sorted.
655	 * Go through it and remove duplicates.
656	 */
657
658	np = new;
659	while (np != NULL) {
660		t = np;
661		while (t->n_flink != NULL &&
662		    strcasecmp(np->n_name, t->n_flink->n_name) == 0)
663			t = t->n_flink;
664		if (t == np || t == NULL) {
665			np = np->n_flink;
666			continue;
667		}
668
669		/*
670		 * Now t points to the last entry with the same name
671		 * as np.  Make np point beyond t.
672		 */
673
674		np->n_flink = t->n_flink;
675		if (t->n_flink != NULL)
676			t->n_flink->n_blink = np;
677		np = np->n_flink;
678	}
679	return (new);
680}
681
682/*
683 * Put another node onto a list of names and return
684 * the list.
685 */
686struct name *
687put(struct name *list, struct name *node)
688{
689	node->n_flink = list;
690	node->n_blink = NULL;
691	if (list != NULL)
692		list->n_blink = node;
693	return (node);
694}
695
696/*
697 * Determine the number of undeleted elements in
698 * a name list and return it.
699 */
700int
701count(struct name *np)
702{
703	int c;
704
705	for (c = 0; np != NULL; np = np->n_flink)
706		if ((np->n_type & GDEL) == 0)
707			c++;
708	return (c);
709}
710
711/*
712 * Delete the given name from a namelist.
713 */
714struct name *
715delname(struct name *np, char name[])
716{
717	struct name *p;
718
719	for (p = np; p != NULL; p = p->n_flink)
720		if (strcasecmp(p->n_name, name) == 0) {
721			if (p->n_blink == NULL) {
722				if (p->n_flink != NULL)
723					p->n_flink->n_blink = NULL;
724				np = p->n_flink;
725				continue;
726			}
727			if (p->n_flink == NULL) {
728				if (p->n_blink != NULL)
729					p->n_blink->n_flink = NULL;
730				continue;
731			}
732			p->n_blink->n_flink = p->n_flink;
733			p->n_flink->n_blink = p->n_blink;
734		}
735	return (np);
736}
737
738/*
739 * Pretty print a name list
740 * Uncomment it if you need it.
741 */
742
743/*
744void
745prettyprint(struct name *name)
746{
747	struct name *np;
748
749	np = name;
750	while (np != NULL) {
751		fprintf(stderr, "%s(%d) ", np->n_name, np->n_type);
752		np = np->n_flink;
753	}
754	fprintf(stderr, "\n");
755}
756*/
757