xref: /netbsd-src/usr.bin/mail/cmd3.c (revision ce099b40997c43048fb78bd578195f81d2456523)
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