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