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