Home | History | Annotate | Line # | Download | only in mail
cmd3.c revision 1.37
      1 /*	$NetBSD: cmd3.c,v 1.37 2007/10/29 23:20:37 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.37 2007/10/29 23:20:37 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_core(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_core(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_core(msgvec);
    396 	else
    397 		return Respond_core(msgvec);
    398 }
    399 
    400 PUBLIC int
    401 Respond(void *v)
    402 {
    403 	int *msgvec = v;
    404 	if (value(ENAME_REPLYALL) == NULL)
    405 		return Respond_core(msgvec);
    406 	else
    407 		return respond_core(msgvec);
    408 }
    409 
    410 PUBLIC int
    411 forward(void *v)
    412 {
    413 	int *msgvec = v;
    414 	int *ip;
    415 	for ( ip = msgvec; *ip; ip++)
    416 		(void)printf("%d ", *ip);
    417 	(void)printf("forward not supported yet\n");
    418 	return 0;
    419 }
    420 
    421 static int
    422 bounce_one(int msgno, const char **smargs, struct name *h_to)
    423 {
    424 	char mailtempname[PATHSIZE];
    425 	struct message *mp;
    426 	int fd;
    427 	FILE *obuf;
    428 	int rval;
    429 
    430 	rval = 0;
    431 
    432 	obuf = NULL;
    433 	(void)snprintf(mailtempname, sizeof(mailtempname),
    434 	    "%s/mail.RsXXXXXXXXXX", tmpdir);
    435 	if ((fd = mkstemp(mailtempname)) == -1 ||
    436 	    (obuf = Fdopen(fd, "w+")) == NULL) {
    437 		if (fd != -1)
    438 			(void)close(fd);
    439 		warn("%s", mailtempname);
    440 		rval = 1;
    441 		goto done;
    442 	}
    443 	(void)rm(mailtempname);
    444 
    445 	mp = get_message(msgno);
    446 
    447 	if (mp == NULL) {
    448 		(void)printf("no such message %d\n", msgno);
    449 		rval = 1;
    450 		goto done;
    451 	}
    452 	else {
    453 		char *cp;
    454 		char **ap;
    455 		struct name *np;
    456 		struct header hdr;
    457 
    458 		/*
    459 		 * Construct and output a new "To:" field:
    460 		 * Remove our address from anything in the old "To:" field
    461 		 * and append that list to the bounce address(es).
    462 		 */
    463 		np = NULL;
    464 		if ((cp = skin(hfield("to", mp))) != NULL)
    465 			np = extract(cp, GTO);
    466 		np = delname(np, myname);
    467 		if (altnames)
    468 			for (ap = altnames; *ap; ap++)
    469 				np = delname(np, *ap);
    470 		np = cat(h_to, np);
    471 		(void)memset(&hdr, 0, sizeof(hdr));
    472 		hdr.h_to = elide(np);
    473 		(void)puthead(&hdr, obuf, GTO | GCOMMA);
    474 	}
    475 	if (sendmessage(mp, obuf, bouncetab, NULL, NULL)) {
    476 		(void)printf("bounce failed for message %d\n", msgno);
    477 		rval = 1;
    478 		goto done;
    479 	}
    480 	rewind(obuf);	/* XXX - here or inside mail2() */
    481 	mail2(obuf, smargs);
    482  done:
    483 	if (obuf)
    484 		(void)Fclose(obuf);
    485 	return rval;
    486 }
    487 
    488 PUBLIC int
    489 bounce(void *v)
    490 {
    491 	int *msgvec = v;
    492 	int *ip;
    493 	const char **smargs;
    494 	struct header hdr;
    495 	int rval;
    496 
    497 	if (bouncetab[0].i_count == 0) {
    498 		/* setup the bounce tab */
    499 		add_ignore("Status", bouncetab);
    500 		add_ignore("Delivered-To", bouncetab);
    501 		add_ignore("To", bouncetab);
    502 		add_ignore("X-Original-To", bouncetab);
    503 	}
    504 	(void)memset(&hdr, 0, sizeof(hdr));
    505 	if ((rval = grabh(&hdr, GTO)) != 0)
    506 		return rval;
    507 
    508 	if (hdr.h_to == NULL)
    509 		return 1;
    510 
    511 	smargs = unpack(hdr.h_to);
    512 	for ( ip = msgvec; *ip; ip++) {
    513 		int e;
    514 		if ((e = bounce_one(*ip, smargs, hdr.h_to)) != 0)
    515 			return e;
    516 	}
    517 	return 0;
    518 }
    519 
    520 /*
    521  * Preserve the named messages, so that they will be sent
    522  * back to the system mailbox.
    523  */
    524 PUBLIC int
    525 preserve(void *v)
    526 {
    527 	int *msgvec = v;
    528 	int *ip;
    529 
    530 	if (edit) {
    531 		(void)printf("Cannot \"preserve\" in edit mode\n");
    532 		return 1;
    533 	}
    534 	for (ip = msgvec; *ip != 0; ip++)
    535 		dot = set_m_flag(*ip, ~(MBOX | MPRESERVE), MPRESERVE);
    536 
    537 	return 0;
    538 }
    539 
    540 /*
    541  * Mark all given messages as unread, preserving the new status.
    542  */
    543 PUBLIC int
    544 unread(void *v)
    545 {
    546 	int *msgvec = v;
    547 	int *ip;
    548 
    549 	for (ip = msgvec; *ip != 0; ip++)
    550 		dot = set_m_flag(*ip, ~(MREAD | MTOUCH | MSTATUS), MSTATUS);
    551 
    552 	return 0;
    553 }
    554 
    555 /*
    556  * Mark all given messages as read.
    557  */
    558 PUBLIC int
    559 markread(void *v)
    560 {
    561 	int *msgvec = v;
    562 	int *ip;
    563 
    564 	for (ip = msgvec; *ip != 0; ip++)
    565 		dot = set_m_flag(*ip,
    566 		    ~(MNEW | MTOUCH | MREAD | MSTATUS), MREAD | MSTATUS);
    567 
    568 	return 0;
    569 }
    570 
    571 /*
    572  * Print the size of each message.
    573  */
    574 PUBLIC int
    575 messize(void *v)
    576 {
    577 	int *msgvec = v;
    578 	struct message *mp;
    579 	int *ip, mesg;
    580 
    581 	for (ip = msgvec; *ip != 0; ip++) {
    582 		mesg = *ip;
    583 		mp = get_message(mesg);
    584 		(void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
    585 		    (unsigned long long)mp->m_size);
    586 	}
    587 	return 0;
    588 }
    589 
    590 /*
    591  * Quit quickly.  If we are sourcing, just pop the input level
    592  * by returning an error.
    593  */
    594 /*ARGSUSED*/
    595 PUBLIC int
    596 rexit(void *v __unused)
    597 {
    598 	if (sourcing)
    599 		return 1;
    600 	exit(0);
    601 	/*NOTREACHED*/
    602 }
    603 
    604 /*
    605  * Set or display a variable value.  Syntax is similar to that
    606  * of csh.
    607  */
    608 PUBLIC int
    609 set(void *v)
    610 {
    611 	const char **arglist = v;
    612 	struct var *vp;
    613 	const char *cp;
    614 	char varbuf[LINESIZE];
    615 	const char **ap, **p;
    616 	int errs, h, s;
    617 	size_t l;
    618 
    619 	if (*arglist == NULL) {
    620 		for (h = 0, s = 1; h < HSHSIZE; h++)
    621 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
    622 				s++;
    623 		ap = salloc(s * sizeof(*ap));
    624 		for (h = 0, p = ap; h < HSHSIZE; h++)
    625 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
    626 				*p++ = vp->v_name;
    627 		*p = NULL;
    628 		sort(ap);
    629 		for (p = ap; *p != NULL; p++)
    630 			(void)printf("%s\t%s\n", *p, value(*p));
    631 		return 0;
    632 	}
    633 	errs = 0;
    634 	for (ap = arglist; *ap != NULL; ap++) {
    635 		cp = *ap;
    636 		while (*cp != '=' && *cp != '\0')
    637 			++cp;
    638 		l = cp - *ap;
    639 		if (l >= sizeof(varbuf))
    640 			l = sizeof(varbuf) - 1;
    641 		(void)strncpy(varbuf, *ap, l);
    642 		varbuf[l] = '\0';
    643 		if (*cp == '\0')
    644 			cp = "";
    645 		else
    646 			cp++;
    647 		if (equal(varbuf, "")) {
    648 			(void)printf("Non-null variable name required\n");
    649 			errs++;
    650 			continue;
    651 		}
    652 		assign(varbuf, cp);
    653 	}
    654 	return errs;
    655 }
    656 
    657 /*
    658  * Unset a bunch of variable values.
    659  */
    660 PUBLIC int
    661 unset(void *v)
    662 {
    663 	char **arglist = v;
    664 	struct var *vp, *vp2;
    665 	int errs, h;
    666 	char **ap;
    667 
    668 	errs = 0;
    669 	for (ap = arglist; *ap != NULL; ap++) {
    670 		if ((vp2 = lookup(*ap)) == NULL) {
    671 			if (getenv(*ap)) {
    672 				(void)unsetenv(*ap);
    673 			} else if (!sourcing) {
    674 				(void)printf("\"%s\": undefined variable\n", *ap);
    675 				errs++;
    676 			}
    677 			continue;
    678 		}
    679 		h = hash(*ap);
    680 		if (vp2 == variables[h]) {
    681 			variables[h] = variables[h]->v_link;
    682 			v_free(vp2->v_name);
    683                         v_free(vp2->v_value);
    684 			free(vp2);
    685 			continue;
    686 		}
    687 		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
    688 			continue;
    689 		vp->v_link = vp2->v_link;
    690                 v_free(vp2->v_name);
    691                 v_free(vp2->v_value);
    692 		free(vp2);
    693 	}
    694 	return errs;
    695 }
    696 
    697 /*
    698  * Show a variable value.
    699  */
    700 PUBLIC int
    701 show(void *v)
    702 {
    703 	const char **arglist = v;
    704 	struct var *vp;
    705 	const char **ap, **p;
    706 	int h, s;
    707 
    708 	if (*arglist == NULL) {
    709 		for (h = 0, s = 1; h < HSHSIZE; h++)
    710 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
    711 				s++;
    712 		ap = salloc(s * sizeof(*ap));
    713 		for (h = 0, p = ap; h < HSHSIZE; h++)
    714 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
    715 				*p++ = vp->v_name;
    716 		*p = NULL;
    717 		sort(ap);
    718 		for (p = ap; *p != NULL; p++)
    719 			(void)printf("%s=%s\n", *p, value(*p));
    720 		return 0;
    721 	}
    722 
    723 	for (ap = arglist; *ap != NULL; ap++) {
    724 		char *val = value(*ap);
    725 		(void)printf("%s=%s\n", *ap, val ? val : "<null>");
    726 	}
    727 	return 0;
    728 }
    729 
    730 
    731 /*
    732  * Put add users to a group.
    733  */
    734 PUBLIC int
    735 group(void *v)
    736 {
    737 	const char **argv = v;
    738 	struct grouphead *gh;
    739 	struct group *gp;
    740 	int h;
    741 	int s;
    742 	const char *gname;
    743 	const char **ap, **p;
    744 
    745 	if (*argv == NULL) {
    746 		for (h = 0, s = 1; h < HSHSIZE; h++)
    747 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
    748 				s++;
    749 		ap = salloc(s * sizeof(*ap));
    750 		for (h = 0, p = ap; h < HSHSIZE; h++)
    751 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
    752 				*p++ = gh->g_name;
    753 		*p = NULL;
    754 		sort(ap);
    755 		for (p = ap; *p != NULL; p++)
    756 			printgroup(*p);
    757 		return 0;
    758 	}
    759 	if (argv[1] == NULL) {
    760 		printgroup(*argv);
    761 		return 0;
    762 	}
    763 	gname = *argv;
    764 	h = hash(gname);
    765 	if ((gh = findgroup(gname)) == NULL) {
    766 		gh = ecalloc(1, sizeof(*gh));
    767 		gh->g_name = vcopy(gname);
    768 		gh->g_list = NULL;
    769 		gh->g_link = groups[h];
    770 		groups[h] = gh;
    771 	}
    772 
    773 	/*
    774 	 * Insert names from the command list into the group.
    775 	 * Who cares if there are duplicates?  They get tossed
    776 	 * later anyway.
    777 	 */
    778 
    779 	for (ap = argv + 1; *ap != NULL; ap++) {
    780 		gp = ecalloc(1, sizeof(*gp));
    781 		gp->ge_name = vcopy(*ap);
    782 		gp->ge_link = gh->g_list;
    783 		gh->g_list = gp;
    784 	}
    785 	return 0;
    786 }
    787 
    788 /*
    789  * Delete the named group alias. Return zero if the group was
    790  * successfully deleted, or -1 if there was no such group.
    791  */
    792 static int
    793 delgroup(const char *name)
    794 {
    795 	struct grouphead *gh, *p;
    796 	struct group *g;
    797 	int h;
    798 
    799 	h = hash(name);
    800 	for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
    801 		if (strcmp(gh->g_name, name) == 0) {
    802 			if (p == NULL)
    803 				groups[h] = gh->g_link;
    804 			else
    805 				p->g_link = gh->g_link;
    806 			while (gh->g_list != NULL) {
    807 				g = gh->g_list;
    808 				gh->g_list = g->ge_link;
    809 				free(g->ge_name);
    810 				free(g);
    811 			}
    812 			free(gh->g_name);
    813 			free(gh);
    814 			return 0;
    815 		}
    816 	return -1;
    817 }
    818 
    819 /*
    820  * The unalias command takes a list of alises
    821  * and discards the remembered groups of users.
    822  */
    823 PUBLIC int
    824 unalias(void *v)
    825 {
    826 	char **ap;
    827 
    828 	for (ap = v; *ap != NULL; ap++)
    829 		(void)delgroup(*ap);
    830 	return 0;
    831 }
    832 
    833 /*
    834  * The do nothing command for comments.
    835  */
    836 /*ARGSUSED*/
    837 PUBLIC int
    838 null(void *v __unused)
    839 {
    840 	return 0;
    841 }
    842 
    843 /*
    844  * Change to another file.  With no argument, print information about
    845  * the current file.
    846  */
    847 PUBLIC int
    848 file(void *v)
    849 {
    850 	char **argv = v;
    851 
    852 	if (argv[0] == NULL) {
    853 		(void)newfileinfo(0);
    854 		return 0;
    855 	}
    856 	if (setfile(*argv) < 0)
    857 		return 1;
    858 	announce();
    859 
    860 	return 0;
    861 }
    862 
    863 /*
    864  * Expand file names like echo
    865  */
    866 PUBLIC int
    867 echo(void *v)
    868 {
    869 	char **argv = v;
    870 	char **ap;
    871 	const char *cp;
    872 
    873 	for (ap = argv; *ap != NULL; ap++) {
    874 		cp = *ap;
    875 		if ((cp = expand(cp)) != NULL) {
    876 			if (ap != argv)
    877 				(void)putchar(' ');
    878 			(void)printf("%s", cp);
    879 		}
    880 	}
    881 	(void)putchar('\n');
    882 	return 0;
    883 }
    884 
    885 /*
    886  * Routines to push and pop the condition code to support nested
    887  * if/else/endif statements.
    888  */
    889 static void
    890 push_cond(int c_cond)
    891 {
    892 	struct cond_stack_s *csp;
    893 	csp = emalloc(sizeof(*csp));
    894 	csp->c_cond = c_cond;
    895 	csp->c_next = cond_stack;
    896 	cond_stack = csp;
    897 }
    898 
    899 static int
    900 pop_cond(void)
    901 {
    902 	int c_cond;
    903 	struct cond_stack_s *csp;
    904 
    905 	if ((csp = cond_stack) == NULL)
    906 		return -1;
    907 
    908 	c_cond = csp->c_cond;
    909 	cond_stack = csp->c_next;
    910 	free(csp);
    911 	return c_cond;
    912 }
    913 
    914 /*
    915  * Conditional commands.  These allow one to parameterize one's
    916  * .mailrc and do some things if sending, others if receiving.
    917  */
    918 static int
    919 if_push(void)
    920 {
    921 	push_cond(cond);
    922 	cond &= ~CELSE;
    923 	if ((cond & (CIF | CSKIP)) == (CIF | CSKIP)) {
    924 		cond |= CIGN;
    925 		return 1;
    926 	}
    927 	return 0;
    928 }
    929 
    930 PUBLIC int
    931 ifcmd(void *v)
    932 {
    933 	char **argv = v;
    934 	char *keyword = argv[0];
    935 	static const struct modetbl_s {
    936 		const char *m_name;
    937 		enum mailmode_e m_mode;
    938 	} modetbl[] = {
    939 		{ "receiving",		mm_receiving },
    940 		{ "sending",		mm_sending },
    941 		{ "headersonly",	mm_hdrsonly },
    942 		{ NULL,			0 },
    943 	};
    944 	const struct modetbl_s *mtp;
    945 
    946 	if (if_push())
    947 		return 0;
    948 
    949 	cond = CIF;
    950 	for (mtp = modetbl; mtp->m_name; mtp++)
    951 		if (strcasecmp(keyword, mtp->m_name) == 0)
    952 			break;
    953 
    954 	if (mtp->m_name == NULL) {
    955 		cond = CNONE;
    956 		(void)printf("Unrecognized if-keyword: \"%s\"\n", keyword);
    957 		return 1;
    958 	}
    959 	if (mtp->m_mode != mailmode)
    960 		cond |= CSKIP;
    961 
    962 	return 0;
    963 }
    964 
    965 PUBLIC int
    966 ifdefcmd(void *v)
    967 {
    968 	char **argv = v;
    969 
    970 	if (if_push())
    971 		return 0;
    972 
    973 	cond = CIF;
    974 	if (value(argv[0]) == NULL)
    975 		cond |= CSKIP;
    976 
    977 	return 0;
    978 }
    979 
    980 PUBLIC int
    981 ifndefcmd(void *v)
    982 {
    983 	int rval;
    984 	rval = ifdefcmd(v);
    985 	cond ^= CSKIP;
    986 	return rval;
    987 }
    988 
    989 /*
    990  * Implement 'else'.  This is pretty simple -- we just
    991  * flip over the conditional flag.
    992  */
    993 /*ARGSUSED*/
    994 PUBLIC int
    995 elsecmd(void *v __unused)
    996 {
    997 	if (cond_stack == NULL || (cond & (CIF | CELSE)) != CIF) {
    998 		(void)printf("\"else\" without matching \"if\"\n");
    999 		cond = CNONE;
   1000 		return 1;
   1001 	}
   1002 	if ((cond & CIGN) == 0) {
   1003 		cond ^= CSKIP;
   1004 		cond |= CELSE;
   1005 	}
   1006 	return 0;
   1007 }
   1008 
   1009 /*
   1010  * End of if statement.  Just set cond back to anything.
   1011  */
   1012 /*ARGSUSED*/
   1013 PUBLIC int
   1014 endifcmd(void *v __unused)
   1015 {
   1016 	if (cond_stack == NULL || (cond & CIF) != CIF) {
   1017 		(void)printf("\"endif\" without matching \"if\"\n");
   1018 		cond = CNONE;
   1019 		return 1;
   1020 	}
   1021 	cond = pop_cond();
   1022 	return 0;
   1023 }
   1024 
   1025 /*
   1026  * Set the list of alternate names.
   1027  */
   1028 PUBLIC int
   1029 alternates(void *v)
   1030 {
   1031 	char **namelist = v;
   1032 	size_t c;
   1033 	char **ap, **ap2, *cp;
   1034 
   1035 	c = argcount(namelist) + 1;
   1036 	if (c == 1) {
   1037 		if (altnames == 0)
   1038 			return 0;
   1039 		for (ap = altnames; *ap; ap++)
   1040 			(void)printf("%s ", *ap);
   1041 		(void)printf("\n");
   1042 		return 0;
   1043 	}
   1044 	if (altnames != 0)
   1045 		free(altnames);
   1046 	altnames = ecalloc(c, sizeof(char *));
   1047 	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
   1048 		cp = ecalloc(strlen(*ap) + 1, sizeof(char));
   1049 		(void)strcpy(cp, *ap);
   1050 		*ap2 = cp;
   1051 	}
   1052 	*ap2 = 0;
   1053 	return 0;
   1054 }
   1055