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