Home | History | Annotate | Line # | Download | only in mail
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
     35 static 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  */
     58 static int
     59 diction(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  */
     68 PUBLIC void
     69 sort(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  */
     84 static int
     85 bangexp(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)) {
     99 overf:
    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  */
    135 PUBLIC int
    136 shell(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*/
    158 PUBLIC int
    159 dosh(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*/
    177 PUBLIC int
    178 help(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  */
    196 PUBLIC int
    197 schdir(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  */
    217 static struct name *
    218 set_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  */
    252 static char *
    253 reedit(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  */
    273 static void
    274 set_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  */
    291 static 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  */
    362 static 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 
    399 PUBLIC int
    400 respond(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 
    409 PUBLIC int
    410 Respond(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  */
    423 PUBLIC int
    424 preserve(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  */
    442 PUBLIC int
    443 unread(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  */
    457 PUBLIC int
    458 markread(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  */
    473 PUBLIC int
    474 messize(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*/
    494 PUBLIC int
    495 rexit(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  */
    507 PUBLIC int
    508 set(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  */
    559 PUBLIC int
    560 unset(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  */
    599 PUBLIC int
    600 show(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  */
    633 PUBLIC int
    634 group(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  */
    691 static int
    692 delgroup(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  */
    722 PUBLIC int
    723 unalias(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*/
    736 PUBLIC int
    737 null(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  */
    746 PUBLIC int
    747 file(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  */
    765 PUBLIC int
    766 echo(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  */
    788 static void
    789 push_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 
    798 static int
    799 pop_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  */
    817 static int
    818 if_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 
    829 PUBLIC int
    830 ifcmd(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 
    864 PUBLIC int
    865 ifdefcmd(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 
    879 PUBLIC int
    880 ifndefcmd(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*/
    893 PUBLIC int
    894 elsecmd(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*/
    912 PUBLIC int
    913 endifcmd(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  */
    927 PUBLIC int
    928 alternates(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