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