xref: /netbsd/usr.bin/mail/lex.c (revision c274a985)
1 /*	$NetBSD: lex.c,v 1.45 2018/02/04 09:01:12 mrg 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.45 2018/02/04 09:01:12 mrg Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include <assert.h>
42 #include <util.h>
43 
44 #include "rcv.h"
45 #include "extern.h"
46 #ifdef USE_EDITLINE
47 #include "complete.h"
48 #endif
49 #include "format.h"
50 #include "sig.h"
51 #include "thread.h"
52 
53 /*
54  * Mail -- a mail program
55  *
56  * Lexical processing of commands.
57  */
58 
59 static const char *prompt = DEFAULT_PROMPT;
60 static int	*msgvec;
61 static int	inithdr;		/* Am printing startup headers. */
62 static jmp_buf	jmpbuf;			/* The reset jmpbuf */
63 static int	reset_on_stop;		/* To do job control longjmp. */
64 
65 #ifdef DEBUG_FILE_LEAK
66 struct glue {
67 	struct  glue *next;
68 	int     niobs;
69 	FILE    *iobs;
70 };
71 extern struct glue __sglue;
72 
73 static int open_fd_cnt;
74 static int open_fp_cnt;
75 
76 static int
file_count(void)77 file_count(void)
78 {
79 	struct glue *gp;
80 	FILE *fp;
81 	int n;
82 	int cnt;
83 
84 	cnt = 0;
85 	for (gp = &__sglue; gp; gp = gp->next) {
86 		for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++)
87 			if (fp->_flags)
88 				cnt++;
89 	}
90 	return cnt;
91 }
92 
93 static int
fds_count(void)94 fds_count(void)
95 {
96 	int maxfd;
97 	int cnt;
98 	int fd;
99 
100 	maxfd = fcntl(0, F_MAXFD);
101 	if (maxfd == -1) {
102 		warn("fcntl");
103 		return -1;
104 	}
105 
106 	cnt = 0;
107 	for (fd = 0; fd <= maxfd; fd++) {
108 		struct stat sb;
109 
110 		if (fstat(fd, &sb) != -1)
111 			cnt++;
112 		else if (errno != EBADF
113 #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
114 		    && errno != EOPNOTSUPP
115 #endif
116 			)
117 			warn("fstat(%d): errno=%d", fd, errno);
118 	}
119 	return cnt;
120 }
121 
122 static void
file_leak_init(void)123 file_leak_init(void)
124 {
125 	open_fd_cnt = fds_count();
126 	open_fp_cnt = file_count();
127 }
128 
129 static void
file_leak_check(void)130 file_leak_check(void)
131 {
132 	if (open_fp_cnt != file_count() ||
133 	    open_fd_cnt != fds_count()) {
134 		(void)printf("FILE LEAK WARNING: "
135 		    "fp-count: %d (%d)  "
136 		    "fd-count: %d (%d)  max-fd: %d\n",
137 		    file_count(), open_fp_cnt,
138 		    fds_count(), open_fd_cnt,
139 		    fcntl(0, F_MAXFD));
140 	}
141 }
142 #endif /* DEBUG_FILE_LEAK */
143 
144 static void
update_mailname(const char * name)145 update_mailname(const char *name)
146 {
147 	char tbuf[PATHSIZE];
148 	size_t l;
149 
150 	/* Don't realpath(3) if it's only an update request */
151 	if (name != NULL && realpath(name, mailname) == NULL) {
152 		warn("Can't canonicalize `%s'", name);
153 		return;
154 	}
155 
156 	if (getfold(tbuf, sizeof(tbuf)) >= 0) {
157 		l = strlen(tbuf);
158 		if (l < sizeof(tbuf) - 1)
159 			tbuf[l++] = '/';
160 		if (strncmp(tbuf, mailname, l) == 0) {
161 			char const *sep = "", *cp = mailname + l;
162 
163 			l = strlen(cp);
164 			if (l >= sizeof(displayname)) {
165 				cp += l;
166 				cp -= sizeof(displayname) - 5;
167 				sep = "...";
168 			}
169 			(void)snprintf(displayname, sizeof(displayname),
170 			    "+%s%s", sep, cp);
171 			return;
172 		}
173 	}
174 
175 	l = strlen(mailname);
176 	if (l < sizeof(displayname))
177 		strcpy(displayname, mailname);
178 	else {
179 		l -= sizeof(displayname) - 4 - sizeof(displayname) / 3;
180 		(void)snprintf(displayname, sizeof(displayname), "%.*s...%s",
181 			(int)sizeof(displayname) / 3, mailname, mailname + l);
182 	}
183 }
184 
185 /*
186  * Set the size of the message vector used to construct argument
187  * lists to message list functions.
188  */
189 static void
setmsize(int sz)190 setmsize(int sz)
191 {
192 	if (msgvec != 0)
193 		free(msgvec);
194 	msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
195 }
196 
197 /*
198  * Set up editing on the given file name.
199  * If the first character of name is %, we are considered to be
200  * editing the file, otherwise we are reading our mail which has
201  * signficance for mbox and so forth.
202  */
203 PUBLIC int
setfile(const char * name)204 setfile(const char *name)
205 {
206 	FILE *ibuf;
207 	int i, fd;
208 	struct stat stb;
209 	char isedit = *name != '%' || getuserid(myname) != (int)getuid();
210 	const char *who = name[1] ? name + 1 : myname;
211 	static int shudclob;
212 	char tempname[PATHSIZE];
213 
214 	if ((name = expand(name)) == NULL)
215 		return -1;
216 
217 	if ((ibuf = Fopen(name, "ref")) == NULL) {
218 		if (!isedit && errno == ENOENT)
219 			goto nomail;
220 		warn("Can't open `%s'", name);
221 		return -1;
222 	}
223 
224 	if (fstat(fileno(ibuf), &stb) < 0) {
225 		warn("fstat");
226 		(void)Fclose(ibuf);
227 		return -1;
228 	}
229 
230 	switch (stb.st_mode & S_IFMT) {
231 	case S_IFDIR:
232 		(void)Fclose(ibuf);
233 		errno = EISDIR;
234 		warn("%s", name);
235 		return -1;
236 
237 	case S_IFREG:
238 		break;
239 
240 	default:
241 		(void)Fclose(ibuf);
242 		errno = EINVAL;
243 		warn("%s", name);
244 		return -1;
245 	}
246 
247 	/*
248 	 * Looks like all will be well.  We must now relinquish our
249 	 * hold on the current set of stuff.  Must hold signals
250 	 * while we are reading the new file, else we will ruin
251 	 * the message[] data structure.
252 	 */
253 
254 	sig_check();
255 	sig_hold();
256 	if (shudclob)
257 		quit(jmpbuf);
258 
259 	/*
260 	 * Copy the messages into /tmp
261 	 * and set pointers.
262 	 */
263 
264 	readonly = 0;
265 	if ((i = open(name, O_WRONLY)) < 0)
266 		readonly++;
267 	else
268 		(void)close(i);
269 	if (shudclob) {
270 		(void)fclose(itf);
271 		(void)fclose(otf);
272 	}
273 	shudclob = 1;
274 	edit = isedit;
275 	(void)strcpy(prevfile, mailname);
276 	update_mailname(name != mailname ? name : NULL);
277 	mailsize = fsize(ibuf);
278 	(void)snprintf(tempname, sizeof(tempname),
279 	    "%s/mail.RxXXXXXXXXXX", tmpdir);
280 	if ((fd = mkstemp(tempname)) == -1 ||
281 	    (otf = fdopen(fd, "wef")) == NULL)
282 		err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
283 	if ((itf = fopen(tempname, "ref")) == NULL)
284 		err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
285 	(void)rm(tempname);
286 	setptr(ibuf, (off_t)0);
287 	setmsize(get_abs_msgCount());
288 	/*
289 	 * New mail may have arrived while we were reading
290 	 * the mail file, so reset mailsize to be where
291 	 * we really are in the file...
292 	 */
293 	mailsize = ftell(ibuf);
294 	(void)Fclose(ibuf);
295 	sig_release();
296 	sig_check();
297 	sawcom = 0;
298 	if (!edit && get_abs_msgCount() == 0) {
299 nomail:
300 		(void)fprintf(stderr, "No mail for %s\n", who);
301 		return -1;
302 	}
303 	return 0;
304 }
305 
306 /*
307  * Incorporate any new mail that has arrived since we first
308  * started reading mail.
309  */
310 PUBLIC int
incfile(void)311 incfile(void)
312 {
313 	off_t newsize;
314 	int omsgCount;
315 	FILE *ibuf;
316 	int rval;
317 
318 	omsgCount = get_abs_msgCount();
319 
320 	ibuf = Fopen(mailname, "ref");
321 	if (ibuf == NULL)
322 		return -1;
323 	sig_check();
324 	sig_hold();
325 	newsize = fsize(ibuf);
326 	if (newsize == 0 ||		/* mail box is now empty??? */
327 	    newsize < mailsize) {	/* mail box has shrunk??? */
328 		rval = -1;
329 		goto done;
330 	}
331 	if (newsize == mailsize) {
332 		rval = 0;               /* no new mail */
333 		goto done;
334 	}
335 	setptr(ibuf, mailsize);		/* read in new mail */
336 	setmsize(get_abs_msgCount());	/* get the new message count */
337 	mailsize = ftell(ibuf);
338 	rval = get_abs_msgCount() - omsgCount;
339  done:
340 	(void)Fclose(ibuf);
341 	sig_release();
342 	sig_check();
343 	return rval;
344 }
345 
346 /*
347  * Return a pointer to the comment character, respecting quoting as
348  * done in getrawlist().  The comment character is ignored inside
349  * quotes.
350  */
351 static char *
comment_char(char * line)352 comment_char(char *line)
353 {
354 	char *p;
355 	char quotec;
356 	quotec = '\0';
357 	for (p = line; *p; p++) {
358 		if (quotec != '\0') {
359 			if (*p == quotec)
360 				quotec = '\0';
361 		}
362 		else if (*p == '"' || *p == '\'')
363 			quotec = *p;
364 		else if (*p == COMMENT_CHAR)
365 			return p;
366 	}
367 	return NULL;
368 }
369 
370 /*
371  * Signal handler is hooked by setup_piping().
372  * Respond to a broken pipe signal --
373  * probably caused by quitting more.
374  */
375 static jmp_buf	pipestop;
376 
377 /*ARGSUSED*/
378 __dead static void
lex_brokpipe(int signo)379 lex_brokpipe(int signo)
380 {
381 
382 	longjmp(pipestop, signo);
383 }
384 
385 /*
386  * Check the command line for any requested piping or redirection,
387  * depending on the value of 'c'.  If "enable-pipes" is set, search
388  * the command line (cp) for the first occurrence of the character 'c'
389  * that is not in a quote or (parenthese) group.
390  */
391 PUBLIC char *
shellpr(char * cp)392 shellpr(char *cp)
393 {
394 	int quotec;
395 	int level;
396 
397 	if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
398 		return NULL;
399 
400 	level = 0;
401 	quotec = 0;
402 	for (/*EMPTY*/; *cp != '\0'; cp++) {
403 		if (quotec) {
404 			if (*cp == quotec)
405 				quotec = 0;
406 			if (*cp == '\\' &&
407 			    (cp[1] == quotec || cp[1] == '\\'))
408 				cp++;
409 		}
410 		else {
411 			switch (*cp) {
412 			case '|':
413 			case '>':
414 				if (level == 0)
415 					return cp;
416 				break;
417 			case '(':
418 				level++;
419 				break;
420 			case ')':
421 				level--;
422 				break;
423 			case '"':
424 			case '\'':
425 				quotec = *cp;
426 				break;
427 			default:
428 				break;
429 			}
430 		}
431 	}
432 	return NULL;
433 }
434 
435 static int
do_paging(const char * cmd,int c_pipe)436 do_paging(const char *cmd, int c_pipe)
437 {
438 	char *cp, *p;
439 
440 	if (value(ENAME_PAGER_OFF) != NULL)
441 		return 0;
442 
443 	if (c_pipe & C_PIPE_PAGER)
444 		return 1;
445 
446 	if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL)
447 		return 1;
448 
449 	if ((cp = value(ENAME_PAGE_ALSO)) == NULL)
450 		return 0;
451 
452 	if ((p = strcasestr(cp, cmd)) == NULL)
453 		return 0;
454 
455 	if (p != cp && p[-1] != ',' && !is_WSP(p[-1]))
456 		return 0;
457 
458 	p += strlen(cmd);
459 
460 	return (*p == '\0' || *p == ',' || is_WSP(*p));
461 }
462 
463 /*
464  * Setup any pipe or redirection that the command line indicates.
465  * If none, then setup the pager unless "pager-off" is defined.
466  */
467 static FILE *fp_stop = NULL;
468 static int oldfd1 = -1;
469 static sig_t old_sigpipe;
470 
471 static int
setup_piping(const char * cmd,char * cmdline,int c_pipe)472 setup_piping(const char *cmd, char *cmdline, int c_pipe)
473 {
474 	FILE *fout;
475 	FILE *last_file;
476 	char *cp;
477 
478 	sig_check();
479 
480 	last_file = last_registered_file(0);
481 
482 	fout = NULL;
483 	if ((cp = shellpr(cmdline)) != NULL) {
484 		char c;
485 		c = *cp;
486 		*cp = '\0';
487 		cp++;
488 
489 		if (c == '|') {
490 			if ((fout = Popen(cp, "we")) == NULL) {
491 				warn("Popen: %s", cp);
492 				return -1;
493 			}
494 		}
495 		else {
496 			const char *mode;
497 			assert(c == '>');
498 			mode = *cp == '>' ? "ae" : "we";
499 			if (*cp == '>')
500 				cp++;
501 
502 			cp = skip_WSP(cp);
503 			if ((fout = Fopen(cp, mode)) == NULL) {
504 				warn("Fopen: %s", cp);
505 				return -1;
506 			}
507 		}
508 
509 	}
510 	else if (do_paging(cmd, c_pipe)) {
511 		const char *pager;
512 		pager = value(ENAME_PAGER);
513 		if (pager == NULL || *pager == '\0')
514 			pager = _PATH_MORE;
515 
516 		if ((fout = Popen(pager, "we")) == NULL) {
517 			warn("Popen: %s", pager);
518 			return -1;
519 		}
520 	}
521 
522 	if (fout) {
523 		old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe);
524 		(void)fflush(stdout);
525 		if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
526 			err(EXIT_FAILURE, "dup failed");
527 		if (dup2(fileno(fout), STDOUT_FILENO) == -1)
528 			err(EXIT_FAILURE, "dup2 failed");
529 		fp_stop = last_file;
530 	}
531 	return 0;
532 }
533 
534 /*
535  * This will close any piping started by setup_piping().
536  */
537 static void
close_piping(void)538 close_piping(void)
539 {
540 	sigset_t oset;
541 	struct sigaction osa;
542 
543 	if (oldfd1 != -1) {
544 		(void)fflush(stdout);
545 		if (fileno(stdout) != oldfd1 &&
546 		    dup2(oldfd1, STDOUT_FILENO) == -1)
547 			err(EXIT_FAILURE, "dup2 failed");
548 
549 		(void)sig_ignore(SIGPIPE, &osa, &oset);
550 
551 		close_top_files(fp_stop);
552 		fp_stop = NULL;
553 		(void)close(oldfd1);
554 		oldfd1 = -1;
555 
556 		(void)sig_signal(SIGPIPE, old_sigpipe);
557 		(void)sig_restore(SIGPIPE, &osa, &oset);
558 	}
559 	sig_check();
560 }
561 
562 /*
563  * Determine if as1 is a valid prefix of as2.
564  * Return true if yep.
565  */
566 static int
isprefix(char * as1,const char * as2)567 isprefix(char *as1, const char *as2)
568 {
569 	char *s1;
570 	const char *s2;
571 
572 	s1 = as1;
573 	s2 = as2;
574 	while (*s1++ == *s2)
575 		if (*s2++ == '\0')
576 			return 1;
577 	return *--s1 == '\0';
578 }
579 
580 /*
581  * Find the correct command in the command table corresponding
582  * to the passed command "word"
583  */
584 PUBLIC const struct cmd *
lex(char word[])585 lex(char word[])
586 {
587 	const struct cmd *cp;
588 
589 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
590 		if (isprefix(word, cp->c_name))
591 			return cp;
592 	return NULL;
593 }
594 
595 PUBLIC char *
get_cmdname(char * buf)596 get_cmdname(char *buf)
597 {
598 	char *cp;
599 	char *cmd;
600 	size_t len;
601 
602 	for (cp = buf; *cp; cp++)
603 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
604 			break;
605 	/* XXX - Don't miss the pipe command! */
606 	if (cp == buf && *cp == '|')
607 		cp++;
608 	len = cp - buf + 1;
609 	cmd = salloc(len);
610 	(void)strlcpy(cmd, buf, len);
611 	return cmd;
612 }
613 
614 /*
615  * Execute a single command.
616  * Command functions return 0 for success, 1 for error, and -1
617  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
618  * the interactive command loop.
619  * execute_contxt_e is in extern.h.
620  */
621 PUBLIC int
execute(char linebuf[],enum execute_contxt_e contxt)622 execute(char linebuf[], enum execute_contxt_e contxt)
623 {
624 	char *word;
625 	char *arglist[MAXARGC];
626 	const struct cmd * volatile com = NULL;
627 	char *volatile cp;
628 	int retval;
629 	int c;
630 	volatile int e = 1;
631 
632 	/*
633 	 * Strip the white space away from the beginning
634 	 * of the command, then scan out a word, which
635 	 * consists of anything except digits and white space.
636 	 *
637 	 * Handle ! escapes differently to get the correct
638 	 * lexical conventions.
639 	 */
640 
641 	cp = skip_space(linebuf);
642 	if (*cp == '!') {
643 		if (sourcing) {
644 			(void)printf("Can't \"!\" while sourcing\n");
645 			goto out;
646 		}
647 		(void)shell(cp + 1);
648 		return 0;
649 	}
650 
651 	word = get_cmdname(cp);
652 	cp += strlen(word);
653 
654 	/*
655 	 * Look up the command; if not found, bitch.
656 	 * Normally, a blank command would map to the
657 	 * first command in the table; while sourcing,
658 	 * however, we ignore blank lines to eliminate
659 	 * confusion.
660 	 */
661 
662 	if (sourcing && *word == '\0')
663 		return 0;
664 	com = lex(word);
665 	if (com == NULL) {
666 		(void)printf("Unknown command: \"%s\"\n", word);
667 		goto out;
668 	}
669 
670 	/*
671 	 * See if we should execute the command -- if a conditional
672 	 * we always execute it, otherwise, check the state of cond.
673 	 */
674 
675 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
676 		return 0;
677 
678 	/*
679 	 * Process the arguments to the command, depending
680 	 * on the type he expects.  Default to an error.
681 	 * If we are sourcing an interactive command, it's
682 	 * an error.
683 	 */
684 
685 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
686 		(void)printf("May not execute \"%s\" while sending\n",
687 		    com->c_name);
688 		goto out;
689 	}
690 	if (sourcing && com->c_argtype & I) {
691 		(void)printf("May not execute \"%s\" while sourcing\n",
692 		    com->c_name);
693 		goto out;
694 	}
695 	if (readonly && com->c_argtype & W) {
696 		(void)printf("May not execute \"%s\" -- message file is read only\n",
697 		   com->c_name);
698 		goto out;
699 	}
700 	if (contxt == ec_composing && com->c_argtype & R) {
701 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
702 		goto out;
703 	}
704 
705 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
706 
707 		sig_check();
708 		if (setjmp(pipestop))
709 			goto out;
710 
711 		if (setup_piping(com->c_name, cp, com->c_pipe) == -1)
712 			goto out;
713 	}
714 	switch (com->c_argtype & ARGTYPE_MASK) {
715 	case MSGLIST:
716 		/*
717 		 * A message list defaulting to nearest forward
718 		 * legal message.
719 		 */
720 		if (msgvec == 0) {
721 			(void)printf("Illegal use of \"message list\"\n");
722 			break;
723 		}
724 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
725 			break;
726 		if (c  == 0) {
727 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
728 			msgvec[1] = 0;
729 		}
730 		if (*msgvec == 0) {
731 			(void)printf("No applicable messages\n");
732 			break;
733 		}
734 		e = (*com->c_func)(msgvec);
735 		break;
736 
737 	case NDMLIST:
738 		/*
739 		 * A message list with no defaults, but no error
740 		 * if none exist.
741 		 */
742 		if (msgvec == 0) {
743 			(void)printf("Illegal use of \"message list\"\n");
744 			break;
745 		}
746 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
747 			break;
748 		e = (*com->c_func)(msgvec);
749 		break;
750 
751 	case STRLIST:
752 		/*
753 		 * Just the straight string, with
754 		 * leading blanks removed.
755 		 */
756 		cp = skip_space(cp);
757 		e = (*com->c_func)(cp);
758 		break;
759 
760 	case RAWLIST:
761 		/*
762 		 * A vector of strings, in shell style.
763 		 */
764 		if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0)
765 			break;
766 		if (c < com->c_minargs) {
767 			(void)printf("%s requires at least %d arg(s)\n",
768 				com->c_name, com->c_minargs);
769 			break;
770 		}
771 		if (c > com->c_maxargs) {
772 			(void)printf("%s takes no more than %d arg(s)\n",
773 				com->c_name, com->c_maxargs);
774 			break;
775 		}
776 		e = (*com->c_func)(arglist);
777 		break;
778 
779 	case NOLIST:
780 		/*
781 		 * Just the constant zero, for exiting,
782 		 * eg.
783 		 */
784 		e = (*com->c_func)(0);
785 		break;
786 
787 	default:
788 		errx(EXIT_FAILURE, "Unknown argtype");
789 	}
790 
791 out:
792 	close_piping();
793 
794 	/*
795 	 * Exit the current source file on
796 	 * error.
797 	 */
798 	retval = 0;
799 	if (e) {
800 		if (e < 0)
801 			retval = 1;
802 		else if (loading)
803 			retval = 1;
804 		else if (sourcing)
805 			(void)unstack();
806 	}
807 	else if (com != NULL) {
808 		if (contxt != ec_autoprint && com->c_argtype & P &&
809 		    value(ENAME_AUTOPRINT) != NULL &&
810 		    (dot->m_flag & MDELETED) == 0)
811 			(void)execute(__UNCONST("print ."), ec_autoprint);
812 		if (!sourcing && (com->c_argtype & T) == 0)
813 			sawcom = 1;
814 	}
815 	sig_check();
816 	return retval;
817 }
818 
819 /*
820  * The following gets called on receipt of an interrupt.  This is
821  * to abort printout of a command, mainly.
822  * Dispatching here when commands() is inactive crashes rcv.
823  * Close all open files except 0, 1, 2, and the temporary.
824  * Also, unstack all source files.
825  */
826 __dead static void
lex_intr(int signo)827 lex_intr(int signo)
828 {
829 
830 	noreset = 0;
831 	if (!inithdr)
832 		sawcom++;
833 	inithdr = 0;
834 	while (sourcing)
835 		(void)unstack();
836 
837 	close_piping();
838 	close_all_files();
839 
840 	if (image >= 0) {
841 		(void)close(image);
842 		image = -1;
843 	}
844 	(void)fprintf(stderr, "Interrupt\n");
845 	longjmp(jmpbuf, signo);
846 }
847 
848 /*
849  * Branch here on hangup signal and simulate "exit".
850  */
851 /*ARGSUSED*/
852 __dead static void
lex_hangup(int s __unused)853 lex_hangup(int s __unused)
854 {
855 
856 	/* nothing to do? */
857 	exit(EXIT_FAILURE);
858 }
859 
860 /*
861  * When we wake up after ^Z, reprint the prompt.
862  *
863  * NOTE: EditLine deals with the prompt and job control, so with it
864  * this does nothing, i.e., reset_on_stop == 0.
865  */
866 static void
lex_stop(int signo)867 lex_stop(int signo)
868 {
869 
870 	if (reset_on_stop) {
871 		reset_on_stop = 0;
872 		longjmp(jmpbuf, signo);
873 	}
874 }
875 
876 /*
877  * Interpret user commands one by one.  If standard input is not a tty,
878  * print no prompt.
879  */
880 PUBLIC void
commands(void)881 commands(void)
882 {
883 	int n;
884 	char linebuf[LINESIZE];
885 	int eofloop;
886 
887 #ifdef DEBUG_FILE_LEAK
888 	file_leak_init();
889 #endif
890 
891 	if (!sourcing) {
892 		sig_check();
893 
894 		sig_hold();
895 		(void)sig_signal(SIGINT,  lex_intr);
896 		(void)sig_signal(SIGHUP,  lex_hangup);
897 		(void)sig_signal(SIGTSTP, lex_stop);
898 		(void)sig_signal(SIGTTOU, lex_stop);
899 		(void)sig_signal(SIGTTIN, lex_stop);
900 		sig_release();
901 	}
902 
903 	(void)setjmp(jmpbuf);	/* "reset" location if we got an interrupt */
904 
905 	eofloop = 0;	/* initialize this after a possible longjmp */
906 	for (;;) {
907 		sig_check();
908 		(void)fflush(stdout);
909 		sreset();
910 		/*
911 		 * Print the prompt, if needed.  Clear out
912 		 * string space, and flush the output.
913 		 */
914 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
915 			if ((prompt = value(ENAME_PROMPT)) == NULL)
916 				prompt = DEFAULT_PROMPT;
917 			prompt = smsgprintf(prompt, dot);
918 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
919 				(void)printf("New mail has arrived.\n");
920 
921 #ifndef USE_EDITLINE
922 			reset_on_stop = 1;	/* enable job control longjmp */
923 			(void)printf("%s", prompt);
924 #endif
925 		}
926 #ifdef DEBUG_FILE_LEAK
927 		file_leak_check();
928 #endif
929 		/*
930 		 * Read a line of commands from the current input
931 		 * and handle end of file specially.
932 		 */
933 		n = 0;
934 		for (;;) {
935 			sig_check();
936 #ifdef USE_EDITLINE
937 			if (!sourcing) {
938 				char *line;
939 
940 				line = my_gets(&elm.command, prompt, NULL);
941 				if (line == NULL) {
942 					if (n == 0)
943 						n = -1;
944 					break;
945 				}
946 				(void)strlcpy(linebuf, line, sizeof(linebuf));
947 			}
948 			else {
949 				if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) {
950 					if (n == 0)
951 						n = -1;
952 					break;
953 				}
954 			}
955 #else /* USE_EDITLINE */
956 			if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) {
957 				if (n == 0)
958 					n = -1;
959 				break;
960 			}
961 #endif /* USE_EDITLINE */
962 			if (!sourcing)
963 				setscreensize(); /* so we can resize window */
964 
965 			if (sourcing) {  /* allow comments in source files */
966 				char *ptr;
967 				if ((ptr = comment_char(linebuf)) != NULL)
968 					*ptr = '\0';
969 			}
970 			if ((n = (int)strlen(linebuf)) == 0)
971 				break;
972 			n--;
973 			if (linebuf[n] != '\\')
974 				break;
975 			linebuf[n++] = ' ';
976 		}
977 #ifndef USE_EDITLINE
978 		sig_check();
979 		reset_on_stop = 0;	/* disable job control longjmp */
980 #endif
981 		if (n < 0) {
982 			char *p;
983 
984 			/* eof */
985 			if (loading)
986 				break;
987 			if (sourcing) {
988 				(void)unstack();
989 				continue;
990 			}
991 			if (value(ENAME_INTERACTIVE) != NULL &&
992 			    (p = value(ENAME_IGNOREEOF)) != NULL &&
993 			    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
994 				(void)printf("Use \"quit\" to quit.\n");
995 				continue;
996 			}
997 			break;
998 		}
999 		eofloop = 0;
1000 		if (execute(linebuf, ec_normal))
1001 			break;
1002 	}
1003 }
1004 
1005 /*
1006  * Announce information about the file we are editing.
1007  * Return a likely place to set dot.
1008  */
1009 PUBLIC int
newfileinfo(int omsgCount)1010 newfileinfo(int omsgCount)
1011 {
1012 	struct message *mp;
1013 	int d, n, s, t, u, mdot;
1014 
1015 	/*
1016 	 * Figure out where to set the 'dot'.  Use the first new or
1017 	 * unread message.
1018 	 */
1019 	for (mp = get_abs_message(omsgCount + 1); mp;
1020 	     mp = next_abs_message(mp))
1021 		if (mp->m_flag & MNEW)
1022 			break;
1023 
1024 	if (mp == NULL)
1025 		for (mp = get_abs_message(omsgCount + 1); mp;
1026 		     mp = next_abs_message(mp))
1027 			if ((mp->m_flag & MREAD) == 0)
1028 				break;
1029 	if (mp != NULL)
1030 		mdot = get_msgnum(mp);
1031 	else
1032 		mdot = omsgCount + 1;
1033 #ifdef THREAD_SUPPORT
1034 	/*
1035 	 * See if the message is in the current thread.
1036 	 */
1037 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
1038 		mdot = 0;
1039 #endif
1040 	/*
1041 	 * Scan the message array counting the new, unread, deleted,
1042 	 * and saved messages.
1043 	 */
1044 	d = n = s = t = u = 0;
1045 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
1046 		if (mp->m_flag & MNEW)
1047 			n++;
1048 		if ((mp->m_flag & MREAD) == 0)
1049 			u++;
1050 		if (mp->m_flag & MDELETED)
1051 			d++;
1052 		if (mp->m_flag & MSAVED)
1053 			s++;
1054 		if (mp->m_flag & MTAGGED)
1055 			t++;
1056 	}
1057 	/*
1058 	 * Display the statistics.
1059 	 */
1060 	update_mailname(NULL);
1061 	(void)printf("\"%s\": ", displayname);
1062 	{
1063 		int cnt = get_abs_msgCount();
1064 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
1065 	}
1066 	if (n > 0)
1067 		(void)printf(" %d new", n);
1068 	if (u-n > 0)
1069 		(void)printf(" %d unread", u);
1070 	if (t > 0)
1071 		(void)printf(" %d tagged", t);
1072 	if (d > 0)
1073 		(void)printf(" %d deleted", d);
1074 	if (s > 0)
1075 		(void)printf(" %d saved", s);
1076 	if (readonly)
1077 		(void)printf(" [Read only]");
1078 	(void)printf("\n");
1079 
1080 	return mdot;
1081 }
1082 
1083 /*
1084  * Announce the presence of the current Mail version,
1085  * give the message count, and print a header listing.
1086  */
1087 PUBLIC void
announce(void)1088 announce(void)
1089 {
1090 	int vec[2], mdot;
1091 
1092 	mdot = newfileinfo(0);
1093 	vec[0] = mdot;
1094 	vec[1] = 0;
1095 	if ((dot = get_message(mdot)) == NULL)
1096 		dot = get_abs_message(1); /* make sure we get something! */
1097 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
1098 		inithdr++;
1099 		(void)headers(vec);
1100 		inithdr = 0;
1101 	}
1102 }
1103 
1104 /*
1105  * Print the current version number.
1106  */
1107 
1108 /*ARGSUSED*/
1109 PUBLIC int
pversion(void * v __unused)1110 pversion(void *v __unused)
1111 {
1112 	(void)printf("Version %s\n", version);
1113 	return 0;
1114 }
1115 
1116 /*
1117  * Load a file of user definitions.
1118  */
1119 PUBLIC void
load(const char * name)1120 load(const char *name)
1121 {
1122 	FILE *in, *oldin;
1123 
1124 	if ((in = Fopen(name, "ref")) == NULL)
1125 		return;
1126 	oldin = input;
1127 	input = in;
1128 	loading = 1;
1129 	sourcing = 1;
1130 	commands();
1131 	loading = 0;
1132 	sourcing = 0;
1133 	input = oldin;
1134 	(void)Fclose(in);
1135 }
1136