Home | History | Annotate | Line # | Download | only in mail
cmd3.c revision 1.35
      1 /*	$NetBSD: cmd3.c,v 1.35 2007/10/23 14:58:43 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.35 2007/10/23 14:58:43 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 	cathelp(_PATH_HELP);
    181 	return 0;
    182 }
    183 
    184 /*
    185  * Change user's working directory.
    186  */
    187 PUBLIC int
    188 schdir(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  */
    208 static struct name *
    209 set_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 != ',' && !is_WSP(*q))
    226 				q++;
    227 			if (p + len == q && strncasecmp(cp, p, len) == 0)
    228 				return np;
    229 			while (*q == ',' || is_WSP(*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  */
    243 static char *
    244 reedit(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  */
    264 static void
    265 set_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  */
    282 static 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  */
    353 static 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 
    390 PUBLIC int
    391 respond(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 
    400 PUBLIC int
    401 Respond(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  */
    414 PUBLIC int
    415 preserve(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  */
    433 PUBLIC int
    434 unread(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  */
    448 PUBLIC int
    449 markread(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  */
    464 PUBLIC int
    465 messize(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*/
    485 PUBLIC int
    486 rexit(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  */
    498 PUBLIC int
    499 set(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  */
    550 PUBLIC int
    551 unset(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  */
    590 PUBLIC int
    591 show(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  */
    624 PUBLIC int
    625 group(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  */
    682 static int
    683 delgroup(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  */
    713 PUBLIC int
    714 unalias(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*/
    727 PUBLIC int
    728 null(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  */
    737 PUBLIC int
    738 file(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  */
    756 PUBLIC int
    757 echo(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  */
    779 static void
    780 push_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 
    789 static int
    790 pop_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  */
    808 static int
    809 if_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 
    820 PUBLIC int
    821 ifcmd(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 
    855 PUBLIC int
    856 ifdefcmd(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 
    870 PUBLIC int
    871 ifndefcmd(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*/
    884 PUBLIC int
    885 elsecmd(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*/
    903 PUBLIC int
    904 endifcmd(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  */
    918 PUBLIC int
    919 alternates(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