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