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
35static 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 */
60static int
61diction(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 */
71PUBLIC void
72sort(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 */
87static int
88bangexp(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 */
139PUBLIC int
140shell(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*/
167PUBLIC int
168dosh(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*/
190PUBLIC int
191help(void *v __unused)
192{
193
194	cathelp(_PATH_HELP);
195	return 0;
196}
197
198/*
199 * Change user's working directory.
200 */
201PUBLIC int
202schdir(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 */
223static struct name *
224set_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 */
260static char *
261reedit(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 */
281static void
282set_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 */
299static int
300respond_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 */
370static int
371Respond_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
407PUBLIC int
408respond(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
417PUBLIC int
418Respond(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
428static int
429forward_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
460PUBLIC int
461forward(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
489static int
490bounce_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
556PUBLIC int
557bounce(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 */
593PUBLIC int
594preserve(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 */
613PUBLIC int
614unread(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 */
629PUBLIC int
630markread(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 */
646PUBLIC int
647messize(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*/
668PUBLIC int
669rexit(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 */
681PUBLIC int
682set(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 */
733PUBLIC int
734unset(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 */
773PUBLIC int
774show(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 */
807PUBLIC int
808group(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 */
865static int
866delgroup(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 */
896PUBLIC int
897unalias(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*/
910PUBLIC int
911null(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 */
920PUBLIC int
921file(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 */
939PUBLIC int
940echo(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 */
962static void
963push_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
972static int
973pop_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 */
991static int
992if_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
1003PUBLIC int
1004ifcmd(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
1038PUBLIC int
1039ifdefcmd(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
1053PUBLIC int
1054ifndefcmd(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*/
1067PUBLIC int
1068elsecmd(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*/
1086PUBLIC int
1087endifcmd(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 */
1101PUBLIC int
1102alternates(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