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