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#include <sys/time.h>
33
34#include <fcntl.h>
35
36#include "rcv.h"
37#include "extern.h"
38
39/*
40 * Mail -- a mail program
41 *
42 * Auxiliary functions.
43 */
44
45static char *save2str(char *, char *);
46
47/*
48 * Return a pointer to a dynamic copy of the argument.
49 */
50char *
51savestr(char *str)
52{
53	char *new;
54	int size = strlen(str) + 1;
55
56	if ((new = salloc(size)) != NULL)
57		bcopy(str, new, size);
58	return (new);
59}
60
61/*
62 * Make a copy of new argument incorporating old one.
63 */
64static char *
65save2str(char *str, char *old)
66{
67	char *new;
68	int newsize = strlen(str) + 1;
69	int oldsize = old ? strlen(old) + 1 : 0;
70
71	if ((new = salloc(newsize + oldsize)) != NULL) {
72		if (oldsize) {
73			bcopy(old, new, oldsize);
74			new[oldsize - 1] = ' ';
75		}
76		bcopy(str, new + oldsize, newsize);
77	}
78	return (new);
79}
80
81/*
82 * Touch the named message by setting its MTOUCH flag.
83 * Touched messages have the effect of not being sent
84 * back to the system mailbox on exit.
85 */
86void
87touch(struct message *mp)
88{
89
90	mp->m_flag |= MTOUCH;
91	if ((mp->m_flag & MREAD) == 0)
92		mp->m_flag |= MREAD|MSTATUS;
93}
94
95/*
96 * Test to see if the passed file name is a directory.
97 * Return true if it is.
98 */
99int
100isdir(char name[])
101{
102	struct stat sbuf;
103
104	if (stat(name, &sbuf) < 0)
105		return (0);
106	return (S_ISDIR(sbuf.st_mode));
107}
108
109/*
110 * Count the number of arguments in the given string raw list.
111 */
112int
113argcount(char **argv)
114{
115	char **ap;
116
117	for (ap = argv; *ap++ != NULL;)
118		;
119	return (ap - argv - 1);
120}
121
122/*
123 * Return the desired header line from the passed message
124 * pointer (or NULL if the desired header field is not available).
125 */
126char *
127hfield(const char *field, struct message *mp)
128{
129	FILE *ibuf;
130	char linebuf[LINESIZE];
131	int lc;
132	char *hfield;
133	char *colon, *oldhfield = NULL;
134
135	ibuf = setinput(mp);
136	if ((lc = mp->m_lines - 1) < 0)
137		return (NULL);
138	if (readline(ibuf, linebuf, LINESIZE) < 0)
139		return (NULL);
140	while (lc > 0) {
141		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
142			return (oldhfield);
143		if ((hfield = ishfield(linebuf, colon, field)) != NULL)
144			oldhfield = save2str(hfield, oldhfield);
145	}
146	return (oldhfield);
147}
148
149/*
150 * Return the next header field found in the given message.
151 * Return >= 0 if something found, < 0 elsewise.
152 * "colon" is set to point to the colon in the header.
153 * Must deal with \ continuations & other such fraud.
154 */
155int
156gethfield(FILE *f, char linebuf[], int rem, char **colon)
157{
158	char line2[LINESIZE];
159	char *cp, *cp2;
160	int c;
161
162	for (;;) {
163		if (--rem < 0)
164			return (-1);
165		if ((c = readline(f, linebuf, LINESIZE)) <= 0)
166			return (-1);
167		for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
168		    cp++)
169			;
170		if (*cp != ':' || cp == linebuf)
171			continue;
172		/*
173		 * I guess we got a headline.
174		 * Handle wraparounding
175		 */
176		*colon = cp;
177		cp = linebuf + c;
178		for (;;) {
179			while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
180				;
181			cp++;
182			if (rem <= 0)
183				break;
184			ungetc(c = getc(f), f);
185			if (c != ' ' && c != '\t')
186				break;
187			if ((c = readline(f, line2, LINESIZE)) < 0)
188				break;
189			rem--;
190			for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
191				;
192			c -= cp2 - line2;
193			if (cp + c >= linebuf + LINESIZE - 2)
194				break;
195			*cp++ = ' ';
196			bcopy(cp2, cp, c);
197			cp += c;
198		}
199		*cp = 0;
200		return (rem);
201	}
202	/* NOTREACHED */
203}
204
205/*
206 * Check whether the passed line is a header line of
207 * the desired breed.  Return the field body, or 0.
208 */
209
210char*
211ishfield(char *linebuf, char *colon, const char *field)
212{
213	char *cp = colon;
214
215	*cp = 0;
216	if (strcasecmp(linebuf, field) != 0) {
217		*cp = ':';
218		return (0);
219	}
220	*cp = ':';
221	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
222		;
223	return (cp);
224}
225
226/*
227 * Copy a string and lowercase the result.
228 * dsize: space left in buffer (including space for NULL)
229 */
230void
231istrncpy(char *dest, const char *src, size_t dsize)
232{
233
234	strlcpy(dest, src, dsize);
235	for (; *dest; dest++)
236		*dest = tolower((unsigned char)*dest);
237}
238
239/*
240 * The following code deals with input stacking to do source
241 * commands.  All but the current file pointer are saved on
242 * the stack.
243 */
244
245static	int	ssp;			/* Top of file stack */
246struct sstack {
247	FILE	*s_file;		/* File we were in. */
248	int	s_cond;			/* Saved state of conditionals */
249	int	s_loading;		/* Loading .mailrc, etc. */
250};
251#define	SSTACK_SIZE	64		/* XXX was NOFILE. */
252static struct sstack sstack[SSTACK_SIZE];
253
254/*
255 * Pushdown current input file and switch to a new one.
256 * Set the global flag "sourcing" so that others will realize
257 * that they are no longer reading from a tty (in all probability).
258 */
259int
260source(void *arg)
261{
262	char **arglist = arg;
263	FILE *fi;
264	char *cp;
265
266	if ((cp = expand(*arglist)) == NULL)
267		return (1);
268	if ((fi = Fopen(cp, "r")) == NULL) {
269		warn("%s", cp);
270		return (1);
271	}
272	if (ssp >= SSTACK_SIZE - 1) {
273		printf("Too much \"sourcing\" going on.\n");
274		(void)Fclose(fi);
275		return (1);
276	}
277	sstack[ssp].s_file = input;
278	sstack[ssp].s_cond = cond;
279	sstack[ssp].s_loading = loading;
280	ssp++;
281	loading = 0;
282	cond = CANY;
283	input = fi;
284	sourcing++;
285	return (0);
286}
287
288/*
289 * Pop the current input back to the previous level.
290 * Update the "sourcing" flag as appropriate.
291 */
292int
293unstack(void)
294{
295	if (ssp <= 0) {
296		printf("\"Source\" stack over-pop.\n");
297		sourcing = 0;
298		return (1);
299	}
300	(void)Fclose(input);
301	if (cond != CANY)
302		printf("Unmatched \"if\"\n");
303	ssp--;
304	cond = sstack[ssp].s_cond;
305	loading = sstack[ssp].s_loading;
306	input = sstack[ssp].s_file;
307	if (ssp == 0)
308		sourcing = loading;
309	return (0);
310}
311
312/*
313 * Touch the indicated file.
314 * This is nifty for the shell.
315 */
316void
317alter(char *name)
318{
319	struct timespec ts[2];
320
321	(void)clock_gettime(CLOCK_REALTIME, &ts[0]);
322	ts[0].tv_sec++;
323	ts[1].tv_sec = 0;
324	ts[1].tv_nsec = UTIME_OMIT;
325	(void)utimensat(AT_FDCWD, name, ts, 0);
326}
327
328/*
329 * Get sender's name from this message.  If the message has
330 * a bunch of arpanet stuff in it, we may have to skin the name
331 * before returning it.
332 */
333char *
334nameof(struct message *mp, int reptype)
335{
336	char *cp, *cp2;
337
338	cp = skin(name1(mp, reptype));
339	if (reptype != 0 || charcount(cp, '!') < 2)
340		return (cp);
341	cp2 = strrchr(cp, '!');
342	cp2--;
343	while (cp2 > cp && *cp2 != '!')
344		cp2--;
345	if (*cp2 == '!')
346		return (cp2 + 1);
347	return (cp);
348}
349
350/*
351 * Start of a "comment".
352 * Ignore it.
353 */
354char *
355skip_comment(char *cp)
356{
357	int nesting = 1;
358
359	for (; nesting > 0 && *cp; cp++) {
360		switch (*cp) {
361		case '\\':
362			if (cp[1])
363				cp++;
364			break;
365		case '(':
366			nesting++;
367			break;
368		case ')':
369			nesting--;
370			break;
371		}
372	}
373	return (cp);
374}
375
376/*
377 * Skin an arpa net address according to the RFC 822 interpretation
378 * of "host-phrase."
379 */
380char *
381skin(char *name)
382{
383	char *nbuf, *bufend, *cp, *cp2;
384	int c, gotlt, lastsp;
385
386	if (name == NULL)
387		return (NULL);
388	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
389	    && strchr(name, ' ') == NULL)
390		return (name);
391
392	/* We assume that length(input) <= length(output) */
393	if ((nbuf = malloc(strlen(name) + 1)) == NULL)
394		err(1, "Out of memory");
395	gotlt = 0;
396	lastsp = 0;
397	bufend = nbuf;
398	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
399		switch (c) {
400		case '(':
401			cp = skip_comment(cp);
402			lastsp = 0;
403			break;
404
405		case '"':
406			/*
407			 * Start of a "quoted-string".
408			 * Copy it in its entirety.
409			 */
410			while ((c = *cp) != '\0') {
411				cp++;
412				if (c == '"')
413					break;
414				if (c != '\\')
415					*cp2++ = c;
416				else if ((c = *cp) != '\0') {
417					*cp2++ = c;
418					cp++;
419				}
420			}
421			lastsp = 0;
422			break;
423
424		case ' ':
425			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
426				cp += 3, *cp2++ = '@';
427			else
428			if (cp[0] == '@' && cp[1] == ' ')
429				cp += 2, *cp2++ = '@';
430			else
431				lastsp = 1;
432			break;
433
434		case '<':
435			cp2 = bufend;
436			gotlt++;
437			lastsp = 0;
438			break;
439
440		case '>':
441			if (gotlt) {
442				gotlt = 0;
443				while ((c = *cp) != '\0' && c != ',') {
444					cp++;
445					if (c == '(')
446						cp = skip_comment(cp);
447					else if (c == '"')
448						while ((c = *cp) != '\0') {
449							cp++;
450							if (c == '"')
451								break;
452							if (c == '\\' && *cp != '\0')
453								cp++;
454						}
455				}
456				lastsp = 0;
457				break;
458			}
459			/* FALLTHROUGH */
460
461		default:
462			if (lastsp) {
463				lastsp = 0;
464				*cp2++ = ' ';
465			}
466			*cp2++ = c;
467			if (c == ',' && !gotlt &&
468			    (*cp == ' ' || *cp == '"' || *cp == '<')) {
469				*cp2++ = ' ';
470				while (*cp == ' ')
471					cp++;
472				lastsp = 0;
473				bufend = cp2;
474			}
475		}
476	}
477	*cp2 = '\0';
478
479	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
480		nbuf = cp;
481	return (nbuf);
482}
483
484/*
485 * Fetch the sender's name from the passed message.
486 * Reptype can be
487 *	0 -- get sender's name for display purposes
488 *	1 -- get sender's name for reply
489 *	2 -- get sender's name for Reply
490 */
491char *
492name1(struct message *mp, int reptype)
493{
494	char namebuf[LINESIZE];
495	char linebuf[LINESIZE];
496	char *cp, *cp2;
497	FILE *ibuf;
498	int first = 1;
499
500	if ((cp = hfield("from", mp)) != NULL)
501		return (cp);
502	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
503		return (cp);
504	ibuf = setinput(mp);
505	namebuf[0] = '\0';
506	if (readline(ibuf, linebuf, LINESIZE) < 0)
507		return (savestr(namebuf));
508newname:
509	for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
510		;
511	for (; *cp == ' ' || *cp == '\t'; cp++)
512		;
513	for (cp2 = &namebuf[strlen(namebuf)];
514	    *cp != '\0' && *cp != ' ' && *cp != '\t' &&
515	    cp2 < namebuf + LINESIZE - 1;)
516		*cp2++ = *cp++;
517	*cp2 = '\0';
518	if (readline(ibuf, linebuf, LINESIZE) < 0)
519		return (savestr(namebuf));
520	if ((cp = strchr(linebuf, 'F')) == NULL)
521		return (savestr(namebuf));
522	if (strncmp(cp, "From", 4) != 0)
523		return (savestr(namebuf));
524	while ((cp = strchr(cp, 'r')) != NULL) {
525		if (strncmp(cp, "remote", 6) == 0) {
526			if ((cp = strchr(cp, 'f')) == NULL)
527				break;
528			if (strncmp(cp, "from", 4) != 0)
529				break;
530			if ((cp = strchr(cp, ' ')) == NULL)
531				break;
532			cp++;
533			if (first) {
534				cp2 = namebuf;
535				first = 0;
536			} else
537				cp2 = strrchr(namebuf, '!') + 1;
538			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
539			strcat(namebuf, "!");
540			goto newname;
541		}
542		cp++;
543	}
544	return (savestr(namebuf));
545}
546
547/*
548 * Count the occurrences of c in str
549 */
550int
551charcount(char *str, int c)
552{
553	char *cp;
554	int i;
555
556	for (i = 0, cp = str; *cp != '\0'; cp++)
557		if (*cp == c)
558			i++;
559	return (i);
560}
561
562/*
563 * See if the given header field is supposed to be ignored.
564 */
565int
566isign(const char *field, struct ignoretab ignore[2])
567{
568	char realfld[LINESIZE];
569
570	if (ignore == ignoreall)
571		return (1);
572	/*
573	 * Lower-case the string, so that "Status" and "status"
574	 * will hash to the same place.
575	 */
576	istrncpy(realfld, field, sizeof(realfld));
577	if (ignore[1].i_count > 0)
578		return (!member(realfld, ignore + 1));
579	else
580		return (member(realfld, ignore));
581}
582
583int
584member(char *realfield, struct ignoretab *table)
585{
586	struct ignore *igp;
587
588	for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
589		if (*igp->i_field == *realfield &&
590		    equal(igp->i_field, realfield))
591			return (1);
592	return (0);
593}
594