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