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