xref: /openbsd/usr.bin/mail/lex.c (revision 404b540a)
1 /*	$OpenBSD: lex.c,v 1.32 2004/05/10 15:25:51 deraadt Exp $	*/
2 /*	$NetBSD: lex.c,v 1.10 1997/05/17 19:55:13 pk Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 #if 0
35 static const char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 4/20/95";
36 #else
37 static const char rcsid[] = "$OpenBSD: lex.c,v 1.32 2004/05/10 15:25:51 deraadt Exp $";
38 #endif
39 #endif /* not lint */
40 
41 #include "rcv.h"
42 #include <errno.h>
43 #include <fcntl.h>
44 #include "extern.h"
45 
46 /*
47  * Mail -- a mail program
48  *
49  * Lexical processing of commands.
50  */
51 
52 char	*prompt = "& ";
53 
54 const struct cmd *com;	/* command we are running */
55 
56 /*
57  * Set up editing on the given file name.
58  * If the first character of name is %, we are considered to be
59  * editing the file, otherwise we are reading our mail which has
60  * signficance for mbox and so forth.
61  */
62 int
63 setfile(char *name)
64 {
65 	FILE *ibuf;
66 	int i, fd;
67 	struct stat stb;
68 	char isedit = *name != '%';
69 	char *who = name[1] ? name + 1 : myname;
70 	char tempname[PATHSIZE];
71 	static int shudclob;
72 
73 	if ((name = expand(name)) == NULL)
74 		return(-1);
75 
76 	if ((ibuf = Fopen(name, "r")) == NULL) {
77 		if (!isedit && errno == ENOENT)
78 			goto nomail;
79 		warn("%s", name);
80 		return(-1);
81 	}
82 
83 	if (fstat(fileno(ibuf), &stb) < 0) {
84 		warn("fstat");
85 		(void)Fclose(ibuf);
86 		return(-1);
87 	}
88 
89 	switch (stb.st_mode & S_IFMT) {
90 	case S_IFDIR:
91 		(void)Fclose(ibuf);
92 		errno = EISDIR;
93 		warn("%s", name);
94 		return(-1);
95 
96 	case S_IFREG:
97 		break;
98 
99 	default:
100 		(void)Fclose(ibuf);
101 		errno = EINVAL;
102 		warn("%s", name);
103 		return(-1);
104 	}
105 
106 	/*
107 	 * Looks like all will be well.  We must now relinquish our
108 	 * hold on the current set of stuff.  Must hold signals
109 	 * while we are reading the new file, else we will ruin
110 	 * the message[] data structure.
111 	 */
112 	holdsigs();
113 	if (shudclob)
114 		quit();
115 
116 	/*
117 	 * Copy the messages into /tmp
118 	 * and set pointers.
119 	 */
120 	readonly = 0;
121 	if ((i = open(name, O_WRONLY, 0)) < 0)
122 		readonly++;
123 	else
124 		(void)close(i);
125 	if (shudclob) {
126 		(void)fclose(itf);
127 		(void)fclose(otf);
128 	}
129 	shudclob = 1;
130 	edit = isedit;
131 	strlcpy(prevfile, mailname, PATHSIZE);
132 	if (name != mailname)
133 		strlcpy(mailname, name, sizeof(mailname));
134 	mailsize = fsize(ibuf);
135 	(void)snprintf(tempname, sizeof(tempname),
136 	    "%s/mail.RxXXXXXXXXXX", tmpdir);
137 	if ((fd = mkstemp(tempname)) == -1 ||
138 	    (otf = fdopen(fd, "w")) == NULL)
139 		err(1, "%s", tempname);
140 	(void)fcntl(fileno(otf), F_SETFD, 1);
141 	if ((itf = fopen(tempname, "r")) == NULL)
142 		err(1, "%s", tempname);
143 	(void)fcntl(fileno(itf), F_SETFD, 1);
144 	(void)rm(tempname);
145 	setptr(ibuf, (off_t)0);
146 	setmsize(msgCount);
147 	/*
148 	 * New mail may have arrived while we were reading
149 	 * the mail file, so reset mailsize to be where
150 	 * we really are in the file...
151 	 */
152 	mailsize = ftell(ibuf);
153 	(void)Fclose(ibuf);
154 	relsesigs();
155 	sawcom = 0;
156 	if (!edit && msgCount == 0) {
157 nomail:
158 		fprintf(stderr, "No mail for %s\n", who);
159 		return(-1);
160 	}
161 	return(0);
162 }
163 
164 /*
165  * Incorporate any new mail that has arrived since we first
166  * started reading mail.
167  */
168 int
169 incfile(void)
170 {
171 	int newsize;
172 	int omsgCount = msgCount;
173 	FILE *ibuf;
174 
175 	ibuf = Fopen(mailname, "r");
176 	if (ibuf == NULL)
177 		return(-1);
178 	holdsigs();
179 	if (!spool_lock()) {
180 		(void)Fclose(ibuf);
181 		relsesigs();
182 		return(-1);
183 	}
184 	newsize = fsize(ibuf);
185 	/* make sure mail box has grown and is non-empty */
186 	if (newsize == 0 || newsize <= mailsize) {
187 		(void)Fclose(ibuf);
188 		spool_unlock();
189 		relsesigs();
190 		return(newsize == mailsize ? 0 : -1);
191 	}
192 	setptr(ibuf, mailsize);
193 	setmsize(msgCount);
194 	mailsize = ftell(ibuf);
195 	(void)Fclose(ibuf);
196 	spool_unlock();
197 	relsesigs();
198 	return(msgCount - omsgCount);
199 }
200 
201 
202 int	*msgvec;
203 int	reset_on_stop;			/* reset prompt if stopped */
204 
205 /*
206  * Interpret user commands one by one.  If standard input is not a tty,
207  * print no prompt.
208  */
209 void
210 commands(void)
211 {
212 	int n, sig, *sigp;
213 	int eofloop = 0;
214 	char linebuf[LINESIZE];
215 
216 	prompt:
217 	for (;;) {
218 		/*
219 		 * Print the prompt, if needed.  Clear out
220 		 * string space, and flush the output.
221 		 */
222 		if (!sourcing && value("interactive") != NULL) {
223 			if ((value("autoinc") != NULL) && (incfile() > 0))
224 				puts("New mail has arrived.");
225 			reset_on_stop = 1;
226 			printf("%s", prompt);
227 		}
228 		fflush(stdout);
229 		sreset();
230 		/*
231 		 * Read a line of commands from the current input
232 		 * and handle end of file specially.
233 		 */
234 		n = 0;
235 		sig = 0;
236 		sigp = sourcing ? NULL : &sig;
237 		for (;;) {
238 			if (readline(input, &linebuf[n], LINESIZE - n, sigp) < 0) {
239 				if (sig) {
240 					if (sig == SIGINT)
241 						dointr();
242 					else if (sig == SIGHUP)
243 						/* nothing to do? */
244 						exit(1);
245 					else {
246 						/* Stopped by job control */
247 						(void)kill(0, sig);
248 						if (reset_on_stop)
249 							reset_on_stop = 0;
250 					}
251 					goto prompt;
252 				}
253 				if (n == 0)
254 					n = -1;
255 				break;
256 			}
257 			if ((n = strlen(linebuf)) == 0)
258 				break;
259 			n--;
260 			if (linebuf[n] != '\\')
261 				break;
262 			linebuf[n++] = ' ';
263 		}
264 		reset_on_stop = 0;
265 		if (n < 0) {
266 				/* eof */
267 			if (loading)
268 				break;
269 			if (sourcing) {
270 				unstack();
271 				continue;
272 			}
273 			if (value("interactive") != NULL &&
274 			    value("ignoreeof") != NULL &&
275 			    ++eofloop < 25) {
276 				puts("Use \"quit\" to quit.");
277 				continue;
278 			}
279 			break;
280 		}
281 		eofloop = 0;
282 		if (execute(linebuf, 0))
283 			break;
284 	}
285 }
286 
287 /*
288  * Execute a single command.
289  * Command functions return 0 for success, 1 for error, and -1
290  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
291  * the interactive command loop.
292  * Contxt is non-zero if called while composing mail.
293  */
294 int
295 execute(char *linebuf, int contxt)
296 {
297 	char word[LINESIZE];
298 	char *arglist[MAXARGC];
299 	char *cp, *cp2;
300 	int c, muvec[2];
301 	int e = 1;
302 
303 	com = NULL;
304 
305 	/*
306 	 * Strip the white space away from the beginning
307 	 * of the command, then scan out a word, which
308 	 * consists of anything except digits and white space.
309 	 *
310 	 * Handle ! escapes differently to get the correct
311 	 * lexical conventions.
312 	 */
313 	for (cp = linebuf; isspace(*cp); cp++)
314 		;
315 	if (*cp == '!') {
316 		if (sourcing) {
317 			puts("Can't \"!\" while sourcing");
318 			goto out;
319 		}
320 		shell(cp+1);
321 		return(0);
322 	}
323 	cp2 = word;
324 	while (*cp && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
325 		*cp2++ = *cp++;
326 	*cp2 = '\0';
327 
328 	/*
329 	 * Look up the command; if not found, bitch.
330 	 * Normally, a blank command would map to the
331 	 * first command in the table; while sourcing,
332 	 * however, we ignore blank lines to eliminate
333 	 * confusion.
334 	 */
335 	if (sourcing && *word == '\0')
336 		return(0);
337 	com = lex(word);
338 	if (com == NULL) {
339 		printf("Unknown command: \"%s\"\n", word);
340 		goto out;
341 	}
342 
343 	/*
344 	 * See if we should execute the command -- if a conditional
345 	 * we always execute it, otherwise, check the state of cond.
346 	 */
347 	if ((com->c_argtype & F) == 0)
348 		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
349 			return(0);
350 
351 	/*
352 	 * Process the arguments to the command, depending
353 	 * on the type he expects.  Default to an error.
354 	 * If we are sourcing an interactive command, it's
355 	 * an error.
356 	 */
357 	if (!rcvmode && (com->c_argtype & M) == 0) {
358 		printf("May not execute \"%s\" while sending\n",
359 		    com->c_name);
360 		goto out;
361 	}
362 	if (sourcing && com->c_argtype & I) {
363 		printf("May not execute \"%s\" while sourcing\n",
364 		    com->c_name);
365 		goto out;
366 	}
367 	if (readonly && com->c_argtype & W) {
368 		printf("May not execute \"%s\" -- message file is read only\n",
369 		   com->c_name);
370 		goto out;
371 	}
372 	if (contxt && com->c_argtype & R) {
373 		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
374 		goto out;
375 	}
376 	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
377 	case MSGLIST|STRLIST:
378 		/*
379 		 * A message list defaulting to nearest forward
380 		 * legal message.
381 		 */
382 		if (msgvec == 0) {
383 			puts("Illegal use of \"message list\"");
384 			break;
385 		}
386 		/*
387 		 * remove leading blanks.
388 		 */
389 		while (isspace(*cp))
390 			cp++;
391 
392 		if (isdigit(*cp) || *cp == ':') {
393 			if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
394 				break;
395 			/* position to next space - past the message list */
396 			while (!isspace(*cp))
397 				cp++;
398 			/* position to next non-space */
399 			while (isspace(*cp))
400 				cp++;
401 		} else {
402 			c = 0; /* no message list */
403 		}
404 
405 		if (c  == 0) {
406 			*msgvec = first(com->c_msgflag,
407 				com->c_msgmask);
408 			msgvec[1] = NULL;
409 		}
410 		if (*msgvec == 0) {
411 			puts("No applicable messages");
412 			break;
413 		}
414 		/*
415 		 * Just the straight string, with
416 		 * leading blanks removed.
417 		 */
418 		while (isspace(*cp))
419 			cp++;
420 
421 		e = (*com->c_func2)(msgvec, cp);
422 		break;
423 
424 	case MSGLIST:
425 		/*
426 		 * A message list defaulting to nearest forward
427 		 * legal message.
428 		 */
429 		if (msgvec == NULL) {
430 			puts("Illegal use of \"message list\"");
431 			break;
432 		}
433 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
434 			break;
435 		if (c  == 0) {
436 			*msgvec = first(com->c_msgflag,
437 				com->c_msgmask);
438 			msgvec[1] = NULL;
439 		}
440 		if (*msgvec == 0) {
441 			puts("No applicable messages");
442 			break;
443 		}
444 		e = (*com->c_func)(msgvec);
445 		break;
446 
447 	case NDMLIST:
448 		/*
449 		 * A message list with no defaults, but no error
450 		 * if none exist.
451 		 */
452 		if (msgvec == 0) {
453 			puts("Illegal use of \"message list\"");
454 			break;
455 		}
456 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
457 			break;
458 		e = (*com->c_func)(msgvec);
459 		break;
460 
461 	case STRLIST:
462 		/*
463 		 * Just the straight string, with
464 		 * leading blanks removed.
465 		 */
466 		while (isspace(*cp))
467 			cp++;
468 		e = (*com->c_func)(cp);
469 		break;
470 
471 	case RAWLIST:
472 		/*
473 		 * A vector of strings, in shell style.
474 		 */
475 		if ((c = getrawlist(cp, arglist,
476 				sizeof(arglist) / sizeof(*arglist))) < 0)
477 			break;
478 		if (c < com->c_minargs) {
479 			printf("%s requires at least %d arg(s)\n",
480 				com->c_name, com->c_minargs);
481 			break;
482 		}
483 		if (c > com->c_maxargs) {
484 			printf("%s takes no more than %d arg(s)\n",
485 				com->c_name, com->c_maxargs);
486 			break;
487 		}
488 		e = (*com->c_func)(arglist);
489 		break;
490 
491 	case NOLIST:
492 		/*
493 		 * Just the constant zero, for exiting,
494 		 * eg.
495 		 */
496 		e = (*com->c_func)(0);
497 		break;
498 
499 	default:
500 		errx(1, "Unknown argtype");
501 	}
502 
503 out:
504 	/*
505 	 * Exit the current source file on
506 	 * error.
507 	 */
508 	if (e) {
509 		if (e < 0)
510 			return(1);
511 		if (loading)
512 			return(1);
513 		if (sourcing)
514 			unstack();
515 		return(0);
516 	}
517 	if (com == NULL)
518 		return(0);
519 	if (value("autoprint") != NULL && com->c_argtype & P)
520 		if ((dot->m_flag & MDELETED) == 0) {
521 			muvec[0] = dot - &message[0] + 1;
522 			muvec[1] = 0;
523 			type(muvec);
524 		}
525 	if (!sourcing && (com->c_argtype & T) == 0)
526 		sawcom = 1;
527 	return(0);
528 }
529 
530 /*
531  * Set the size of the message vector used to construct argument
532  * lists to message list functions.
533  */
534 void
535 setmsize(int n)
536 {
537 	int *msgvec2;
538 	size_t msize;
539 
540 	msize = (n + 1) * sizeof(*msgvec);
541 	if ((msgvec2 = realloc(msgvec, msize)) == NULL)
542 		errx(1, "Out of memory");
543 	msgvec = msgvec2;
544 	memset(msgvec, 0, msize);
545 }
546 
547 /*
548  * Find the correct command in the command table corresponding
549  * to the passed command "word"
550  */
551 
552 const struct cmd *
553 lex(char *word)
554 {
555 	extern const struct cmd cmdtab[];
556 	const struct cmd *cp;
557 
558 	if (word[0] == '#')
559 		word = "#";
560 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
561 		if (isprefix(word, cp->c_name))
562 			return(cp);
563 	return(NULL);
564 }
565 
566 /*
567  * Determine if as1 is a valid prefix of as2.
568  * Return true if yep.
569  */
570 int
571 isprefix(char *as1, char *as2)
572 {
573 	char *s1, *s2;
574 
575 	s1 = as1;
576 	s2 = as2;
577 	while (*s1++ == *s2)
578 		if (*s2++ == '\0')
579 			return(1);
580 	return(*--s1 == '\0');
581 }
582 
583 /*
584  * The following gets called on receipt of an interrupt.  This is
585  * to abort printout of a command, mainly.
586  * Dispatching here when command() is inactive crashes rcv.
587  * Close all open files except 0, 1, 2, and the temporary.
588  * Also, unstack all source files.
589  */
590 int	inithdr;			/* am printing startup headers */
591 
592 void
593 dointr(void)
594 {
595 
596 	noreset = 0;
597 	if (!inithdr)
598 		sawcom++;
599 	inithdr = 0;
600 	while (sourcing)
601 		unstack();
602 
603 	close_all_files();
604 
605 	if (image >= 0) {
606 		(void)close(image);
607 		image = -1;
608 	}
609 	fputs("Interrupt\n", stderr);
610 }
611 
612 /*
613  * Announce the presence of the current Mail version,
614  * give the message count, and print a header listing.
615  */
616 void
617 announce(void)
618 {
619 	int vec[2], mdot;
620 
621 	mdot = newfileinfo(0);
622 	vec[0] = mdot;
623 	vec[1] = 0;
624 	dot = &message[mdot - 1];
625 	if (msgCount > 0 && value("noheader") == NULL) {
626 		inithdr++;
627 		headers(vec);
628 		inithdr = 0;
629 	}
630 }
631 
632 /*
633  * Announce information about the file we are editing.
634  * Return a likely place to set dot.
635  */
636 int
637 newfileinfo(int omsgCount)
638 {
639 	struct message *mp;
640 	int u, n, mdot, d, s;
641 	char fname[PATHSIZE], zname[PATHSIZE], *ename;
642 
643 	for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
644 		if (mp->m_flag & MNEW)
645 			break;
646 	if (mp >= &message[msgCount])
647 		for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
648 			if ((mp->m_flag & MREAD) == 0)
649 				break;
650 	if (mp < &message[msgCount])
651 		mdot = mp - &message[0] + 1;
652 	else
653 		mdot = omsgCount + 1;
654 	s = d = 0;
655 	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
656 		if (mp->m_flag & MNEW)
657 			n++;
658 		if ((mp->m_flag & MREAD) == 0)
659 			u++;
660 		if (mp->m_flag & MDELETED)
661 			d++;
662 		if (mp->m_flag & MSAVED)
663 			s++;
664 	}
665 	ename = mailname;
666 	if (getfold(fname, sizeof(fname)) >= 0) {
667 		strlcat(fname, "/", sizeof(fname));
668 		if (strncmp(fname, mailname, strlen(fname)) == 0) {
669 			(void)snprintf(zname, sizeof(zname), "+%s",
670 			    mailname + strlen(fname));
671 			ename = zname;
672 		}
673 	}
674 	printf("\"%s\": ", ename);
675 	if (msgCount == 1)
676 		fputs("1 message", stdout);
677 	else
678 		printf("%d messages", msgCount);
679 	if (n > 0)
680 		printf(" %d new", n);
681 	if (u-n > 0)
682 		printf(" %d unread", u);
683 	if (d > 0)
684 		printf(" %d deleted", d);
685 	if (s > 0)
686 		printf(" %d saved", s);
687 	if (readonly)
688 		fputs(" [Read only]", stdout);
689 	putchar('\n');
690 	return(mdot);
691 }
692 
693 /*
694  * Print the current version number.
695  */
696 /*ARGSUSED*/
697 int
698 pversion(void *v)
699 {
700 	extern const char version[];
701 
702 	printf("Version %s\n", version);
703 	return(0);
704 }
705 
706 /*
707  * Load a file of user definitions.
708  */
709 void
710 load(char *name)
711 {
712 	FILE *in, *oldin;
713 
714 	if ((in = Fopen(name, "r")) == NULL)
715 		return;
716 	oldin = input;
717 	input = in;
718 	loading = 1;
719 	sourcing = 1;
720 	commands();
721 	loading = 0;
722 	sourcing = 0;
723 	input = oldin;
724 	(void)Fclose(in);
725 }
726