xref: /netbsd/usr.bin/mail/cmd3.c (revision 6550d01e)
1 /*	$NetBSD: cmd3.c,v 1.41 2009/04/11 14:22:32 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.41 2009/04/11 14:22:32 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
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
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
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
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
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
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
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 *
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 *
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
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
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
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
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
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
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
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
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, "w+")) == 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
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(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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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