xref: /dragonfly/usr.bin/mail/cmd3.c (revision dc71b7ab81c4f5270d3668e1625d94a58895fa7a)
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
shell(char * str)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
dosh(char * str)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
bangexp(char * str,size_t strsize)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
help(void)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
schdir(char ** arglist)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
respond(int * msgvec)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
dorespond(int * msgvec)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 *
reedit(char * subj)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
preserve(int * msgvec)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
unread(int * msgvec)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
messize(int * msgvec)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
rexit(int e)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
set(char ** arglist)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
unset(char ** arglist)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
group(char ** argv)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
sort(char ** list)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
diction(const void * a,const void * b)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
null(int e)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
file(char ** argv)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
echo(char ** argv)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
Respond(int * msgvec)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
doRespond(int * msgvec)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
ifcmd(char ** argv)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
elsecmd(void)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
endifcmd(void)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
alternates(char ** namelist)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 }
706