xref: /netbsd-src/usr.bin/mail/cmd3.c (revision 503611ba29d4c920cb1878a9ece7ebd1e0ac2e16)
1 /*	$NetBSD: cmd3.c,v 1.28 2006/03/03 13:36:27 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.28 2006/03/03 13:36:27 christos Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include "rcv.h"
42 #include "extern.h"
43 
44 /*
45  * Mail -- a mail program
46  *
47  * Still more user commands.
48  */
49 static int delgroup(const char *);
50 static int diction(const void *, const void *);
51 
52 /*
53  * Process a shell escape by saving signals, ignoring signals,
54  * and forking a sh -c
55  */
56 int
57 shell(void *v)
58 {
59 	char *str = v;
60 	sig_t sigint = signal(SIGINT, SIG_IGN);
61 	const char *shellcmd;
62 	char cmd[BUFSIZ];
63 
64 	(void)strcpy(cmd, str);
65 	if (bangexp(cmd) < 0)
66 		return 1;
67 	if ((shellcmd = value("SHELL")) == NULL)
68 		shellcmd = _PATH_CSHELL;
69 	(void)run_command(shellcmd, 0, 0, 1, "-c", cmd, NULL);
70 	(void)signal(SIGINT, sigint);
71 	(void)printf("!\n");
72 	return 0;
73 }
74 
75 /*
76  * Fork an interactive shell.
77  */
78 /*ARGSUSED*/
79 int
80 dosh(void *v)
81 {
82 	sig_t sigint = signal(SIGINT, SIG_IGN);
83 	const char *shellcmd;
84 
85 	if ((shellcmd = value("SHELL")) == NULL)
86 		shellcmd = _PATH_CSHELL;
87 	(void)run_command(shellcmd, 0, 0, 1, NULL);
88 	(void)signal(SIGINT, sigint);
89 	(void)putchar('\n');
90 	return 0;
91 }
92 
93 /*
94  * Expand the shell escape by expanding unescaped !'s into the
95  * last issued command where possible.
96  */
97 
98 char	lastbang[128];
99 
100 int
101 bangexp(char *str)
102 {
103 	char bangbuf[BUFSIZ];
104 	char *cp, *cp2;
105 	int n;
106 	int changed = 0;
107 
108 	cp = str;
109 	cp2 = bangbuf;
110 	n = BUFSIZ;
111 	while (*cp) {
112 		if (*cp == '!') {
113 			if (n < strlen(lastbang)) {
114 overf:
115 				(void)printf("Command buffer overflow\n");
116 				return(-1);
117 			}
118 			changed++;
119 			(void)strcpy(cp2, lastbang);
120 			cp2 += strlen(lastbang);
121 			n -= strlen(lastbang);
122 			cp++;
123 			continue;
124 		}
125 		if (*cp == '\\' && cp[1] == '!') {
126 			if (--n <= 1)
127 				goto overf;
128 			*cp2++ = '!';
129 			cp += 2;
130 			changed++;
131 		}
132 		if (--n <= 1)
133 			goto overf;
134 		*cp2++ = *cp++;
135 	}
136 	*cp2 = 0;
137 	if (changed) {
138 		(void)printf("!%s\n", bangbuf);
139 		(void)fflush(stdout);
140 	}
141 	(void)strcpy(str, bangbuf);
142 	(void)strncpy(lastbang, bangbuf, 128);
143 	lastbang[127] = 0;
144 	return(0);
145 }
146 
147 /*
148  * Print out a nice help message from some file or another.
149  */
150 
151 int
152 /*ARGSUSED*/
153 help(void *v)
154 {
155 	int c;
156 	FILE *f;
157 
158 	if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
159 		warn(_PATH_HELP);
160 		return(1);
161 	}
162 	while ((c = getc(f)) != EOF)
163 		(void)putchar(c);
164 	(void)Fclose(f);
165 	return(0);
166 }
167 
168 /*
169  * Change user's working directory.
170  */
171 int
172 schdir(void *v)
173 {
174 	char **arglist = v;
175 	const char *cp;
176 
177 	if (*arglist == NULL)
178 		cp = homedir;
179 	else
180 		if ((cp = expand(*arglist)) == NULL)
181 			return(1);
182 	if (chdir(cp) < 0) {
183 		warn("%s", cp);
184 		return(1);
185 	}
186 	return 0;
187 }
188 
189 int
190 respond(void *v)
191 {
192 	int *msgvec = v;
193 	if (value("Replyall") == NULL)
194 		return (_respond(msgvec));
195 	else
196 		return (_Respond(msgvec));
197 }
198 
199 /*
200  * Reply to a list of messages.  Extract each name from the
201  * message header and send them off to mail1()
202  */
203 int
204 _respond(int *msgvec)
205 {
206 	struct message *mp;
207 	char *cp, *rcv, *replyto;
208 	char **ap;
209 	struct name *np;
210 	struct header head;
211 
212 	if (msgvec[1] != 0) {
213 		(void)printf("Sorry, can't reply to multiple messages at once\n");
214 		return(1);
215 	}
216 	mp = &message[msgvec[0] - 1];
217 	touch(mp);
218 	dot = mp;
219 	if ((rcv = skin(hfield("from", mp))) == NULL)
220 		rcv = skin(nameof(mp, 1));
221 	if ((replyto = skin(hfield("reply-to", mp))) != NULL)
222 		np = extract(replyto, GTO);
223 	else if ((cp = skin(hfield("to", mp))) != NULL)
224 		np = extract(cp, GTO);
225 	else
226 		np = NULL;
227 	np = elide(np);
228 	/*
229 	 * Delete my name from the reply list,
230 	 * and with it, all my alternate names.
231 	 */
232 	np = delname(np, myname);
233 	if (altnames)
234 		for (ap = altnames; *ap; ap++)
235 			np = delname(np, *ap);
236 	if (np != NULL && replyto == NULL)
237 		np = cat(np, extract(rcv, GTO));
238 	else if (np == NULL) {
239 		if (replyto != NULL)
240 			(void)printf("Empty reply-to field -- replying to author\n");
241 		np = extract(rcv, GTO);
242 	}
243 	head.h_to = np;
244 	if ((head.h_subject = hfield("subject", mp)) == NULL)
245 		head.h_subject = hfield("subj", mp);
246 	head.h_subject = reedit(head.h_subject);
247 	if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
248 		np = elide(extract(cp, GCC));
249 		np = delname(np, myname);
250 		if (altnames != 0)
251 			for (ap = altnames; *ap; ap++)
252 				np = delname(np, *ap);
253 		head.h_cc = np;
254 	} else
255 		head.h_cc = NULL;
256 	head.h_bcc = NULL;
257 	head.h_smopts = NULL;
258 	mail1(&head, 1);
259 	return(0);
260 }
261 
262 /*
263  * Modify the subject we are replying to to begin with Re: if
264  * it does not already.
265  */
266 char *
267 reedit(char *subj)
268 {
269 	char *newsubj;
270 
271 	if (subj == NULL)
272 		return NULL;
273 	if ((subj[0] == 'r' || subj[0] == 'R') &&
274 	    (subj[1] == 'e' || subj[1] == 'E') &&
275 	    subj[2] == ':')
276 		return subj;
277 	newsubj = salloc(strlen(subj) + 5);
278 	(void)strcpy(newsubj, "Re: ");
279 	(void)strcpy(newsubj + 4, subj);
280 	return newsubj;
281 }
282 
283 /*
284  * Preserve the named messages, so that they will be sent
285  * back to the system mailbox.
286  */
287 int
288 preserve(void *v)
289 {
290 	int *msgvec = v;
291 	struct message *mp;
292 	int *ip, mesg;
293 
294 	if (edit) {
295 		(void)printf("Cannot \"preserve\" in edit mode\n");
296 		return(1);
297 	}
298 	for (ip = msgvec; *ip != 0; ip++) {
299 		mesg = *ip;
300 		mp = &message[mesg-1];
301 		mp->m_flag |= MPRESERVE;
302 		mp->m_flag &= ~MBOX;
303 		dot = mp;
304 	}
305 	return(0);
306 }
307 
308 /*
309  * Mark all given messages as unread.
310  */
311 int
312 unread(void *v)
313 {
314 	int *msgvec = v;
315 	int *ip;
316 
317 	for (ip = msgvec; *ip != 0; ip++) {
318 		dot = &message[*ip-1];
319 		dot->m_flag &= ~(MREAD|MTOUCH);
320 		dot->m_flag |= MSTATUS;
321 	}
322 	return(0);
323 }
324 
325 /*
326  * Print the size of each message.
327  */
328 int
329 messize(void *v)
330 {
331 	int *msgvec = v;
332 	struct message *mp;
333 	int *ip, mesg;
334 
335 	for (ip = msgvec; *ip != 0; ip++) {
336 		mesg = *ip;
337 		mp = &message[mesg-1];
338 		(void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
339 		    /*LINTED*/
340 		    (unsigned long long)mp->m_size);
341 	}
342 	return(0);
343 }
344 
345 /*
346  * Quit quickly.  If we are sourcing, just pop the input level
347  * by returning an error.
348  */
349 int
350 /*ARGSUSED*/
351 rexit(void *v)
352 {
353 	if (sourcing)
354 		return(1);
355 	exit(0);
356 	/*NOTREACHED*/
357 }
358 
359 /*
360  * Set or display a variable value.  Syntax is similar to that
361  * of csh.
362  */
363 int
364 set(void *v)
365 {
366 	char **arglist = v;
367 	struct var *vp;
368 	const char *cp;
369 	char varbuf[BUFSIZ], **ap, **p;
370 	int errs, h, s;
371 	size_t l;
372 
373 	if (*arglist == NULL) {
374 		for (h = 0, s = 1; h < HSHSIZE; h++)
375 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
376 				s++;
377 		ap = salloc(s * sizeof *ap);
378 		for (h = 0, p = ap; h < HSHSIZE; h++)
379 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
380 				*p++ = vp->v_name;
381 		*p = NULL;
382 		sort(ap);
383 		for (p = ap; *p != NULL; p++)
384 			(void)printf("%s\t%s\n", *p, value(*p));
385 		return(0);
386 	}
387 	errs = 0;
388 	for (ap = arglist; *ap != NULL; ap++) {
389 		cp = *ap;
390 		while (*cp != '=' && *cp != '\0')
391 			++cp;
392 		l = cp - *ap;
393 		if (l >= sizeof varbuf)
394 			l = sizeof varbuf - 1;
395 		(void)strncpy(varbuf, *ap, l);
396 		varbuf[l] = '\0';
397 		if (*cp == '\0')
398 			cp = "";
399 		else
400 			cp++;
401 		if (equal(varbuf, "")) {
402 			(void)printf("Non-null variable name required\n");
403 			errs++;
404 			continue;
405 		}
406 		assign(varbuf, cp);
407 	}
408 	return(errs);
409 }
410 
411 /*
412  * Unset a bunch of variable values.
413  */
414 int
415 unset(void *v)
416 {
417 	char **arglist = v;
418 	struct var *vp, *vp2;
419 	int errs, h;
420 	char **ap;
421 
422 	errs = 0;
423 	for (ap = arglist; *ap != NULL; ap++) {
424 		if ((vp2 = lookup(*ap)) == NULL) {
425 			if (getenv(*ap)) {
426 				(void)unsetenv(*ap);
427 			} else if (!sourcing) {
428 				(void)printf("\"%s\": undefined variable\n", *ap);
429 				errs++;
430 			}
431 			continue;
432 		}
433 		h = hash(*ap);
434 		if (vp2 == variables[h]) {
435 			variables[h] = variables[h]->v_link;
436 			v_free(vp2->v_name);
437                         v_free(vp2->v_value);
438 			free(vp2);
439 			continue;
440 		}
441 		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
442 			;
443 		vp->v_link = vp2->v_link;
444                 v_free(vp2->v_name);
445                 v_free(vp2->v_value);
446 		free(vp2);
447 	}
448 	return(errs);
449 }
450 
451 /*
452  * Put add users to a group.
453  */
454 int
455 group(void *v)
456 {
457 	char **argv = v;
458 	struct grouphead *gh;
459 	struct group *gp;
460 	int h;
461 	int s;
462 	char **ap, *gname, **p;
463 
464 	if (*argv == NULL) {
465 		for (h = 0, s = 1; h < HSHSIZE; h++)
466 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
467 				s++;
468 		ap = salloc(s * sizeof *ap);
469 		for (h = 0, p = ap; h < HSHSIZE; h++)
470 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
471 				*p++ = gh->g_name;
472 		*p = NULL;
473 		sort(ap);
474 		for (p = ap; *p != NULL; p++)
475 			printgroup(*p);
476 		return(0);
477 	}
478 	if (argv[1] == NULL) {
479 		printgroup(*argv);
480 		return(0);
481 	}
482 	gname = *argv;
483 	h = hash(gname);
484 	if ((gh = findgroup(gname)) == NULL) {
485 		gh = (struct grouphead *) calloc(1, sizeof *gh);
486 		gh->g_name = vcopy(gname);
487 		gh->g_list = NULL;
488 		gh->g_link = groups[h];
489 		groups[h] = gh;
490 	}
491 
492 	/*
493 	 * Insert names from the command list into the group.
494 	 * Who cares if there are duplicates?  They get tossed
495 	 * later anyway.
496 	 */
497 
498 	for (ap = argv+1; *ap != NULL; ap++) {
499 		gp = (struct group *) calloc(1, sizeof *gp);
500 		gp->ge_name = vcopy(*ap);
501 		gp->ge_link = gh->g_list;
502 		gh->g_list = gp;
503 	}
504 	return(0);
505 }
506 
507 /*
508  * The unalias command takes a list of alises
509  * and discards the remembered groups of users.
510  */
511 int
512 unalias(void *v)
513 {
514 	char **ap;
515 
516 	for (ap = v; *ap != NULL; ap++)
517 		(void)delgroup(*ap);
518 	return 0;
519 }
520 
521 /*
522  * Delete the named group alias. Return zero if the group was
523  * successfully deleted, or -1 if there was no such group.
524  */
525 static int
526 delgroup(const char *name)
527 {
528 	struct grouphead *gh, *p;
529 	struct group *g;
530 	int h;
531 
532 	h = hash(name);
533 	for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
534 		if (strcmp(gh->g_name, name) == 0) {
535 			if (p == NULL)
536 				groups[h] = gh->g_link;
537 			else
538 				p->g_link = gh->g_link;
539 			while (gh->g_list != NULL) {
540 				g = gh->g_list;
541 				gh->g_list = g->ge_link;
542 				free(g->ge_name);
543 				free(g);
544 			}
545 			free(gh->g_name);
546 			free(gh);
547 			return 0;
548 		}
549 	return -1;
550 }
551 
552 /*
553  * Sort the passed string vecotor into ascending dictionary
554  * order.
555  */
556 void
557 sort(char **list)
558 {
559 	char **ap;
560 
561 	for (ap = list; *ap != NULL; ap++)
562 		;
563 	if (ap-list < 2)
564 		return;
565 	qsort(list, (size_t)(ap-list), sizeof(*list), diction);
566 }
567 
568 /*
569  * Do a dictionary order comparison of the arguments from
570  * qsort.
571  */
572 static int
573 diction(const void *a, const void *b)
574 {
575 	return(strcmp(*(const char *const *)a, *(const char *const *)b));
576 }
577 
578 /*
579  * The do nothing command for comments.
580  */
581 
582 /*ARGSUSED*/
583 int
584 null(void *v)
585 {
586 	return 0;
587 }
588 
589 /*
590  * Change to another file.  With no argument, print information about
591  * the current file.
592  */
593 int
594 file(void *v)
595 {
596 	char **argv = v;
597 
598 	if (argv[0] == NULL) {
599 		(void)newfileinfo(0);
600 		return 0;
601 	}
602 	if (setfile(*argv) < 0)
603 		return 1;
604 	announce();
605 	return 0;
606 }
607 
608 /*
609  * Expand file names like echo
610  */
611 int
612 echo(void *v)
613 {
614 	char **argv = v;
615 	char **ap;
616 	const char *cp;
617 
618 	for (ap = argv; *ap != NULL; ap++) {
619 		cp = *ap;
620 		if ((cp = expand(cp)) != NULL) {
621 			if (ap != argv)
622 				(void)putchar(' ');
623 			(void)printf("%s", cp);
624 		}
625 	}
626 	(void)putchar('\n');
627 	return 0;
628 }
629 
630 int
631 Respond(void *v)
632 {
633 	int *msgvec = v;
634 	if (value("Replyall") == NULL)
635 		return (_Respond(msgvec));
636 	else
637 		return (_respond(msgvec));
638 }
639 
640 /*
641  * Reply to a series of messages by simply mailing to the senders
642  * and not messing around with the To: and Cc: lists as in normal
643  * reply.
644  */
645 int
646 _Respond(int msgvec[])
647 {
648 	struct header head;
649 	struct message *mp;
650 	int *ap;
651 	char *cp;
652 
653 	head.h_to = NULL;
654 	for (ap = msgvec; *ap != 0; ap++) {
655 		mp = &message[*ap - 1];
656 		touch(mp);
657 		dot = mp;
658 		if ((cp = skin(hfield("from", mp))) == NULL)
659 			cp = skin(nameof(mp, 2));
660 		head.h_to = cat(head.h_to, extract(cp, GTO));
661 	}
662 	if (head.h_to == NULL)
663 		return 0;
664 	mp = &message[msgvec[0] - 1];
665 	if ((head.h_subject = hfield("subject", mp)) == NULL)
666 		head.h_subject = hfield("subj", mp);
667 	head.h_subject = reedit(head.h_subject);
668 	head.h_cc = NULL;
669 	head.h_bcc = NULL;
670 	head.h_smopts = NULL;
671 	mail1(&head, 1);
672 	return 0;
673 }
674 
675 /*
676  * Conditional commands.  These allow one to parameterize one's
677  * .mailrc and do some things if sending, others if receiving.
678  */
679 int
680 ifcmd(void *v)
681 {
682 	char **argv = v;
683 	char *cp;
684 
685 	if (cond != CANY) {
686 		(void)printf("Illegal nested \"if\"\n");
687 		return(1);
688 	}
689 	cond = CANY;
690 	cp = argv[0];
691 	switch (*cp) {
692 	case 'r': case 'R':
693 		cond = CRCV;
694 		break;
695 
696 	case 's': case 'S':
697 		cond = CSEND;
698 		break;
699 
700 	default:
701 		(void)printf("Unrecognized if-keyword: \"%s\"\n", cp);
702 		return(1);
703 	}
704 	return(0);
705 }
706 
707 /*
708  * Implement 'else'.  This is pretty simple -- we just
709  * flip over the conditional flag.
710  */
711 int
712 /*ARGSUSED*/
713 elsecmd(void *v)
714 {
715 
716 	switch (cond) {
717 	case CANY:
718 		(void)printf("\"Else\" without matching \"if\"\n");
719 		return(1);
720 
721 	case CSEND:
722 		cond = CRCV;
723 		break;
724 
725 	case CRCV:
726 		cond = CSEND;
727 		break;
728 
729 	default:
730 		(void)printf("Mail's idea of conditions is screwed up\n");
731 		cond = CANY;
732 		break;
733 	}
734 	return(0);
735 }
736 
737 /*
738  * End of if statement.  Just set cond back to anything.
739  */
740 int
741 /*ARGSUSED*/
742 endifcmd(void *v)
743 {
744 
745 	if (cond == CANY) {
746 		(void)printf("\"Endif\" without matching \"if\"\n");
747 		return(1);
748 	}
749 	cond = CANY;
750 	return(0);
751 }
752 
753 /*
754  * Set the list of alternate names.
755  */
756 int
757 alternates(void *v)
758 {
759 	char **namelist = v;
760 	int c;
761 	char **ap, **ap2, *cp;
762 
763 	c = argcount(namelist) + 1;
764 	if (c == 1) {
765 		if (altnames == 0)
766 			return(0);
767 		for (ap = altnames; *ap; ap++)
768 			(void)printf("%s ", *ap);
769 		(void)printf("\n");
770 		return(0);
771 	}
772 	if (altnames != 0)
773 		free(altnames);
774 	altnames = (char **) calloc((unsigned) c, sizeof (char *));
775 	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
776 		cp = calloc((unsigned) strlen(*ap) + 1, sizeof (char));
777 		(void)strcpy(cp, *ap);
778 		*ap2 = cp;
779 	}
780 	*ap2 = 0;
781 	return(0);
782 }
783