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