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