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