Correct BSD License clause numbering from 1-2-4 to 1-2-3.
[dragonfly.git] / usr.bin / mail / cmd3.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  * @(#)cmd3.c   8.2 (Berkeley) 4/20/95
30  * $FreeBSD: src/usr.bin/mail/cmd3.c,v 1.4.6.4 2003/01/06 05:46:03 mikeh Exp $
31  */
32
33 #include "rcv.h"
34 #include "extern.h"
35
36 /*
37  * Mail -- a mail program
38  *
39  * Still more user commands.
40  */
41
42 /*
43  * Process a shell escape by saving signals, ignoring signals,
44  * and forking a sh -c
45  */
46 int
47 shell(char *str)
48 {
49         sig_t sigint = signal(SIGINT, SIG_IGN);
50         char *sh;
51         char cmd[BUFSIZ];
52
53         if (strlcpy(cmd, str, sizeof(cmd)) >= sizeof(cmd))
54                 return (1);
55         if (bangexp(cmd, sizeof(cmd)) < 0)
56                 return (1);
57         if ((sh = value("SHELL")) == NULL)
58                 sh = _PATH_CSHELL;
59         run_command(sh, 0, -1, -1, "-c", cmd, NULL);
60         signal(SIGINT, sigint);
61         printf("!\n");
62         return (0);
63 }
64
65 /*
66  * Fork an interactive shell.
67  */
68 /*ARGSUSED*/
69 int
70 dosh(char *str)
71 {
72         sig_t sigint = signal(SIGINT, SIG_IGN);
73         char *sh;
74
75         if ((sh = value("SHELL")) == NULL)
76                 sh = _PATH_CSHELL;
77         run_command(sh, 0, -1, -1, NULL, NULL, NULL);
78         signal(SIGINT, sigint);
79         printf("\n");
80         return (0);
81 }
82
83 /*
84  * Expand the shell escape by expanding unescaped !'s into the
85  * last issued command where possible.
86  */
87 int
88 bangexp(char *str, size_t strsize)
89 {
90         char bangbuf[BUFSIZ];
91         static char lastbang[BUFSIZ];
92         char *cp, *cp2;
93         int n, changed = 0;
94
95         cp = str;
96         cp2 = bangbuf;
97         n = sizeof(bangbuf);
98         while (*cp != '\0') {
99                 if (*cp == '!') {
100                         if (n < strlen(lastbang)) {
101 overf:
102                                 printf("Command buffer overflow\n");
103                                 return (-1);
104                         }
105                         changed++;
106                         if (strlcpy(cp2, lastbang, sizeof(bangbuf) - (cp2 - bangbuf))
107                             >= sizeof(bangbuf) - (cp2 - bangbuf))
108                                 goto overf;
109                         cp2 += strlen(lastbang);
110                         n -= strlen(lastbang);
111                         cp++;
112                         continue;
113                 }
114                 if (*cp == '\\' && cp[1] == '!') {
115                         if (--n <= 1)
116                                 goto overf;
117                         *cp2++ = '!';
118                         cp += 2;
119                         changed++;
120                 }
121                 if (--n <= 1)
122                         goto overf;
123                 *cp2++ = *cp++;
124         }
125         *cp2 = 0;
126         if (changed) {
127                 printf("!%s\n", bangbuf);
128                 fflush(stdout);
129         }
130         if (strlcpy(str, bangbuf, strsize) >= strsize)
131                 goto overf;
132         if (strlcpy(lastbang, bangbuf, sizeof(lastbang)) >= sizeof(lastbang))
133                 goto overf;
134         return (0);
135 }
136
137 /*
138  * Print out a nice help message from some file or another.
139  */
140
141 int
142 help(void)
143 {
144         int c;
145         FILE *f;
146
147         if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
148                 warn("%s", _PATH_HELP);
149                 return (1);
150         }
151         while ((c = getc(f)) != EOF)
152                 putchar(c);
153         Fclose(f);
154         return (0);
155 }
156
157 /*
158  * Change user's working directory.
159  */
160 int
161 schdir(char **arglist)
162 {
163         char *cp;
164
165         if (*arglist == NULL) {
166                 if (homedir == NULL)
167                         return (1);
168                 cp = homedir;
169         } else
170                 if ((cp = expand(*arglist)) == NULL)
171                         return (1);
172         if (chdir(cp) < 0) {
173                 warn("%s", cp);
174                 return (1);
175         }
176         return (0);
177 }
178
179 int
180 respond(int *msgvec)
181 {
182         if (value("Replyall") == NULL && value("flipr") == NULL)
183                 return (dorespond(msgvec));
184         else
185                 return (doRespond(msgvec));
186 }
187
188 /*
189  * Reply to a list of messages.  Extract each name from the
190  * message header and send them off to mail1()
191  */
192 int
193 dorespond(int *msgvec)
194 {
195         struct message *mp;
196         char *cp, *rcv, *replyto;
197         char **ap;
198         struct name *np;
199         struct header head;
200
201         if (msgvec[1] != 0) {
202                 printf("Sorry, can't reply to multiple messages at once\n");
203                 return (1);
204         }
205         mp = &message[msgvec[0] - 1];
206         touch(mp);
207         dot = mp;
208         if ((rcv = skin(hfield("from", mp))) == NULL)
209                 rcv = skin(nameof(mp, 1));
210         if ((replyto = skin(hfield("reply-to", mp))) != NULL)
211                 np = extract(replyto, GTO);
212         else if ((cp = skin(hfield("to", mp))) != NULL)
213                 np = extract(cp, GTO);
214         else
215                 np = NULL;
216         np = elide(np);
217         /*
218          * Delete my name from the reply list,
219          * and with it, all my alternate names.
220          */
221         np = delname(np, myname);
222         if (altnames)
223                 for (ap = altnames; *ap != NULL; ap++)
224                         np = delname(np, *ap);
225         if (np != NULL && replyto == NULL)
226                 np = cat(np, extract(rcv, GTO));
227         else if (np == NULL) {
228                 if (replyto != NULL)
229                         printf("Empty reply-to field -- replying to author\n");
230                 np = extract(rcv, GTO);
231         }
232         head.h_to = np;
233         if ((head.h_subject = hfield("subject", mp)) == NULL)
234                 head.h_subject = hfield("subj", mp);
235         head.h_subject = reedit(head.h_subject);
236         if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
237                 np = elide(extract(cp, GCC));
238                 np = delname(np, myname);
239                 if (altnames != 0)
240                         for (ap = altnames; *ap != NULL; ap++)
241                                 np = delname(np, *ap);
242                 head.h_cc = np;
243         } else
244                 head.h_cc = NULL;
245         head.h_bcc = NULL;
246         head.h_smopts = NULL;
247         head.h_replyto = value("REPLYTO");
248         head.h_inreplyto = skin(hfield("message-id", mp));
249         mail1(&head, 1);
250         return (0);
251 }
252
253 /*
254  * Modify the subject we are replying to to begin with Re: if
255  * it does not already.
256  */
257 char *
258 reedit(char *subj)
259 {
260         char *newsubj;
261
262         if (subj == NULL)
263                 return (NULL);
264         if ((subj[0] == 'r' || subj[0] == 'R') &&
265             (subj[1] == 'e' || subj[1] == 'E') &&
266             subj[2] == ':')
267                 return (subj);
268         newsubj = salloc(strlen(subj) + 5);
269         sprintf(newsubj, "Re: %s", subj);
270         return (newsubj);
271 }
272
273 /*
274  * Preserve the named messages, so that they will be sent
275  * back to the system mailbox.
276  */
277 int
278 preserve(int *msgvec)
279 {
280         int *ip, mesg;
281         struct message *mp;
282
283         if (edit) {
284                 printf("Cannot \"preserve\" in edit mode\n");
285                 return (1);
286         }
287         for (ip = msgvec; *ip != 0; ip++) {
288                 mesg = *ip;
289                 mp = &message[mesg-1];
290                 mp->m_flag |= MPRESERVE;
291                 mp->m_flag &= ~MBOX;
292                 dot = mp;
293         }
294         return (0);
295 }
296
297 /*
298  * Mark all given messages as unread.
299  */
300 int
301 unread(int *msgvec)
302 {
303         int *ip;
304
305         for (ip = msgvec; *ip != 0; ip++) {
306                 dot = &message[*ip-1];
307                 dot->m_flag &= ~(MREAD|MTOUCH);
308                 dot->m_flag |= MSTATUS;
309         }
310         return (0);
311 }
312
313 /*
314  * Print the size of each message.
315  */
316 int
317 messize(int *msgvec)
318 {
319         struct message *mp;
320         int *ip, mesg;
321
322         for (ip = msgvec; *ip != 0; ip++) {
323                 mesg = *ip;
324                 mp = &message[mesg-1];
325                 printf("%d: %ld/%ld\n", mesg, mp->m_lines, mp->m_size);
326         }
327         return (0);
328 }
329
330 /*
331  * Quit quickly.  If we are sourcing, just pop the input level
332  * by returning an error.
333  */
334 int
335 rexit(int e)
336 {
337         if (sourcing)
338                 return (1);
339         exit(0);
340         /*NOTREACHED*/
341 }
342
343 /*
344  * Set or display a variable value.  Syntax is similar to that
345  * of csh.
346  */
347 int
348 set(char **arglist)
349 {
350         struct var *vp;
351         char *cp, *cp2;
352         char varbuf[BUFSIZ], **ap, **p;
353         int errs, h, s;
354
355         if (*arglist == NULL) {
356                 for (h = 0, s = 1; h < HSHSIZE; h++)
357                         for (vp = variables[h]; vp != NULL; vp = vp->v_link)
358                                 s++;
359                 ap = (char **)salloc(s * sizeof(*ap));
360                 for (h = 0, p = ap; h < HSHSIZE; h++)
361                         for (vp = variables[h]; vp != NULL; vp = vp->v_link)
362                                 *p++ = vp->v_name;
363                 *p = NULL;
364                 sort(ap);
365                 for (p = ap; *p != NULL; p++)
366                         printf("%s\t%s\n", *p, value(*p));
367                 return (0);
368         }
369         errs = 0;
370         for (ap = arglist; *ap != NULL; ap++) {
371                 cp = *ap;
372                 cp2 = varbuf;
373                 while (cp2 < varbuf + sizeof(varbuf) - 1 && *cp != '=' && *cp != '\0')
374                         *cp2++ = *cp++;
375                 *cp2 = '\0';
376                 if (*cp == '\0')
377                         cp = "";
378                 else
379                         cp++;
380                 if (equal(varbuf, "")) {
381                         printf("Non-null variable name required\n");
382                         errs++;
383                         continue;
384                 }
385                 assign(varbuf, cp);
386         }
387         return (errs);
388 }
389
390 /*
391  * Unset a bunch of variable values.
392  */
393 int
394 unset(char **arglist)
395 {
396         struct var *vp, *vp2;
397         int errs, h;
398         char **ap;
399
400         errs = 0;
401         for (ap = arglist; *ap != NULL; ap++) {
402                 if ((vp2 = lookup(*ap)) == NULL) {
403                         if (getenv(*ap)) 
404                                 unsetenv(*ap);
405                         else if (!sourcing) {
406                                 printf("\"%s\": undefined variable\n", *ap);
407                                 errs++;
408                         }
409                         continue;
410                 }
411                 h = hash(*ap);
412                 if (vp2 == variables[h]) {
413                         variables[h] = variables[h]->v_link;
414                         vfree(vp2->v_name);
415                         vfree(vp2->v_value);
416                         free(vp2);
417                         continue;
418                 }
419                 for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
420                         ;
421                 vp->v_link = vp2->v_link;
422                 vfree(vp2->v_name);
423                 vfree(vp2->v_value);
424                 free(vp2);
425         }
426         return (errs);
427 }
428
429 /*
430  * Put add users to a group.
431  */
432 int
433 group(char **argv)
434 {
435         struct grouphead *gh;
436         struct group *gp;
437         char **ap, *gname, **p;
438         int h, s;
439
440         if (*argv == NULL) {
441                 for (h = 0, s = 1; h < HSHSIZE; h++)
442                         for (gh = groups[h]; gh != NULL; gh = gh->g_link)
443                                 s++;
444                 ap = (char **)salloc(s * sizeof(*ap));
445                 for (h = 0, p = ap; h < HSHSIZE; h++)
446                         for (gh = groups[h]; gh != NULL; gh = gh->g_link)
447                                 *p++ = gh->g_name;
448                 *p = NULL;
449                 sort(ap);
450                 for (p = ap; *p != NULL; p++)
451                         printgroup(*p);
452                 return (0);
453         }
454         if (argv[1] == NULL) {
455                 printgroup(*argv);
456                 return (0);
457         }
458         gname = *argv;
459         h = hash(gname);
460         if ((gh = findgroup(gname)) == NULL) {
461                 gh = calloc(sizeof(*gh), 1);
462                 gh->g_name = vcopy(gname);
463                 gh->g_list = NULL;
464                 gh->g_link = groups[h];
465                 groups[h] = gh;
466         }
467
468         /*
469          * Insert names from the command list into the group.
470          * Who cares if there are duplicates?  They get tossed
471          * later anyway.
472          */
473
474         for (ap = argv+1; *ap != NULL; ap++) {
475                 gp = calloc(sizeof(*gp), 1);
476                 gp->ge_name = vcopy(*ap);
477                 gp->ge_link = gh->g_list;
478                 gh->g_list = gp;
479         }
480         return (0);
481 }
482
483 /*
484  * Sort the passed string vecotor into ascending dictionary
485  * order.
486  */
487 void
488 sort(char **list)
489 {
490         char **ap;
491
492         for (ap = list; *ap != NULL; ap++)
493                 ;
494         if (ap-list < 2)
495                 return;
496         qsort(list, ap-list, sizeof(*list), diction);
497 }
498
499 /*
500  * Do a dictionary order comparison of the arguments from
501  * qsort.
502  */
503 int
504 diction(const void *a, const void *b)
505 {
506         return (strcmp(*(const char **)a, *(const char **)b));
507 }
508
509 /*
510  * The do nothing command for comments.
511  */
512
513 /*ARGSUSED*/
514 int
515 null(int e)
516 {
517         return (0);
518 }
519
520 /*
521  * Change to another file.  With no argument, print information about
522  * the current file.
523  */
524 int
525 file(char **argv)
526 {
527         if (argv[0] == NULL) {
528                 newfileinfo(0);
529                 return (0);
530         }
531         if (setfile(*argv) < 0)
532                 return (1);
533         announce();
534         return (0);
535 }
536
537 /*
538  * Expand file names like echo
539  */
540 int
541 echo(char **argv)
542 {
543         char **ap, *cp;
544
545         for (ap = argv; *ap != NULL; ap++) {
546                 cp = *ap;
547                 if ((cp = expand(cp)) != NULL) {
548                         if (ap != argv)
549                                 printf(" ");
550                         printf("%s", cp);
551                 }
552         }
553         printf("\n");
554         return (0);
555 }
556
557 int
558 Respond(int *msgvec)
559 {
560         if (value("Replyall") == NULL && value("flipr") == NULL)
561                 return (doRespond(msgvec));
562         else
563                 return (dorespond(msgvec));
564 }
565
566 /*
567  * Reply to a series of messages by simply mailing to the senders
568  * and not messing around with the To: and Cc: lists as in normal
569  * reply.
570  */
571 int
572 doRespond(int *msgvec)
573 {
574         struct header head;
575         struct message *mp;
576         int *ap;
577         char *cp, *mid;
578
579         head.h_to = NULL;
580         for (ap = msgvec; *ap != 0; ap++) {
581                 mp = &message[*ap - 1];
582                 touch(mp);
583                 dot = mp;
584                 if ((cp = skin(hfield("from", mp))) == NULL)
585                         cp = skin(nameof(mp, 2));
586                 head.h_to = cat(head.h_to, extract(cp, GTO));
587                 mid = skin(hfield("message-id", mp));
588         }
589         if (head.h_to == NULL)
590                 return (0);
591         mp = &message[msgvec[0] - 1];
592         if ((head.h_subject = hfield("subject", mp)) == NULL)
593                 head.h_subject = hfield("subj", mp);
594         head.h_subject = reedit(head.h_subject);
595         head.h_cc = NULL;
596         head.h_bcc = NULL;
597         head.h_smopts = NULL;
598         head.h_replyto = value("REPLYTO");
599         head.h_inreplyto = mid;
600         mail1(&head, 1);
601         return (0);
602 }
603
604 /*
605  * Conditional commands.  These allow one to parameterize one's
606  * .mailrc and do some things if sending, others if receiving.
607  */
608 int
609 ifcmd(char **argv)
610 {
611         char *cp;
612
613         if (cond != CANY) {
614                 printf("Illegal nested \"if\"\n");
615                 return (1);
616         }
617         cond = CANY;
618         cp = argv[0];
619         switch (*cp) {
620         case 'r': case 'R':
621                 cond = CRCV;
622                 break;
623
624         case 's': case 'S':
625                 cond = CSEND;
626                 break;
627
628         default:
629                 printf("Unrecognized if-keyword: \"%s\"\n", cp);
630                 return (1);
631         }
632         return (0);
633 }
634
635 /*
636  * Implement 'else'.  This is pretty simple -- we just
637  * flip over the conditional flag.
638  */
639 int
640 elsecmd(void)
641 {
642         switch (cond) {
643         case CANY:
644                 printf("\"Else\" without matching \"if\"\n");
645                 return (1);
646
647         case CSEND:
648                 cond = CRCV;
649                 break;
650
651         case CRCV:
652                 cond = CSEND;
653                 break;
654
655         default:
656                 printf("Mail's idea of conditions is screwed up\n");
657                 cond = CANY;
658                 break;
659         }
660         return (0);
661 }
662
663 /*
664  * End of if statement.  Just set cond back to anything.
665  */
666 int
667 endifcmd(void)
668 {
669         if (cond == CANY) {
670                 printf("\"Endif\" without matching \"if\"\n");
671                 return (1);
672         }
673         cond = CANY;
674         return (0);
675 }
676
677 /*
678  * Set the list of alternate names.
679  */
680 int
681 alternates(char **namelist)
682 {
683         int c;
684         char **ap, **ap2, *cp;
685
686         c = argcount(namelist) + 1;
687         if (c == 1) {
688                 if (altnames == 0)
689                         return (0);
690                 for (ap = altnames; *ap != NULL; ap++)
691                         printf("%s ", *ap);
692                 printf("\n");
693                 return (0);
694         }
695         if (altnames != 0)
696                 free(altnames);
697         altnames = calloc((unsigned)c, sizeof(char *));
698         for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
699                 cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
700                 strcpy(cp, *ap);
701                 *ap2 = cp;
702         }
703         *ap2 = NULL;
704         return (0);
705 }