lex.c revision 216370
138032Speter/*
238032Speter * Copyright (c) 1980, 1993
338032Speter *	The Regents of the University of California.  All rights reserved.
438032Speter *
538032Speter * Redistribution and use in source and binary forms, with or without
690792Sgshapiro * modification, are permitted provided that the following conditions
738032Speter * are met:
864562Sgshapiro * 1. Redistributions of source code must retain the above copyright
938032Speter *    notice, this list of conditions and the following disclaimer.
1038032Speter * 2. Redistributions in binary form must reproduce the above copyright
1138032Speter *    notice, this list of conditions and the following disclaimer in the
1238032Speter *    documentation and/or other materials provided with the distribution.
1338032Speter * 4. Neither the name of the University nor the names of its contributors
1438032Speter *    may be used to endorse or promote products derived from this software
1566494Sgshapiro *    without specific prior written permission.
1638032Speter *
1766494Sgshapiro * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
1866494Sgshapiro * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1966494Sgshapiro * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2066494Sgshapiro * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2166494Sgshapiro * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2266494Sgshapiro * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2366494Sgshapiro * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2466494Sgshapiro * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2566494Sgshapiro * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2666494Sgshapiro * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2766494Sgshapiro * SUCH DAMAGE.
2866494Sgshapiro */
2938032Speter
3038032Speter#ifndef lint
3138032Speter#if 0
3238032Speterstatic char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 4/20/95";
3338032Speter#endif
3438032Speter#endif /* not lint */
3538032Speter#include <sys/cdefs.h>
3638032Speter__FBSDID("$FreeBSD: head/usr.bin/mail/lex.c 216370 2010-12-11 08:32:16Z joel $");
3790792Sgshapiro
3880785Sgshapiro#include "rcv.h"
3980785Sgshapiro#include <errno.h>
4080785Sgshapiro#include <fcntl.h>
4180785Sgshapiro#include "extern.h"
4280785Sgshapiro
4380785Sgshapiro/*
4494334Sgshapiro * Mail -- a mail program
4594334Sgshapiro *
4694334Sgshapiro * Lexical processing of commands.
4794334Sgshapiro */
4894334Sgshapiro
4994334Sgshapirostatic const char	*prompt = "& ";
5094334Sgshapiro
5194334Sgshapiroextern const struct cmd cmdtab[];
5294334Sgshapiroextern const char *version;
5394334Sgshapiro
5494334Sgshapiro/*
5594334Sgshapiro * Set up editing on the given file name.
5694334Sgshapiro * If the first character of name is %, we are considered to be
5794334Sgshapiro * editing the file, otherwise we are reading our mail which has
5894334Sgshapiro * signficance for mbox and so forth.
5994334Sgshapiro *
6094334Sgshapiro * If the -e option is being passed to mail, this function has a
6194334Sgshapiro * tri-state return code: -1 on error, 0 on no mail, 1 if there is
6294334Sgshapiro * mail.
6338032Speter */
6438032Speterint
6538032Spetersetfile(name)
6638032Speter	char *name;
6738032Speter{
6890792Sgshapiro	FILE *ibuf;
6938032Speter	int checkmode, i, fd;
7090792Sgshapiro	struct stat stb;
7190792Sgshapiro	char isedit = *name != '%' || getuserid(myname) != getuid();
7290792Sgshapiro	char *who = name[1] ? name + 1 : myname;
7390792Sgshapiro	char tempname[PATHSIZE];
7438032Speter	static int shudclob;
7538032Speter
7638032Speter	checkmode = value("checkmode") != NULL;
7738032Speter	if ((name = expand(name)) == NULL)
7838032Speter		return (-1);
7938032Speter
8038032Speter	if ((ibuf = Fopen(name, "r")) == NULL) {
8138032Speter		if (!isedit && errno == ENOENT)
8238032Speter			goto nomail;
8338032Speter		warn("%s", name);
8438032Speter		return (-1);
8538032Speter	}
8638032Speter
8738032Speter	if (fstat(fileno(ibuf), &stb) < 0) {
8838032Speter		warn("fstat");
8938032Speter		(void)Fclose(ibuf);
9038032Speter		return (-1);
9138032Speter	}
9238032Speter
9338032Speter	if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) {
9438032Speter		(void)Fclose(ibuf);
9538032Speter		errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
9638032Speter		warn("%s", name);
9738032Speter		return (-1);
9838032Speter	}
9938032Speter
10042575Speter	/*
10142575Speter	 * Looks like all will be well.  We must now relinquish our
10238032Speter	 * hold on the current set of stuff.  Must hold signals
10338032Speter	 * while we are reading the new file, else we will ruin
10438032Speter	 * the message[] data structure.
10538032Speter	 */
10638032Speter
10738032Speter	holdsigs();
10838032Speter	if (shudclob)
10938032Speter		quit();
11038032Speter
11138032Speter	/*
11238032Speter	 * Copy the messages into /tmp
11338032Speter	 * and set pointers.
11438032Speter	 */
11538032Speter
11638032Speter	readonly = 0;
11738032Speter	if ((i = open(name, 1)) < 0)
11838032Speter		readonly++;
11938032Speter	else
12038032Speter		(void)close(i);
12138032Speter	if (shudclob) {
12238032Speter		(void)fclose(itf);
12338032Speter		(void)fclose(otf);
12438032Speter	}
12538032Speter	shudclob = 1;
12638032Speter	edit = isedit;
12738032Speter	strlcpy(prevfile, mailname, sizeof(prevfile));
12838032Speter	if (name != mailname)
12938032Speter		strlcpy(mailname, name, sizeof(mailname));
13038032Speter	mailsize = fsize(ibuf);
13138032Speter	(void)snprintf(tempname, sizeof(tempname),
13238032Speter	    "%s/mail.RxXXXXXXXXXX", tmpdir);
13338032Speter	if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL)
13438032Speter		err(1, "%s", tempname);
13538032Speter	(void)fcntl(fileno(otf), F_SETFD, 1);
13638032Speter	if ((itf = fopen(tempname, "r")) == NULL)
13738032Speter		err(1, "%s", tempname);
13838032Speter	(void)fcntl(fileno(itf), F_SETFD, 1);
13938032Speter	(void)rm(tempname);
14038032Speter	setptr(ibuf, 0);
14190792Sgshapiro	setmsize(msgCount);
14290792Sgshapiro	/*
14390792Sgshapiro	 * New mail may have arrived while we were reading
14490792Sgshapiro	 * the mail file, so reset mailsize to be where
14590792Sgshapiro	 * we really are in the file...
14690792Sgshapiro	 */
14790792Sgshapiro	mailsize = ftello(ibuf);
14890792Sgshapiro	(void)Fclose(ibuf);
14938032Speter	relsesigs();
15038032Speter	sawcom = 0;
15138032Speter
15238032Speter	if ((checkmode || !edit) && msgCount == 0) {
15338032Speternomail:
15438032Speter		if (!checkmode) {
15538032Speter			fprintf(stderr, "No mail for %s\n", who);
15638032Speter			return (-1);
15738032Speter		} else
15838032Speter			return (0);
15938032Speter	}
16038032Speter	return (checkmode ? 1 : 0);
16138032Speter}
16238032Speter
16338032Speter/*
16438032Speter * Incorporate any new mail that has arrived since we first
16538032Speter * started reading mail.
16638032Speter */
16738032Speterint
16838032Speterincfile()
16938032Speter{
17038032Speter	off_t newsize;
17138032Speter	int omsgCount = msgCount;
17238032Speter	FILE *ibuf;
17338032Speter
17438032Speter	ibuf = Fopen(mailname, "r");
17538032Speter	if (ibuf == NULL)
17638032Speter		return (-1);
17764562Sgshapiro	holdsigs();
17890792Sgshapiro	newsize = fsize(ibuf);
17964562Sgshapiro	if (newsize == 0)
18064562Sgshapiro		return (-1);		/* mail box is now empty??? */
18138032Speter	if (newsize < mailsize)
18238032Speter		return (-1);		/* mail box has shrunk??? */
18338032Speter	if (newsize == mailsize)
18438032Speter		return (0);		/* no new mail */
18542575Speter	setptr(ibuf, mailsize);
18638032Speter	setmsize(msgCount);
18742575Speter	mailsize = ftello(ibuf);
18842575Speter	(void)Fclose(ibuf);
18942575Speter	relsesigs();
19042575Speter	return (msgCount - omsgCount);
19142575Speter}
19243730Speter
19342575Speterstatic int	*msgvec;
19443730Speterstatic int	reset_on_stop;		/* do a reset() if stopped */
19543730Speter
19643730Speter/*
19743730Speter * Interpret user commands one by one.  If standard input is not a tty,
19843730Speter * print no prompt.
19943730Speter */
20064562Sgshapirovoid
20143730Spetercommands()
20243730Speter{
20343730Speter	int n, eofloop = 0;
20443730Speter	char linebuf[LINESIZE];
20543730Speter
20643730Speter	if (!sourcing) {
20743730Speter		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
20843730Speter			(void)signal(SIGINT, intr);
20943730Speter		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
21043730Speter			(void)signal(SIGHUP, hangup);
21143730Speter		(void)signal(SIGTSTP, stop);
21243730Speter		(void)signal(SIGTTOU, stop);
21343730Speter		(void)signal(SIGTTIN, stop);
21443730Speter	}
21543730Speter	setexit();
21664562Sgshapiro	for (;;) {
21764562Sgshapiro		/*
21864562Sgshapiro		 * Print the prompt, if needed.  Clear out
21943730Speter		 * string space, and flush the output.
22043730Speter		 */
22143730Speter		if (!sourcing && value("interactive") != NULL) {
22243730Speter			if ((value("autoinc") != NULL) && (incfile() > 0))
22343730Speter				printf("New mail has arrived.\n");
22443730Speter			reset_on_stop = 1;
22543730Speter			printf("%s", prompt);
22643730Speter		}
22743730Speter		(void)fflush(stdout);
22890792Sgshapiro		sreset();
22964562Sgshapiro		/*
23064562Sgshapiro		 * Read a line of commands from the current input
23190792Sgshapiro		 * and handle end of file specially.
23264562Sgshapiro		 */
23364562Sgshapiro		n = 0;
23464562Sgshapiro		for (;;) {
23564562Sgshapiro			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
23664562Sgshapiro				if (n == 0)
23764562Sgshapiro					n = -1;
238110560Sgshapiro				break;
239110560Sgshapiro			}
240110560Sgshapiro			if ((n = strlen(linebuf)) == 0)
241110560Sgshapiro				break;
242110560Sgshapiro			n--;
243110560Sgshapiro			if (linebuf[n] != '\\')
244110560Sgshapiro				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