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