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