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 "rcv.h"
33#include <errno.h>
34#include <fcntl.h>
35#include "extern.h"
36
37/*
38 * Mail -- a mail program
39 *
40 * Lexical processing of commands.
41 */
42
43static const char	*prompt = "& ";
44
45extern const struct cmd cmdtab[];
46extern const char *version;
47
48/*
49 * Set up editing on the given file name.
50 * If the first character of name is %, we are considered to be
51 * editing the file, otherwise we are reading our mail which has
52 * signficance for mbox and so forth.
53 *
54 * If the -e option is being passed to mail, this function has a
55 * tri-state return code: -1 on error, 0 on no mail, 1 if there is
56 * mail.
57 */
58int
59setfile(char *name)
60{
61	FILE *ibuf;
62	int checkmode, i, fd;
63	struct stat stb;
64	char isedit = *name != '%' || getuserid(myname) != getuid();
65	char *who = name[1] ? name + 1 : myname;
66	char tempname[PATHSIZE];
67	static int shudclob;
68
69	checkmode = value("checkmode") != NULL;
70	if ((name = expand(name)) == NULL)
71		return (-1);
72
73	if ((ibuf = Fopen(name, "r")) == NULL) {
74		if (!isedit && errno == ENOENT)
75			goto nomail;
76		warn("%s", name);
77		return (-1);
78	}
79
80	if (fstat(fileno(ibuf), &stb) < 0) {
81		warn("fstat");
82		(void)Fclose(ibuf);
83		return (-1);
84	}
85
86	if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) {
87		(void)Fclose(ibuf);
88		errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
89		warn("%s", name);
90		return (-1);
91	}
92
93	/*
94	 * Looks like all will be well.  We must now relinquish our
95	 * hold on the current set of stuff.  Must hold signals
96	 * while we are reading the new file, else we will ruin
97	 * the message[] data structure.
98	 */
99
100	holdsigs();
101	if (shudclob)
102		quit();
103
104	/*
105	 * Copy the messages into /tmp
106	 * and set pointers.
107	 */
108
109	readonly = 0;
110	if ((i = open(name, 1)) < 0)
111		readonly++;
112	else
113		(void)close(i);
114	if (shudclob) {
115		(void)fclose(itf);
116		(void)fclose(otf);
117	}
118	shudclob = 1;
119	edit = isedit;
120	strlcpy(prevfile, mailname, sizeof(prevfile));
121	if (name != mailname)
122		strlcpy(mailname, name, sizeof(mailname));
123	mailsize = fsize(ibuf);
124	(void)snprintf(tempname, sizeof(tempname),
125	    "%s/mail.RxXXXXXXXXXX", tmpdir);
126	if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL)
127		err(1, "%s", tempname);
128	(void)fcntl(fileno(otf), F_SETFD, 1);
129	if ((itf = fopen(tempname, "r")) == NULL)
130		err(1, "%s", tempname);
131	(void)fcntl(fileno(itf), F_SETFD, 1);
132	(void)rm(tempname);
133	setptr(ibuf, 0);
134	setmsize(msgCount);
135	/*
136	 * New mail may have arrived while we were reading
137	 * the mail file, so reset mailsize to be where
138	 * we really are in the file...
139	 */
140	mailsize = ftello(ibuf);
141	(void)Fclose(ibuf);
142	relsesigs();
143	sawcom = 0;
144
145	if ((checkmode || !edit) && msgCount == 0) {
146nomail:
147		if (!checkmode) {
148			fprintf(stderr, "No mail for %s\n", who);
149			return (-1);
150		} else
151			return (0);
152	}
153	return (checkmode ? 1 : 0);
154}
155
156/*
157 * Incorporate any new mail that has arrived since we first
158 * started reading mail.
159 */
160int
161incfile(void)
162{
163	off_t newsize;
164	int omsgCount = msgCount;
165	FILE *ibuf;
166
167	ibuf = Fopen(mailname, "r");
168	if (ibuf == NULL)
169		return (-1);
170	holdsigs();
171	newsize = fsize(ibuf);
172	if (newsize == 0)
173		return (-1);		/* mail box is now empty??? */
174	if (newsize < mailsize)
175		return (-1);		/* mail box has shrunk??? */
176	if (newsize == mailsize)
177		return (0);		/* no new mail */
178	setptr(ibuf, mailsize);
179	setmsize(msgCount);
180	mailsize = ftello(ibuf);
181	(void)Fclose(ibuf);
182	relsesigs();
183	return (msgCount - omsgCount);
184}
185
186static int	*msgvec;
187static int	reset_on_stop;		/* do a reset() if stopped */
188
189/*
190 * Interpret user commands one by one.  If standard input is not a tty,
191 * print no prompt.
192 */
193void
194commands(void)
195{
196	int n, eofloop = 0;
197	char linebuf[LINESIZE];
198
199	if (!sourcing) {
200		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
201			(void)signal(SIGINT, intr);
202		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
203			(void)signal(SIGHUP, hangup);
204		(void)signal(SIGTSTP, stop);
205		(void)signal(SIGTTOU, stop);
206		(void)signal(SIGTTIN, stop);
207	}
208	setexit();
209	for (;;) {
210		/*
211		 * Print the prompt, if needed.  Clear out
212		 * string space, and flush the output.
213		 */
214		if (!sourcing && value("interactive") != NULL) {
215			if ((value("autoinc") != NULL) && (incfile() > 0))
216				printf("New mail has arrived.\n");
217			reset_on_stop = 1;
218			printf("%s", prompt);
219		}
220		(void)fflush(stdout);
221		sreset();
222		/*
223		 * Read a line of commands from the current input
224		 * and handle end of file specially.
225		 */
226		n = 0;
227		for (;;) {
228			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
229				if (n == 0)
230					n = -1;
231				break;
232			}
233			if ((n = strlen(linebuf)) == 0)
234				break;
235			n--;
236			if (linebuf[n] != '\\')
237				break;
238			linebuf[n++] = ' ';
239		}
240		reset_on_stop = 0;
241		if (n < 0) {
242				/* eof */
243			if (loading)
244				break;
245			if (sourcing) {
246				unstack();
247				continue;
248			}
249			if (value("interactive") != NULL &&
250			    value("ignoreeof") != NULL &&
251			    ++eofloop < 25) {
252				printf("Use \"quit\" to quit.\n");
253				continue;
254			}
255			break;
256		}
257		eofloop = 0;
258		if (execute(linebuf, 0))
259			break;
260	}
261}
262
263/*
264 * Execute a single command.
265 * Command functions return 0 for success, 1 for error, and -1
266 * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
267 * the interactive command loop.
268 * Contxt is non-zero if called while composing mail.
269 */
270int
271execute(char linebuf[], int contxt)
272{
273	char word[LINESIZE];
274	char *arglist[MAXARGC];
275	const struct cmd *com;
276	char *cp, *cp2;
277	int c, muvec[2];
278	int e = 1;
279
280	/*
281	 * Strip the white space away from the beginning
282	 * of the command, then scan out a word, which
283	 * consists of anything except digits and white space.
284	 *
285	 * Handle ! escapes differently to get the correct
286	 * lexical conventions.
287	 */
288
289	for (cp = linebuf; isspace((unsigned char)*cp); cp++)
290		;
291	if (*cp == '!') {
292		if (sourcing) {
293			printf("Can't \"!\" while sourcing\n");
294			goto out;
295		}
296		shell(cp+1);
297		return (0);
298	}
299	cp2 = word;
300	while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
301		*cp2++ = *cp++;
302	*cp2 = '\0';
303
304	/*
305	 * Look up the command; if not found, bitch.
306	 * Normally, a blank command would map to the
307	 * first command in the table; while sourcing,
308	 * however, we ignore blank lines to eliminate
309	 * confusion.
310	 */
311
312	if (sourcing && *word == '\0')
313		return (0);
314	com = lex(word);
315	if (com == NULL) {
316		printf("Unknown command: \"%s\"\n", word);
317		goto out;
318	}
319
320	/*
321	 * See if we should execute the command -- if a conditional
322	 * we always execute it, otherwise, check the state of cond.
323	 */
324
325	if ((com->c_argtype & F) == 0)
326		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
327			return (0);
328
329	/*
330	 * Process the arguments to the command, depending
331	 * on the type he expects.  Default to an error.
332	 * If we are sourcing an interactive command, it's
333	 * an error.
334	 */
335
336	if (!rcvmode && (com->c_argtype & M) == 0) {
337		printf("May not execute \"%s\" while sending\n",
338		    com->c_name);
339		goto out;
340	}
341	if (sourcing && com->c_argtype & I) {
342		printf("May not execute \"%s\" while sourcing\n",
343		    com->c_name);
344		goto out;
345	}
346	if (readonly && com->c_argtype & W) {
347		printf("May not execute \"%s\" -- message file is read only\n",
348		   com->c_name);
349		goto out;
350	}
351	if (contxt && com->c_argtype & R) {
352		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
353		goto out;
354	}
355	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
356	case MSGLIST:
357		/*
358		 * A message list defaulting to nearest forward
359		 * legal message.
360		 */
361		if (msgvec == 0) {
362			printf("Illegal use of \"message list\"\n");
363			break;
364		}
365		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
366			break;
367		if (c  == 0) {
368			*msgvec = first(com->c_msgflag, com->c_msgmask);
369			msgvec[1] = 0;
370		}
371		if (*msgvec == 0) {
372			printf("No applicable messages\n");
373			break;
374		}
375		e = (*com->c_func)(msgvec);
376		break;
377
378	case NDMLIST:
379		/*
380		 * A message list with no defaults, but no error
381		 * if none exist.
382		 */
383		if (msgvec == 0) {
384			printf("Illegal use of \"message list\"\n");
385			break;
386		}
387		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
388			break;
389		e = (*com->c_func)(msgvec);
390		break;
391
392	case STRLIST:
393		/*
394		 * Just the straight string, with
395		 * leading blanks removed.
396		 */
397		while (isspace((unsigned char)*cp))
398			cp++;
399		e = (*com->c_func)(cp);
400		break;
401
402	case RAWLIST:
403		/*
404		 * A vector of strings, in shell style.
405		 */
406		if ((c = getrawlist(cp, arglist,
407		    sizeof(arglist) / sizeof(*arglist))) < 0)
408			break;
409		if (c < com->c_minargs) {
410			printf("%s requires at least %d arg(s)\n",
411			    com->c_name, com->c_minargs);
412			break;
413		}
414		if (c > com->c_maxargs) {
415			printf("%s takes no more than %d arg(s)\n",
416			    com->c_name, com->c_maxargs);
417			break;
418		}
419		e = (*com->c_func)(arglist);
420		break;
421
422	case NOLIST:
423		/*
424		 * Just the constant zero, for exiting,
425		 * eg.
426		 */
427		e = (*com->c_func)(0);
428		break;
429
430	default:
431		errx(1, "Unknown argtype");
432	}
433
434out:
435	/*
436	 * Exit the current source file on
437	 * error.
438	 */
439	if (e) {
440		if (e < 0)
441			return (1);
442		if (loading)
443			return (1);
444		if (sourcing)
445			unstack();
446		return (0);
447	}
448	if (com == NULL)
449		return (0);
450	if (value("autoprint") != NULL && com->c_argtype & P)
451		if ((dot->m_flag & MDELETED) == 0) {
452			muvec[0] = dot - &message[0] + 1;
453			muvec[1] = 0;
454			type(muvec);
455		}
456	if (!sourcing && (com->c_argtype & T) == 0)
457		sawcom = 1;
458	return (0);
459}
460
461/*
462 * Set the size of the message vector used to construct argument
463 * lists to message list functions.
464 */
465void
466setmsize(int sz)
467{
468
469	if (msgvec != NULL)
470		(void)free(msgvec);
471	msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec));
472}
473
474/*
475 * Find the correct command in the command table corresponding
476 * to the passed command "word"
477 */
478
479const struct cmd *
480lex(char word[])
481{
482	const struct cmd *cp;
483
484	/*
485	 * ignore trailing chars after `#'
486	 *
487	 * lines with beginning `#' are comments
488	 * spaces before `#' are ignored in execute()
489	 */
490
491	if (*word == '#')
492	    *(word+1) = '\0';
493
494
495	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
496		if (isprefix(word, cp->c_name))
497			return (cp);
498	return (NULL);
499}
500
501/*
502 * Determine if as1 is a valid prefix of as2.
503 * Return true if yep.
504 */
505int
506isprefix(const char *as1, const char *as2)
507{
508	const char *s1, *s2;
509
510	s1 = as1;
511	s2 = as2;
512	while (*s1++ == *s2)
513		if (*s2++ == '\0')
514			return (1);
515	return (*--s1 == '\0');
516}
517
518/*
519 * The following gets called on receipt of an interrupt.  This is
520 * to abort printout of a command, mainly.
521 * Dispatching here when command() is inactive crashes rcv.
522 * Close all open files except 0, 1, 2, and the temporary.
523 * Also, unstack all source files.
524 */
525
526static int	inithdr;		/* am printing startup headers */
527
528void
529intr(int s __unused)
530{
531
532	noreset = 0;
533	if (!inithdr)
534		sawcom++;
535	inithdr = 0;
536	while (sourcing)
537		unstack();
538
539	close_all_files();
540
541	if (image >= 0) {
542		(void)close(image);
543		image = -1;
544	}
545	fprintf(stderr, "Interrupt\n");
546	reset(0);
547}
548
549/*
550 * When we wake up after ^Z, reprint the prompt.
551 */
552void
553stop(int s)
554{
555	sig_t old_action = signal(s, SIG_DFL);
556	sigset_t nset;
557
558	(void)sigemptyset(&nset);
559	(void)sigaddset(&nset, s);
560	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
561	(void)kill(0, s);
562	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
563	(void)signal(s, old_action);
564	if (reset_on_stop) {
565		reset_on_stop = 0;
566		reset(0);
567	}
568}
569
570/*
571 * Branch here on hangup signal and simulate "exit".
572 */
573void
574hangup(int s __unused)
575{
576
577	/* nothing to do? */
578	exit(1);
579}
580
581/*
582 * Announce the presence of the current Mail version,
583 * give the message count, and print a header listing.
584 */
585void
586announce(void)
587{
588	int vec[2], mdot;
589
590	mdot = newfileinfo(0);
591	vec[0] = mdot;
592	vec[1] = 0;
593	dot = &message[mdot - 1];
594	if (msgCount > 0 && value("noheader") == NULL) {
595		inithdr++;
596		headers(vec);
597		inithdr = 0;
598	}
599}
600
601/*
602 * Announce information about the file we are editing.
603 * Return a likely place to set dot.
604 */
605int
606newfileinfo(int omsgCount)
607{
608	struct message *mp;
609	int u, n, mdot, d, s;
610	char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename;
611
612	for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
613		if (mp->m_flag & MNEW)
614			break;
615	if (mp >= &message[msgCount])
616		for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
617			if ((mp->m_flag & MREAD) == 0)
618				break;
619	if (mp < &message[msgCount])
620		mdot = mp - &message[0] + 1;
621	else
622		mdot = omsgCount + 1;
623	s = d = 0;
624	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
625		if (mp->m_flag & MNEW)
626			n++;
627		if ((mp->m_flag & MREAD) == 0)
628			u++;
629		if (mp->m_flag & MDELETED)
630			d++;
631		if (mp->m_flag & MSAVED)
632			s++;
633	}
634	ename = mailname;
635	if (getfold(fname, sizeof(fname) - 1) >= 0) {
636		strcat(fname, "/");
637		if (strncmp(fname, mailname, strlen(fname)) == 0) {
638			(void)snprintf(zname, sizeof(zname), "+%s",
639			    mailname + strlen(fname));
640			ename = zname;
641		}
642	}
643	printf("\"%s\": ", ename);
644	if (msgCount == 1)
645		printf("1 message");
646	else
647		printf("%d messages", msgCount);
648	if (n > 0)
649		printf(" %d new", n);
650	if (u-n > 0)
651		printf(" %d unread", u);
652	if (d > 0)
653		printf(" %d deleted", d);
654	if (s > 0)
655		printf(" %d saved", s);
656	if (readonly)
657		printf(" [Read only]");
658	printf("\n");
659	return (mdot);
660}
661
662/*
663 * Print the current version number.
664 */
665
666int
667pversion(void *arg __unused)
668{
669
670	printf("Version %s\n", version);
671	return (0);
672}
673
674/*
675 * Load a file of user definitions.
676 */
677void
678load(char *name)
679{
680	FILE *in, *oldin;
681
682	if ((in = Fopen(name, "r")) == NULL)
683		return;
684	oldin = input;
685	input = in;
686	loading = 1;
687	sourcing = 1;
688	commands();
689	loading = 0;
690	sourcing = 0;
691	input = oldin;
692	(void)Fclose(in);
693}
694