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