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