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