1 /*        $NetBSD: cmd3.c,v 1.44 2017/11/09 20:27:50 christos Exp $   */
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)cmd3.c      8.2 (Berkeley) 4/20/95";
36 #else
37 __RCSID("$NetBSD: cmd3.c,v 1.44 2017/11/09 20:27:50 christos Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include "rcv.h"
42 #include <assert.h>
43 #include <util.h>
44 #include "extern.h"
45 #include "mime.h"
46 #include "sig.h"
47 #include "thread.h"
48 
49 /*
50  * Mail -- a mail program
51  *
52  * Still more user commands.
53  */
54 
55 
56 /*
57  * Do a dictionary order comparison of the arguments from
58  * qsort.
59  */
60 static int
diction(const void * a,const void * b)61 diction(const void *a, const void *b)
62 {
63 
64           return strcmp(*(const char *const *)a, *(const char *const *)b);
65 }
66 
67 /*
68  * Sort the passed string vector into ascending dictionary
69  * order.
70  */
71 PUBLIC void
sort(const char ** list)72 sort(const char **list)
73 {
74           const char **ap;
75 
76           for (ap = list; *ap != NULL; ap++)
77                     continue;
78           if (ap - list < 2)
79                     return;
80           qsort(list, (size_t)(ap - list), sizeof(*list), diction);
81 }
82 
83 /*
84  * Expand the shell escape by expanding unescaped !'s into the
85  * last issued command where possible.
86  */
87 static int
bangexp(char * str)88 bangexp(char *str)
89 {
90           static char lastbang[128];
91           char bangbuf[LINESIZE];
92           char *cp, *cp2;
93           ssize_t n;
94           int changed;
95 
96           changed = 0;
97           cp = str;
98           cp2 = bangbuf;
99           n = sizeof(bangbuf);          /* bytes left in bangbuf */
100           while (*cp) {
101                     if (*cp == '!') {
102                               if (n < (int)strlen(lastbang)) {
103  overf:
104                                         (void)printf("Command buffer overflow\n");
105                                         return -1;
106                               }
107                               changed++;
108                               (void)strcpy(cp2, lastbang);
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                     (void)printf("!%s\n", bangbuf);
128                     (void)fflush(stdout);
129           }
130           (void)strcpy(str, bangbuf);
131           (void)strlcpy(lastbang, bangbuf, sizeof(lastbang));
132           return 0;
133 }
134 
135 /*
136  * Process a shell escape by saving signals, ignoring signals,
137  * and forking a sh -c
138  */
139 PUBLIC int
shell(void * v)140 shell(void *v)
141 {
142           struct sigaction osa;
143           sigset_t oset;
144           char *str;
145           const char *shellcmd;
146           char cmd[LINESIZE];
147 
148           str = v;
149           sig_check();
150           (void)sig_ignore(SIGINT, &osa, &oset);
151           (void)strcpy(cmd, str);
152           if (bangexp(cmd) < 0)
153                     return 1;
154           if ((shellcmd = value(ENAME_SHELL)) == NULL)
155                     shellcmd = _PATH_CSHELL;
156           (void)run_command(shellcmd, NULL, 0, 1, "-c", cmd, NULL);
157           (void)sig_restore(SIGINT, &osa, &oset);
158           (void)printf("!\n");
159           sig_check();
160           return 0;
161 }
162 
163 /*
164  * Fork an interactive shell.
165  */
166 /*ARGSUSED*/
167 PUBLIC int
dosh(void * v __unused)168 dosh(void *v __unused)
169 {
170           struct sigaction osa;
171           sigset_t oset;
172           const char *shellcmd;
173 
174           sig_check();
175           (void)sig_ignore(SIGINT, &osa, &oset);
176           if ((shellcmd = value(ENAME_SHELL)) == NULL)
177                     shellcmd = _PATH_CSHELL;
178           (void)run_command(shellcmd, NULL, 0, 1, NULL);
179           (void)sig_restore(SIGINT, &osa, &oset);
180           (void)putchar('\n');
181           sig_check();
182           return 0;
183 }
184 
185 /*
186  * Print out a nice help message from some file or another.
187  */
188 
189 /*ARGSUSED*/
190 PUBLIC int
help(void * v __unused)191 help(void *v __unused)
192 {
193 
194           cathelp(_PATH_HELP);
195           return 0;
196 }
197 
198 /*
199  * Change user's working directory.
200  */
201 PUBLIC int
schdir(void * v)202 schdir(void *v)
203 {
204           char **arglist;
205           const char *cp;
206 
207           arglist = v;
208           if (*arglist == NULL)
209                     cp = homedir;
210           else
211                     if ((cp = expand(*arglist)) == NULL)
212                               return 1;
213           if (chdir(cp) < 0) {
214                     warn("%s", cp);
215                     return 1;
216           }
217           return 0;
218 }
219 
220 /*
221  * Return the smopts field if "ReplyAsRecipient" is defined.
222  */
223 static struct name *
set_smopts(struct message * mp)224 set_smopts(struct message *mp)
225 {
226           char *cp;
227           struct name *np;
228           char *reply_as_recipient;
229 
230           np = NULL;
231           reply_as_recipient = value(ENAME_REPLYASRECIPIENT);
232           if (reply_as_recipient &&
233               (cp = skin(hfield("to", mp))) != NULL &&
234               extract(cp, GTO)->n_flink == NULL) {  /* check for one recipient */
235                     char *p, *q;
236                     size_t len = strlen(cp);
237                     /*
238                      * XXX - perhaps we always want to ignore
239                      *       "undisclosed-recipients:;" ?
240                      */
241                     for (p = q = reply_as_recipient; *p; p = q) {
242                               while (*q != '\0' && *q != ',' && !is_WSP(*q))
243                                         q++;
244                               if (p + len == q && strncasecmp(cp, p, len) == 0)
245                                         return np;
246                               while (*q == ',' || is_WSP(*q))
247                                         q++;
248                     }
249                     np = extract(__UNCONST("-f"), GSMOPTS);
250                     np = cat(np, extract(cp, GSMOPTS));
251           }
252 
253           return np;
254 }
255 
256 /*
257  * Modify the subject we are replying to to begin with Re: if
258  * it does not already.
259  */
260 static char *
reedit(char * subj,const char * pref)261 reedit(char *subj, const char *pref)
262 {
263           char *newsubj;
264           size_t preflen;
265 
266           assert(pref != NULL);
267           if (subj == NULL)
268                     return __UNCONST(pref);
269           preflen = strlen(pref);
270           if (strncasecmp(subj, pref, preflen) == 0)
271                     return subj;
272           newsubj = salloc(strlen(subj) + preflen + 1 + 1);
273           (void)sprintf(newsubj, "%s %s", pref, subj);
274           return newsubj;
275 }
276 
277 /*
278  * Set the "In-Reply-To" and "References" header fields appropriately.
279  * Used in replies.
280  */
281 static void
set_ident_fields(struct header * hp,struct message * mp)282 set_ident_fields(struct header *hp, struct message *mp)
283 {
284           char *in_reply_to;
285           char *references;
286 
287           in_reply_to = hfield("message-id", mp);
288           hp->h_in_reply_to = in_reply_to;
289 
290           references = hfield("references", mp);
291           hp->h_references = extract(references, GMISC);
292           hp->h_references = cat(hp->h_references, extract(in_reply_to, GMISC));
293 }
294 
295 /*
296  * Reply to a list of messages.  Extract each name from the
297  * message header and send them off to mail1()
298  */
299 static int
respond_core(int * msgvec)300 respond_core(int *msgvec)
301 {
302           struct message *mp;
303           char *cp, *rcv, *replyto;
304           char **ap;
305           struct name *np;
306           struct header head;
307 
308           /* ensure that all header fields are initially NULL */
309           (void)memset(&head, 0, sizeof(head));
310 
311           if (msgvec[1] != 0) {
312                     (void)printf("Sorry, can't reply to multiple messages at once\n");
313                     return 1;
314           }
315           mp = get_message(msgvec[0]);
316           touch(mp);
317           dot = mp;
318           if ((rcv = skin(hfield("from", mp))) == NULL)
319                     rcv = skin(nameof(mp, 1));
320           if ((replyto = skin(hfield("reply-to", mp))) != NULL)
321                     np = extract(replyto, GTO);
322           else if ((cp = skin(hfield("to", mp))) != NULL)
323                     np = extract(cp, GTO);
324           else
325                     np = NULL;
326           np = elide(np);
327           /*
328            * Delete my name from the reply list,
329            * and with it, all my alternate names.
330            */
331           np = delname(np, myname);
332           if (altnames)
333                     for (ap = altnames; *ap; ap++)
334                               np = delname(np, *ap);
335           if (np != NULL && replyto == NULL)
336                     np = cat(np, extract(rcv, GTO));
337           else if (np == NULL) {
338                     if (replyto != NULL)
339                               (void)printf("Empty reply-to field -- replying to author\n");
340                     np = extract(rcv, GTO);
341           }
342           head.h_to = np;
343           if ((head.h_subject = hfield("subject", mp)) == NULL)
344                     head.h_subject = hfield("subj", mp);
345           head.h_subject = reedit(head.h_subject, "Re:");
346           if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
347                     np = elide(extract(cp, GCC));
348                     np = delname(np, myname);
349                     if (altnames != 0)
350                               for (ap = altnames; *ap; ap++)
351                                         np = delname(np, *ap);
352                     head.h_cc = np;
353           } else
354                     head.h_cc = NULL;
355           head.h_bcc = NULL;
356           head.h_smopts = set_smopts(mp);
357 #ifdef MIME_SUPPORT
358           head.h_attach = NULL;
359 #endif
360           set_ident_fields(&head, mp);
361           mail1(&head, 1);
362           return 0;
363 }
364 
365 /*
366  * Reply to a series of messages by simply mailing to the senders
367  * and not messing around with the To: and Cc: lists as in normal
368  * reply.
369  */
370 static int
Respond_core(int msgvec[])371 Respond_core(int msgvec[])
372 {
373           struct header head;
374           struct message *mp;
375           int *ap;
376           char *cp;
377 
378           /* ensure that all header fields are initially NULL */
379           (void)memset(&head, 0, sizeof(head));
380 
381           head.h_to = NULL;
382           for (ap = msgvec; *ap != 0; ap++) {
383                     mp = get_message(*ap);
384                     touch(mp);
385                     dot = mp;
386                     if ((cp = skin(hfield("from", mp))) == NULL)
387                               cp = skin(nameof(mp, 2));
388                     head.h_to = cat(head.h_to, extract(cp, GTO));
389           }
390           if (head.h_to == NULL)
391                     return 0;
392           mp = get_message(msgvec[0]);
393           if ((head.h_subject = hfield("subject", mp)) == NULL)
394                     head.h_subject = hfield("subj", mp);
395           head.h_subject = reedit(head.h_subject, "Re:");
396           head.h_cc = NULL;
397           head.h_bcc = NULL;
398           head.h_smopts = set_smopts(mp);
399 #ifdef MIME_SUPPORT
400           head.h_attach = NULL;
401 #endif
402           set_ident_fields(&head, mp);
403           mail1(&head, 1);
404           return 0;
405 }
406 
407 PUBLIC int
respond(void * v)408 respond(void *v)
409 {
410           int *msgvec = v;
411           if (value(ENAME_REPLYALL) == NULL)
412                     return respond_core(msgvec);
413           else
414                     return Respond_core(msgvec);
415 }
416 
417 PUBLIC int
Respond(void * v)418 Respond(void *v)
419 {
420           int *msgvec = v;
421           if (value(ENAME_REPLYALL) == NULL)
422                     return Respond_core(msgvec);
423           else
424                     return respond_core(msgvec);
425 }
426 
427 #ifdef MIME_SUPPORT
428 static int
forward_one(int msgno,struct name * h_to)429 forward_one(int msgno, struct name *h_to)
430 {
431           struct attachment attach;
432           struct message *mp;
433           struct header hdr;
434 
435           mp = get_message(msgno);
436           if (mp == NULL) {
437                     (void)printf("no such message %d\n", msgno);
438                     return 1;
439           }
440           (void)printf("message %d\n", msgno);
441 
442           (void)memset(&attach, 0, sizeof(attach));
443           attach.a_type = ATTACH_MSG;
444           attach.a_msg = mp;
445           attach.a_Content = get_mime_content(&attach, 0);
446 
447           (void)memset(&hdr, 0, sizeof(hdr));
448           hdr.h_to = h_to;
449           if ((hdr.h_subject = hfield("subject", mp)) == NULL)
450                     hdr.h_subject = hfield("subj", mp);
451           hdr.h_subject = reedit(hdr.h_subject, "Fwd:");
452           hdr.h_attach = &attach;
453           hdr.h_smopts = set_smopts(mp);
454 
455           set_ident_fields(&hdr, mp);
456           mail1(&hdr, 1);
457           return 0;
458 }
459 
460 PUBLIC int
forward(void * v)461 forward(void *v)
462 {
463           int *msgvec = v;
464           int *ip;
465           struct header hdr;
466           int rval;
467 
468           if (forwardtab[0].i_count == 0) {
469                     /* setup the forward tab */
470                     add_ignore("Status", forwardtab);
471           }
472           (void)memset(&hdr, 0, sizeof(hdr));
473           if ((rval = grabh(&hdr, GTO)) != 0)
474                     return rval;
475 
476           if (hdr.h_to == NULL) {
477                     (void)printf("address missing!\n");
478                     return 1;
479           }
480           for (ip = msgvec; *ip; ip++) {
481                     int e;
482                     if ((e = forward_one(*ip, hdr.h_to)) != 0)
483                               return e;
484           }
485           return 0;
486 }
487 #endif /* MIME_SUPPORT */
488 
489 static int
bounce_one(int msgno,const char ** smargs,struct name * h_to)490 bounce_one(int msgno, const char **smargs, struct name *h_to)
491 {
492           char mailtempname[PATHSIZE];
493           struct message *mp;
494           int fd;
495           FILE *obuf;
496           int rval;
497 
498           rval = 0;
499 
500           obuf = NULL;
501           (void)snprintf(mailtempname, sizeof(mailtempname),
502               "%s/mail.RsXXXXXXXXXX", tmpdir);
503           if ((fd = mkstemp(mailtempname)) == -1 ||
504               (obuf = Fdopen(fd, "wef+")) == NULL) {
505                     if (fd != -1)
506                               (void)close(fd);
507                     warn("%s", mailtempname);
508                     rval = 1;
509                     goto done;
510           }
511           (void)rm(mailtempname);
512 
513           mp = get_message(msgno);
514 
515           if (mp == NULL) {
516                     (void)printf("no such message %d\n", msgno);
517                     rval = 1;
518                     goto done;
519           }
520           else {
521                     char *cp;
522                     char **ap;
523                     struct name *np;
524                     struct header hdr;
525 
526                     /*
527                      * Construct and output a new "To:" field:
528                      * Remove our address from anything in the old "To:" field
529                      * and append that list to the bounce address(es).
530                      */
531                     np = NULL;
532                     if ((cp = skin(hfield("to", mp))) != NULL)
533                               np = extract(cp, GTO);
534                     np = delname(np, myname);
535                     if (altnames)
536                               for (ap = altnames; *ap; ap++)
537                                         np = delname(np, *ap);
538                     np = cat(h_to, np);
539                     (void)memset(&hdr, 0, sizeof(hdr));
540                     hdr.h_to = elide(np);
541                     (void)puthead(&hdr, obuf, GTO | GCOMMA);
542           }
543           if (sendmessage(mp, obuf, bouncetab, NULL, NULL)) {
544                     (void)printf("bounce failed for message %d\n", msgno);
545                     rval = 1;
546                     goto done;
547           }
548           rewind(obuf);       /* XXX - here or inside mail2() */
549           mail2(obuf, smargs);
550  done:
551           if (obuf)
552                     (void)Fclose(obuf);
553           return rval;
554 }
555 
556 PUBLIC int
bounce(void * v)557 bounce(void *v)
558 {
559           int *msgvec;
560           int *ip;
561           const char **smargs;
562           struct header hdr;
563           int rval;
564 
565           msgvec = v;
566           if (bouncetab[0].i_count == 0) {
567                     /* setup the bounce tab */
568                     add_ignore("Status", bouncetab);
569                     add_ignore("Delivered-To", bouncetab);
570                     add_ignore("To", bouncetab);
571                     add_ignore("X-Original-To", bouncetab);
572           }
573           (void)memset(&hdr, 0, sizeof(hdr));
574           if ((rval = grabh(&hdr, GTO)) != 0)
575                     return rval;
576 
577           if (hdr.h_to == NULL)
578                     return 1;
579 
580           smargs = unpack(NULL, hdr.h_to);
581           for (ip = msgvec; *ip; ip++) {
582                     int e;
583                     if ((e = bounce_one(*ip, smargs, hdr.h_to)) != 0)
584                               return e;
585           }
586           return 0;
587 }
588 
589 /*
590  * Preserve the named messages, so that they will be sent
591  * back to the system mailbox.
592  */
593 PUBLIC int
preserve(void * v)594 preserve(void *v)
595 {
596           int *msgvec;
597           int *ip;
598 
599           msgvec = v;
600           if (edit) {
601                     (void)printf("Cannot \"preserve\" in edit mode\n");
602                     return 1;
603           }
604           for (ip = msgvec; *ip != 0; ip++)
605                     dot = set_m_flag(*ip, ~(MBOX | MPRESERVE), MPRESERVE);
606 
607           return 0;
608 }
609 
610 /*
611  * Mark all given messages as unread, preserving the new status.
612  */
613 PUBLIC int
unread(void * v)614 unread(void *v)
615 {
616           int *msgvec;
617           int *ip;
618 
619           msgvec = v;
620           for (ip = msgvec; *ip != 0; ip++)
621                     dot = set_m_flag(*ip, ~(MREAD | MTOUCH | MSTATUS), MSTATUS);
622 
623           return 0;
624 }
625 
626 /*
627  * Mark all given messages as read.
628  */
629 PUBLIC int
markread(void * v)630 markread(void *v)
631 {
632           int *msgvec;
633           int *ip;
634 
635           msgvec = v;
636           for (ip = msgvec; *ip != 0; ip++)
637                     dot = set_m_flag(*ip,
638                         ~(MNEW | MTOUCH | MREAD | MSTATUS), MREAD | MSTATUS);
639 
640           return 0;
641 }
642 
643 /*
644  * Print the size of each message.
645  */
646 PUBLIC int
messize(void * v)647 messize(void *v)
648 {
649           int *msgvec;
650           struct message *mp;
651           int *ip, mesg;
652 
653           msgvec = v;
654           for (ip = msgvec; *ip != 0; ip++) {
655                     mesg = *ip;
656                     mp = get_message(mesg);
657                     (void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
658                         (unsigned long long)mp->m_size);
659           }
660           return 0;
661 }
662 
663 /*
664  * Quit quickly.  If we are sourcing, just pop the input level
665  * by returning an error.
666  */
667 /*ARGSUSED*/
668 PUBLIC int
rexit(void * v __unused)669 rexit(void *v __unused)
670 {
671           if (sourcing)
672                     return 1;
673           exit(0);
674           /*NOTREACHED*/
675 }
676 
677 /*
678  * Set or display a variable value.  Syntax is similar to that
679  * of csh.
680  */
681 PUBLIC int
set(void * v)682 set(void *v)
683 {
684           const char **arglist = v;
685           struct var *vp;
686           const char *cp;
687           char varbuf[LINESIZE];
688           const char **ap, **p;
689           int errs, h, s;
690           size_t l;
691 
692           if (*arglist == NULL) {
693                     for (h = 0, s = 1; h < HSHSIZE; h++)
694                               for (vp = variables[h]; vp != NULL; vp = vp->v_link)
695                                         s++;
696                     ap = salloc(s * sizeof(*ap));
697                     for (h = 0, p = ap; h < HSHSIZE; h++)
698                               for (vp = variables[h]; vp != NULL; vp = vp->v_link)
699                                         *p++ = vp->v_name;
700                     *p = NULL;
701                     sort(ap);
702                     for (p = ap; *p != NULL; p++)
703                               (void)printf("%s\t%s\n", *p, value(*p));
704                     return 0;
705           }
706           errs = 0;
707           for (ap = arglist; *ap != NULL; ap++) {
708                     cp = *ap;
709                     while (*cp != '=' && *cp != '\0')
710                               ++cp;
711                     l = cp - *ap;
712                     if (l >= sizeof(varbuf))
713                               l = sizeof(varbuf) - 1;
714                     (void)strncpy(varbuf, *ap, l);
715                     varbuf[l] = '\0';
716                     if (*cp == '\0')
717                               cp = "";
718                     else
719                               cp++;
720                     if (equal(varbuf, "")) {
721                               (void)printf("Non-null variable name required\n");
722                               errs++;
723                               continue;
724                     }
725                     assign(varbuf, cp);
726           }
727           return errs;
728 }
729 
730 /*
731  * Unset a bunch of variable values.
732  */
733 PUBLIC int
unset(void * v)734 unset(void *v)
735 {
736           char **arglist = v;
737           struct var *vp, *vp2;
738           int errs, h;
739           char **ap;
740 
741           errs = 0;
742           for (ap = arglist; *ap != NULL; ap++) {
743                     if ((vp2 = lookup(*ap)) == NULL) {
744                               if (getenv(*ap)) {
745                                         (void)unsetenv(*ap);
746                               } else if (!sourcing) {
747                                         (void)printf("\"%s\": undefined variable\n", *ap);
748                                         errs++;
749                               }
750                               continue;
751                     }
752                     h = hash(*ap);
753                     if (vp2 == variables[h]) {
754                               variables[h] = variables[h]->v_link;
755                               v_free(vp2->v_name);
756                         v_free(vp2->v_value);
757                               free(vp2);
758                               continue;
759                     }
760                     for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
761                               continue;
762                     vp->v_link = vp2->v_link;
763                 v_free(vp2->v_name);
764                 v_free(vp2->v_value);
765                     free(vp2);
766           }
767           return errs;
768 }
769 
770 /*
771  * Show a variable value.
772  */
773 PUBLIC int
show(void * v)774 show(void *v)
775 {
776           const char **arglist = v;
777           struct var *vp;
778           const char **ap, **p;
779           int h, s;
780 
781           if (*arglist == NULL) {
782                     for (h = 0, s = 1; h < HSHSIZE; h++)
783                               for (vp = variables[h]; vp != NULL; vp = vp->v_link)
784                                         s++;
785                     ap = salloc(s * sizeof(*ap));
786                     for (h = 0, p = ap; h < HSHSIZE; h++)
787                               for (vp = variables[h]; vp != NULL; vp = vp->v_link)
788                                         *p++ = vp->v_name;
789                     *p = NULL;
790                     sort(ap);
791                     for (p = ap; *p != NULL; p++)
792                               (void)printf("%s=%s\n", *p, value(*p));
793                     return 0;
794           }
795 
796           for (ap = arglist; *ap != NULL; ap++) {
797                     char *val = value(*ap);
798                     (void)printf("%s=%s\n", *ap, val ? val : "<null>");
799           }
800           return 0;
801 }
802 
803 
804 /*
805  * Put add users to a group.
806  */
807 PUBLIC int
group(void * v)808 group(void *v)
809 {
810           const char **argv = v;
811           struct grouphead *gh;
812           struct group *gp;
813           int h;
814           int s;
815           const char *gname;
816           const char **ap, **p;
817 
818           if (*argv == NULL) {
819                     for (h = 0, s = 1; h < HSHSIZE; h++)
820                               for (gh = groups[h]; gh != NULL; gh = gh->g_link)
821                                         s++;
822                     ap = salloc(s * sizeof(*ap));
823                     for (h = 0, p = ap; h < HSHSIZE; h++)
824                               for (gh = groups[h]; gh != NULL; gh = gh->g_link)
825                                         *p++ = gh->g_name;
826                     *p = NULL;
827                     sort(ap);
828                     for (p = ap; *p != NULL; p++)
829                               printgroup(*p);
830                     return 0;
831           }
832           if (argv[1] == NULL) {
833                     printgroup(*argv);
834                     return 0;
835           }
836           gname = *argv;
837           h = hash(gname);
838           if ((gh = findgroup(gname)) == NULL) {
839                     gh = ecalloc(1, sizeof(*gh));
840                     gh->g_name = vcopy(gname);
841                     gh->g_list = NULL;
842                     gh->g_link = groups[h];
843                     groups[h] = gh;
844           }
845 
846           /*
847            * Insert names from the command list into the group.
848            * Who cares if there are duplicates?  They get tossed
849            * later anyway.
850            */
851 
852           for (ap = argv + 1; *ap != NULL; ap++) {
853                     gp = ecalloc(1, sizeof(*gp));
854                     gp->ge_name = vcopy(*ap);
855                     gp->ge_link = gh->g_list;
856                     gh->g_list = gp;
857           }
858           return 0;
859 }
860 
861 /*
862  * Delete the named group alias. Return zero if the group was
863  * successfully deleted, or -1 if there was no such group.
864  */
865 static int
delgroup(const char * name)866 delgroup(const char *name)
867 {
868           struct grouphead *gh, *p;
869           struct group *g;
870           int h;
871 
872           h = hash(name);
873           for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
874                     if (strcmp(gh->g_name, name) == 0) {
875                               if (p == NULL)
876                                         groups[h] = gh->g_link;
877                               else
878                                         p->g_link = gh->g_link;
879                               while (gh->g_list != NULL) {
880                                         g = gh->g_list;
881                                         gh->g_list = g->ge_link;
882                                         free(g->ge_name);
883                                         free(g);
884                               }
885                               free(gh->g_name);
886                               free(gh);
887                               return 0;
888                     }
889           return -1;
890 }
891 
892 /*
893  * The unalias command takes a list of alises
894  * and discards the remembered groups of users.
895  */
896 PUBLIC int
unalias(void * v)897 unalias(void *v)
898 {
899           char **ap;
900 
901           for (ap = v; *ap != NULL; ap++)
902                     (void)delgroup(*ap);
903           return 0;
904 }
905 
906 /*
907  * The do nothing command for comments.
908  */
909 /*ARGSUSED*/
910 PUBLIC int
null(void * v __unused)911 null(void *v __unused)
912 {
913           return 0;
914 }
915 
916 /*
917  * Change to another file.  With no argument, print information about
918  * the current file.
919  */
920 PUBLIC int
file(void * v)921 file(void *v)
922 {
923           char **argv = v;
924 
925           if (argv[0] == NULL) {
926                     (void)newfileinfo(0);
927                     return 0;
928           }
929           if (setfile(*argv) < 0)
930                     return 1;
931           announce();
932 
933           return 0;
934 }
935 
936 /*
937  * Expand file names like echo
938  */
939 PUBLIC int
echo(void * v)940 echo(void *v)
941 {
942           char **argv = v;
943           char **ap;
944           const char *cp;
945 
946           for (ap = argv; *ap != NULL; ap++) {
947                     cp = *ap;
948                     if ((cp = expand(cp)) != NULL) {
949                               if (ap != argv)
950                                         (void)putchar(' ');
951                               (void)printf("%s", cp);
952                     }
953           }
954           (void)putchar('\n');
955           return 0;
956 }
957 
958 /*
959  * Routines to push and pop the condition code to support nested
960  * if/else/endif statements.
961  */
962 static void
push_cond(int c_cond)963 push_cond(int c_cond)
964 {
965           struct cond_stack_s *csp;
966           csp = emalloc(sizeof(*csp));
967           csp->c_cond = c_cond;
968           csp->c_next = cond_stack;
969           cond_stack = csp;
970 }
971 
972 static int
pop_cond(void)973 pop_cond(void)
974 {
975           int c_cond;
976           struct cond_stack_s *csp;
977 
978           if ((csp = cond_stack) == NULL)
979                     return -1;
980 
981           c_cond = csp->c_cond;
982           cond_stack = csp->c_next;
983           free(csp);
984           return c_cond;
985 }
986 
987 /*
988  * Conditional commands.  These allow one to parameterize one's
989  * .mailrc and do some things if sending, others if receiving.
990  */
991 static int
if_push(void)992 if_push(void)
993 {
994           push_cond(cond);
995           cond &= ~CELSE;
996           if ((cond & (CIF | CSKIP)) == (CIF | CSKIP)) {
997                     cond |= CIGN;
998                     return 1;
999           }
1000           return 0;
1001 }
1002 
1003 PUBLIC int
ifcmd(void * v)1004 ifcmd(void *v)
1005 {
1006           char **argv = v;
1007           char *keyword = argv[0];
1008           static const struct modetbl_s {
1009                     const char *m_name;
1010                     enum mailmode_e m_mode;
1011           } modetbl[] = {
1012                     { "receiving",                mm_receiving },
1013                     { "sending",                  mm_sending },
1014                     { "headersonly",    mm_hdrsonly },
1015                     { NULL,                       0 },
1016           };
1017           const struct modetbl_s *mtp;
1018 
1019           if (if_push())
1020                     return 0;
1021 
1022           cond = CIF;
1023           for (mtp = modetbl; mtp->m_name; mtp++)
1024                     if (strcasecmp(keyword, mtp->m_name) == 0)
1025                               break;
1026 
1027           if (mtp->m_name == NULL) {
1028                     cond = CNONE;
1029                     (void)printf("Unrecognized if-keyword: \"%s\"\n", keyword);
1030                     return 1;
1031           }
1032           if (mtp->m_mode != mailmode)
1033                     cond |= CSKIP;
1034 
1035           return 0;
1036 }
1037 
1038 PUBLIC int
ifdefcmd(void * v)1039 ifdefcmd(void *v)
1040 {
1041           char **argv = v;
1042 
1043           if (if_push())
1044                     return 0;
1045 
1046           cond = CIF;
1047           if (value(argv[0]) == NULL)
1048                     cond |= CSKIP;
1049 
1050           return 0;
1051 }
1052 
1053 PUBLIC int
ifndefcmd(void * v)1054 ifndefcmd(void *v)
1055 {
1056           int rval;
1057           rval = ifdefcmd(v);
1058           cond ^= CSKIP;
1059           return rval;
1060 }
1061 
1062 /*
1063  * Implement 'else'.  This is pretty simple -- we just
1064  * flip over the conditional flag.
1065  */
1066 /*ARGSUSED*/
1067 PUBLIC int
elsecmd(void * v __unused)1068 elsecmd(void *v __unused)
1069 {
1070           if (cond_stack == NULL || (cond & (CIF | CELSE)) != CIF) {
1071                     (void)printf("\"else\" without matching \"if\"\n");
1072                     cond = CNONE;
1073                     return 1;
1074           }
1075           if ((cond & CIGN) == 0) {
1076                     cond ^= CSKIP;
1077                     cond |= CELSE;
1078           }
1079           return 0;
1080 }
1081 
1082 /*
1083  * End of if statement.  Just set cond back to anything.
1084  */
1085 /*ARGSUSED*/
1086 PUBLIC int
endifcmd(void * v __unused)1087 endifcmd(void *v __unused)
1088 {
1089           if (cond_stack == NULL || (cond & CIF) != CIF) {
1090                     (void)printf("\"endif\" without matching \"if\"\n");
1091                     cond = CNONE;
1092                     return 1;
1093           }
1094           cond = pop_cond();
1095           return 0;
1096 }
1097 
1098 /*
1099  * Set the list of alternate names.
1100  */
1101 PUBLIC int
alternates(void * v)1102 alternates(void *v)
1103 {
1104           char **namelist = v;
1105           size_t c;
1106           char **ap, **ap2, *cp;
1107 
1108           c = argcount(namelist) + 1;
1109           if (c == 1) {
1110                     if (altnames == 0)
1111                               return 0;
1112                     for (ap = altnames; *ap; ap++)
1113                               (void)printf("%s ", *ap);
1114                     (void)printf("\n");
1115                     return 0;
1116           }
1117           if (altnames != 0)
1118                     free(altnames);
1119           altnames = ecalloc(c, sizeof(char *));
1120           for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1121                     cp = ecalloc(strlen(*ap) + 1, sizeof(char));
1122                     (void)strcpy(cp, *ap);
1123                     *ap2 = cp;
1124           }
1125           *ap2 = 0;
1126           return 0;
1127 }
1128