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