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