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