cmd3.c revision 1.33
1/*	$NetBSD: cmd3.c,v 1.33 2006/11/28 18:45: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
35static char sccsid[] = "@(#)cmd3.c	8.2 (Berkeley) 4/20/95";
36#else
37__RCSID("$NetBSD: cmd3.c,v 1.33 2006/11/28 18:45:32 christos Exp $");
38#endif
39#endif /* not lint */
40
41#include "rcv.h"
42#include <util.h>
43#include "extern.h"
44#include "mime.h"
45#include "thread.h"
46
47/*
48 * Mail -- a mail program
49 *
50 * Still more user commands.
51 */
52
53
54/*
55 * Do a dictionary order comparison of the arguments from
56 * qsort.
57 */
58static int
59diction(const void *a, const void *b)
60{
61	return strcmp(*(const char *const *)a, *(const char *const *)b);
62}
63
64/*
65 * Sort the passed string vector into ascending dictionary
66 * order.
67 */
68PUBLIC void
69sort(const char **list)
70{
71	const char **ap;
72
73	for (ap = list; *ap != NULL; ap++)
74		continue;
75	if (ap-list < 2)
76		return;
77	qsort(list, (size_t)(ap-list), sizeof(*list), diction);
78}
79
80/*
81 * Expand the shell escape by expanding unescaped !'s into the
82 * last issued command where possible.
83 */
84static int
85bangexp(char *str)
86{
87	static char lastbang[128];
88	char bangbuf[LINESIZE];
89	char *cp, *cp2;
90	int n;
91	int changed = 0;
92
93	cp = str;
94	cp2 = bangbuf;
95	n = sizeof(bangbuf);	/* bytes left in bangbuf */
96	while (*cp) {
97		if (*cp == '!') {
98			if (n < (int)strlen(lastbang)) {
99overf:
100				(void)printf("Command buffer overflow\n");
101				return -1;
102			}
103			changed++;
104			(void)strcpy(cp2, lastbang);
105			cp2 += strlen(lastbang);
106			n -= strlen(lastbang);
107			cp++;
108			continue;
109		}
110		if (*cp == '\\' && cp[1] == '!') {
111			if (--n <= 1)
112				goto overf;
113			*cp2++ = '!';
114			cp += 2;
115			changed++;
116		}
117		if (--n <= 1)
118			goto overf;
119		*cp2++ = *cp++;
120	}
121	*cp2 = 0;
122	if (changed) {
123		(void)printf("!%s\n", bangbuf);
124		(void)fflush(stdout);
125	}
126	(void)strcpy(str, bangbuf);
127	(void)strlcpy(lastbang, bangbuf, sizeof(lastbang));
128	return 0;
129}
130
131/*
132 * Process a shell escape by saving signals, ignoring signals,
133 * and forking a sh -c
134 */
135PUBLIC int
136shell(void *v)
137{
138	char *str = v;
139	sig_t sigint = signal(SIGINT, SIG_IGN);
140	const char *shellcmd;
141	char cmd[LINESIZE];
142
143	(void)strcpy(cmd, str);
144	if (bangexp(cmd) < 0)
145		return 1;
146	if ((shellcmd = value(ENAME_SHELL)) == NULL)
147		shellcmd = _PATH_CSHELL;
148	(void)run_command(shellcmd, 0, 0, 1, "-c", cmd, NULL);
149	(void)signal(SIGINT, sigint);
150	(void)printf("!\n");
151	return 0;
152}
153
154/*
155 * Fork an interactive shell.
156 */
157/*ARGSUSED*/
158PUBLIC int
159dosh(void *v __unused)
160{
161	sig_t sigint = signal(SIGINT, SIG_IGN);
162	const char *shellcmd;
163
164	if ((shellcmd = value(ENAME_SHELL)) == NULL)
165		shellcmd = _PATH_CSHELL;
166	(void)run_command(shellcmd, 0, 0, 1, NULL);
167	(void)signal(SIGINT, sigint);
168	(void)putchar('\n');
169	return 0;
170}
171
172/*
173 * Print out a nice help message from some file or another.
174 */
175
176/*ARGSUSED*/
177PUBLIC int
178help(void *v __unused)
179{
180	int c;
181	FILE *f;
182
183	if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
184		warn(_PATH_HELP);
185		return 1;
186	}
187	while ((c = getc(f)) != EOF)
188		(void)putchar(c);
189	(void)Fclose(f);
190	return 0;
191}
192
193/*
194 * Change user's working directory.
195 */
196PUBLIC int
197schdir(void *v)
198{
199	char **arglist = v;
200	const char *cp;
201
202	if (*arglist == NULL)
203		cp = homedir;
204	else
205		if ((cp = expand(*arglist)) == NULL)
206			return 1;
207	if (chdir(cp) < 0) {
208		warn("%s", cp);
209		return 1;
210	}
211	return 0;
212}
213
214/*
215 * Return the smopts field if "ReplyAsRecipient" is defined.
216 */
217static struct name *
218set_smopts(struct message *mp)
219{
220	char *cp;
221	struct name *np = NULL;
222	char *reply_as_recipient = value(ENAME_REPLYASRECIPIENT);
223
224	if (reply_as_recipient &&
225	    (cp = skin(hfield("to", mp))) != NULL &&
226	    extract(cp, GTO)->n_flink == NULL) {  /* check for one recipient */
227		char *p, *q;
228		size_t len = strlen(cp);
229		/*
230		 * XXX - perhaps we always want to ignore
231		 *       "undisclosed-recipients:;" ?
232		 */
233		for (p = q = reply_as_recipient; *p; p = q) {
234			while (*q != '\0' && *q != ',' && !isblank((unsigned char)*q))
235				q++;
236			if (p + len == q && strncasecmp(cp, p, len) == 0)
237				return np;
238			while (*q == ',' || isblank((unsigned char)*q))
239				q++;
240		}
241		np = extract(__UNCONST("-f"), GSMOPTS);
242		np = cat(np, extract(cp, GSMOPTS));
243	}
244
245	return np;
246}
247
248/*
249 * Modify the subject we are replying to to begin with Re: if
250 * it does not already.
251 */
252static char *
253reedit(char *subj)
254{
255	char *newsubj;
256
257	if (subj == NULL)
258		return NULL;
259	if ((subj[0] == 'r' || subj[0] == 'R') &&
260	    (subj[1] == 'e' || subj[1] == 'E') &&
261	    subj[2] == ':')
262		return subj;
263	newsubj = salloc(strlen(subj) + 5);
264	(void)strcpy(newsubj, "Re: ");
265	(void)strcpy(newsubj + 4, subj);
266	return newsubj;
267}
268
269/*
270 * Set the "In-Reply-To" and "References" header fields appropriately.
271 * Used in replies.
272 */
273static void
274set_ident_fields(struct header *hp, struct message *mp)
275{
276	char *in_reply_to;
277	char *references;
278
279	in_reply_to = hfield("message-id", mp);
280	hp->h_in_reply_to = in_reply_to;
281
282	references = hfield("references", mp);
283	hp->h_references = extract(references, GMISC);
284	hp->h_references = cat(hp->h_references, extract(in_reply_to, GMISC));
285}
286
287/*
288 * Reply to a list of messages.  Extract each name from the
289 * message header and send them off to mail1()
290 */
291static int
292_respond(int *msgvec)
293{
294	struct message *mp;
295	char *cp, *rcv, *replyto;
296	char **ap;
297	struct name *np;
298	struct header head;
299
300	/* ensure that all header fields are initially NULL */
301	(void)memset(&head, 0, sizeof(head));
302
303	if (msgvec[1] != 0) {
304		(void)printf("Sorry, can't reply to multiple messages at once\n");
305		return 1;
306	}
307	mp = get_message(msgvec[0]);
308	touch(mp);
309	dot = mp;
310	if ((rcv = skin(hfield("from", mp))) == NULL)
311		rcv = skin(nameof(mp, 1));
312	if ((replyto = skin(hfield("reply-to", mp))) != NULL)
313		np = extract(replyto, GTO);
314	else if ((cp = skin(hfield("to", mp))) != NULL)
315		np = extract(cp, GTO);
316	else
317		np = NULL;
318	np = elide(np);
319	/*
320	 * Delete my name from the reply list,
321	 * and with it, all my alternate names.
322	 */
323	np = delname(np, myname);
324	if (altnames)
325		for (ap = altnames; *ap; ap++)
326			np = delname(np, *ap);
327	if (np != NULL && replyto == NULL)
328		np = cat(np, extract(rcv, GTO));
329	else if (np == NULL) {
330		if (replyto != NULL)
331			(void)printf("Empty reply-to field -- replying to author\n");
332		np = extract(rcv, GTO);
333	}
334	head.h_to = np;
335	if ((head.h_subject = hfield("subject", mp)) == NULL)
336		head.h_subject = hfield("subj", mp);
337	head.h_subject = reedit(head.h_subject);
338	if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
339		np = elide(extract(cp, GCC));
340		np = delname(np, myname);
341		if (altnames != 0)
342			for (ap = altnames; *ap; ap++)
343				np = delname(np, *ap);
344		head.h_cc = np;
345	} else
346		head.h_cc = NULL;
347	head.h_bcc = NULL;
348	head.h_smopts = set_smopts(mp);
349#ifdef MIME_SUPPORT
350	head.h_attach = NULL;
351#endif
352	set_ident_fields(&head, mp);
353	mail1(&head, 1);
354	return 0;
355}
356
357/*
358 * Reply to a series of messages by simply mailing to the senders
359 * and not messing around with the To: and Cc: lists as in normal
360 * reply.
361 */
362static int
363_Respond(int msgvec[])
364{
365	struct header head;
366	struct message *mp;
367	int *ap;
368	char *cp;
369
370	/* ensure that all header fields are initially NULL */
371	(void)memset(&head, 0, sizeof(head));
372
373	head.h_to = NULL;
374	for (ap = msgvec; *ap != 0; ap++) {
375		mp = get_message(*ap);
376		touch(mp);
377		dot = mp;
378		if ((cp = skin(hfield("from", mp))) == NULL)
379			cp = skin(nameof(mp, 2));
380		head.h_to = cat(head.h_to, extract(cp, GTO));
381	}
382	if (head.h_to == NULL)
383		return 0;
384	mp = get_message(msgvec[0]);
385	if ((head.h_subject = hfield("subject", mp)) == NULL)
386		head.h_subject = hfield("subj", mp);
387	head.h_subject = reedit(head.h_subject);
388	head.h_cc = NULL;
389	head.h_bcc = NULL;
390	head.h_smopts = set_smopts(mp);
391#ifdef MIME_SUPPORT
392	head.h_attach = NULL;
393#endif
394	set_ident_fields(&head, mp);
395	mail1(&head, 1);
396	return 0;
397}
398
399PUBLIC int
400respond(void *v)
401{
402	int *msgvec = v;
403	if (value(ENAME_REPLYALL) == NULL)
404		return _respond(msgvec);
405	else
406		return _Respond(msgvec);
407}
408
409PUBLIC int
410Respond(void *v)
411{
412	int *msgvec = v;
413	if (value(ENAME_REPLYALL) == NULL)
414		return _Respond(msgvec);
415	else
416		return _respond(msgvec);
417}
418
419/*
420 * Preserve the named messages, so that they will be sent
421 * back to the system mailbox.
422 */
423PUBLIC int
424preserve(void *v)
425{
426	int *msgvec = v;
427	int *ip;
428
429	if (edit) {
430		(void)printf("Cannot \"preserve\" in edit mode\n");
431		return 1;
432	}
433	for (ip = msgvec; *ip != 0; ip++)
434		dot = set_m_flag(*ip, ~(MBOX | MPRESERVE), MPRESERVE);
435
436	return 0;
437}
438
439/*
440 * Mark all given messages as unread, preserving the new status.
441 */
442PUBLIC int
443unread(void *v)
444{
445	int *msgvec = v;
446	int *ip;
447
448	for (ip = msgvec; *ip != 0; ip++)
449		dot = set_m_flag(*ip, ~(MREAD | MTOUCH | MSTATUS), MSTATUS);
450
451	return 0;
452}
453
454/*
455 * Mark all given messages as read.
456 */
457PUBLIC int
458markread(void *v)
459{
460	int *msgvec = v;
461	int *ip;
462
463	for (ip = msgvec; *ip != 0; ip++)
464		dot = set_m_flag(*ip,
465		    ~(MNEW | MTOUCH | MREAD | MSTATUS), MREAD | MSTATUS);
466
467	return 0;
468}
469
470/*
471 * Print the size of each message.
472 */
473PUBLIC int
474messize(void *v)
475{
476	int *msgvec = v;
477	struct message *mp;
478	int *ip, mesg;
479
480	for (ip = msgvec; *ip != 0; ip++) {
481		mesg = *ip;
482		mp = get_message(mesg);
483		(void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
484		    (unsigned long long)mp->m_size);
485	}
486	return 0;
487}
488
489/*
490 * Quit quickly.  If we are sourcing, just pop the input level
491 * by returning an error.
492 */
493/*ARGSUSED*/
494PUBLIC int
495rexit(void *v __unused)
496{
497	if (sourcing)
498		return 1;
499	exit(0);
500	/*NOTREACHED*/
501}
502
503/*
504 * Set or display a variable value.  Syntax is similar to that
505 * of csh.
506 */
507PUBLIC int
508set(void *v)
509{
510	const char **arglist = v;
511	struct var *vp;
512	const char *cp;
513	char varbuf[LINESIZE];
514	const char **ap, **p;
515	int errs, h, s;
516	size_t l;
517
518	if (*arglist == NULL) {
519		for (h = 0, s = 1; h < HSHSIZE; h++)
520			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
521				s++;
522		ap = salloc(s * sizeof *ap);
523		for (h = 0, p = ap; h < HSHSIZE; h++)
524			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
525				*p++ = vp->v_name;
526		*p = NULL;
527		sort(ap);
528		for (p = ap; *p != NULL; p++)
529			(void)printf("%s\t%s\n", *p, value(*p));
530		return 0;
531	}
532	errs = 0;
533	for (ap = arglist; *ap != NULL; ap++) {
534		cp = *ap;
535		while (*cp != '=' && *cp != '\0')
536			++cp;
537		l = cp - *ap;
538		if (l >= sizeof varbuf)
539			l = sizeof(varbuf) - 1;
540		(void)strncpy(varbuf, *ap, l);
541		varbuf[l] = '\0';
542		if (*cp == '\0')
543			cp = "";
544		else
545			cp++;
546		if (equal(varbuf, "")) {
547			(void)printf("Non-null variable name required\n");
548			errs++;
549			continue;
550		}
551		assign(varbuf, cp);
552	}
553	return errs;
554}
555
556/*
557 * Unset a bunch of variable values.
558 */
559PUBLIC int
560unset(void *v)
561{
562	char **arglist = v;
563	struct var *vp, *vp2;
564	int errs, h;
565	char **ap;
566
567	errs = 0;
568	for (ap = arglist; *ap != NULL; ap++) {
569		if ((vp2 = lookup(*ap)) == NULL) {
570			if (getenv(*ap)) {
571				(void)unsetenv(*ap);
572			} else if (!sourcing) {
573				(void)printf("\"%s\": undefined variable\n", *ap);
574				errs++;
575			}
576			continue;
577		}
578		h = hash(*ap);
579		if (vp2 == variables[h]) {
580			variables[h] = variables[h]->v_link;
581			v_free(vp2->v_name);
582                        v_free(vp2->v_value);
583			free(vp2);
584			continue;
585		}
586		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
587			continue;
588		vp->v_link = vp2->v_link;
589                v_free(vp2->v_name);
590                v_free(vp2->v_value);
591		free(vp2);
592	}
593	return errs;
594}
595
596/*
597 * Show a variable value.
598 */
599PUBLIC int
600show(void *v)
601{
602	const char **arglist = v;
603	struct var *vp;
604	const char **ap, **p;
605	int h, s;
606
607	if (*arglist == NULL) {
608		for (h = 0, s = 1; h < HSHSIZE; h++)
609			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
610				s++;
611		ap = salloc(s * sizeof *ap);
612		for (h = 0, p = ap; h < HSHSIZE; h++)
613			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
614				*p++ = vp->v_name;
615		*p = NULL;
616		sort(ap);
617		for (p = ap; *p != NULL; p++)
618			(void)printf("%s=%s\n", *p, value(*p));
619		return 0;
620	}
621
622	for (ap = arglist; *ap != NULL; ap++) {
623		char *val = value(*ap);
624		(void)printf("%s=%s\n", *ap, val ? val : "<null>");
625	}
626	return 0;
627}
628
629
630/*
631 * Put add users to a group.
632 */
633PUBLIC int
634group(void *v)
635{
636	const char **argv = v;
637	struct grouphead *gh;
638	struct group *gp;
639	int h;
640	int s;
641	const char *gname;
642	const char **ap, **p;
643
644	if (*argv == NULL) {
645		for (h = 0, s = 1; h < HSHSIZE; h++)
646			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
647				s++;
648		ap = salloc(s * sizeof *ap);
649		for (h = 0, p = ap; h < HSHSIZE; h++)
650			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
651				*p++ = gh->g_name;
652		*p = NULL;
653		sort(ap);
654		for (p = ap; *p != NULL; p++)
655			printgroup(*p);
656		return 0;
657	}
658	if (argv[1] == NULL) {
659		printgroup(*argv);
660		return 0;
661	}
662	gname = *argv;
663	h = hash(gname);
664	if ((gh = findgroup(gname)) == NULL) {
665		gh = (struct grouphead *) ecalloc(1, sizeof *gh);
666		gh->g_name = vcopy(gname);
667		gh->g_list = NULL;
668		gh->g_link = groups[h];
669		groups[h] = gh;
670	}
671
672	/*
673	 * Insert names from the command list into the group.
674	 * Who cares if there are duplicates?  They get tossed
675	 * later anyway.
676	 */
677
678	for (ap = argv + 1; *ap != NULL; ap++) {
679		gp = (struct group *) ecalloc(1, sizeof *gp);
680		gp->ge_name = vcopy(*ap);
681		gp->ge_link = gh->g_list;
682		gh->g_list = gp;
683	}
684	return 0;
685}
686
687/*
688 * Delete the named group alias. Return zero if the group was
689 * successfully deleted, or -1 if there was no such group.
690 */
691static int
692delgroup(const char *name)
693{
694	struct grouphead *gh, *p;
695	struct group *g;
696	int h;
697
698	h = hash(name);
699	for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
700		if (strcmp(gh->g_name, name) == 0) {
701			if (p == NULL)
702				groups[h] = gh->g_link;
703			else
704				p->g_link = gh->g_link;
705			while (gh->g_list != NULL) {
706				g = gh->g_list;
707				gh->g_list = g->ge_link;
708				free(g->ge_name);
709				free(g);
710			}
711			free(gh->g_name);
712			free(gh);
713			return 0;
714		}
715	return -1;
716}
717
718/*
719 * The unalias command takes a list of alises
720 * and discards the remembered groups of users.
721 */
722PUBLIC int
723unalias(void *v)
724{
725	char **ap;
726
727	for (ap = v; *ap != NULL; ap++)
728		(void)delgroup(*ap);
729	return 0;
730}
731
732/*
733 * The do nothing command for comments.
734 */
735/*ARGSUSED*/
736PUBLIC int
737null(void *v __unused)
738{
739	return 0;
740}
741
742/*
743 * Change to another file.  With no argument, print information about
744 * the current file.
745 */
746PUBLIC int
747file(void *v)
748{
749	char **argv = v;
750
751	if (argv[0] == NULL) {
752		(void)newfileinfo(0);
753		return 0;
754	}
755	if (setfile(*argv) < 0)
756		return 1;
757	announce();
758
759	return 0;
760}
761
762/*
763 * Expand file names like echo
764 */
765PUBLIC int
766echo(void *v)
767{
768	char **argv = v;
769	char **ap;
770	const char *cp;
771
772	for (ap = argv; *ap != NULL; ap++) {
773		cp = *ap;
774		if ((cp = expand(cp)) != NULL) {
775			if (ap != argv)
776				(void)putchar(' ');
777			(void)printf("%s", cp);
778		}
779	}
780	(void)putchar('\n');
781	return 0;
782}
783
784/*
785 * Routines to push and pop the condition code to support nested
786 * if/else/endif statements.
787 */
788static void
789push_cond(int c_cond)
790{
791	struct cond_stack_s *csp;
792	csp = emalloc(sizeof(*csp));
793	csp->c_cond = c_cond;
794	csp->c_next = cond_stack;
795	cond_stack = csp;
796}
797
798static int
799pop_cond(void)
800{
801	int c_cond;
802	struct cond_stack_s *csp;
803
804	if ((csp = cond_stack) == NULL)
805		return -1;
806
807	c_cond = csp->c_cond;
808	cond_stack = csp->c_next;
809	free(csp);
810	return c_cond;
811}
812
813/*
814 * Conditional commands.  These allow one to parameterize one's
815 * .mailrc and do some things if sending, others if receiving.
816 */
817static int
818if_push(void)
819{
820	push_cond(cond);
821	cond &= ~CELSE;
822	if ((cond & (CIF | CSKIP)) == (CIF | CSKIP)) {
823		cond |= CIGN;
824		return 1;
825	}
826	return 0;
827}
828
829PUBLIC int
830ifcmd(void *v)
831{
832	char **argv = v;
833	char *keyword = argv[0];
834	static const struct modetbl_s {
835		const char *m_name;
836		enum mailmode_e m_mode;
837	} modetbl[] = {
838		{ "receiving",		mm_receiving },
839		{ "sending",		mm_sending },
840		{ "headersonly",	mm_hdrsonly },
841		{ NULL,			0 },
842	};
843	const struct modetbl_s *mtp;
844
845	if (if_push())
846		return 0;
847
848	cond = CIF;
849	for (mtp = modetbl; mtp->m_name; mtp++)
850		if (strcasecmp(keyword, mtp->m_name) == 0)
851			break;
852
853	if (mtp->m_name == NULL) {
854		cond = CNONE;
855		(void)printf("Unrecognized if-keyword: \"%s\"\n", keyword);
856		return 1;
857	}
858	if (mtp->m_mode != mailmode)
859		cond |= CSKIP;
860
861	return 0;
862}
863
864PUBLIC int
865ifdefcmd(void *v)
866{
867	char **argv = v;
868
869	if (if_push())
870		return 0;
871
872	cond = CIF;
873	if (value(argv[0]) == NULL)
874		cond |= CSKIP;
875
876	return 0;
877}
878
879PUBLIC int
880ifndefcmd(void *v)
881{
882	int rval;
883	rval = ifdefcmd(v);
884	cond ^= CSKIP;
885	return rval;
886}
887
888/*
889 * Implement 'else'.  This is pretty simple -- we just
890 * flip over the conditional flag.
891 */
892/*ARGSUSED*/
893PUBLIC int
894elsecmd(void *v __unused)
895{
896	if (cond_stack == NULL || (cond & (CIF | CELSE)) != CIF) {
897		(void)printf("\"else\" without matching \"if\"\n");
898		cond = CNONE;
899		return 1;
900	}
901	if ((cond & CIGN) == 0) {
902		cond ^= CSKIP;
903		cond |= CELSE;
904	}
905	return 0;
906}
907
908/*
909 * End of if statement.  Just set cond back to anything.
910 */
911/*ARGSUSED*/
912PUBLIC int
913endifcmd(void *v __unused)
914{
915	if (cond_stack == NULL || (cond & CIF) != CIF) {
916		(void)printf("\"endif\" without matching \"if\"\n");
917		cond = CNONE;
918		return 1;
919	}
920	cond = pop_cond();
921	return 0;
922}
923
924/*
925 * Set the list of alternate names.
926 */
927PUBLIC int
928alternates(void *v)
929{
930	char **namelist = v;
931	int c;
932	char **ap, **ap2, *cp;
933
934	c = argcount(namelist) + 1;
935	if (c == 1) {
936		if (altnames == 0)
937			return 0;
938		for (ap = altnames; *ap; ap++)
939			(void)printf("%s ", *ap);
940		(void)printf("\n");
941		return 0;
942	}
943	if (altnames != 0)
944		free(altnames);
945	altnames = (char **) ecalloc((unsigned) c, sizeof (char *));
946	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
947		cp = ecalloc((unsigned) strlen(*ap) + 1, sizeof (char));
948		(void)strcpy(cp, *ap);
949		*ap2 = cp;
950	}
951	*ap2 = 0;
952	return 0;
953}
954