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