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