xref: /freebsd/usr.bin/mail/cmd3.c (revision f4dc9bf43457515e5c88d1400d4f5ff70a82d9c7)
1 /*
2  * Copyright (c) 1980, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)cmd3.c	8.2 (Berkeley) 4/20/95";
33 #endif
34 #endif /* not lint */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include "rcv.h"
39 #include "extern.h"
40 
41 /*
42  * Mail -- a mail program
43  *
44  * Still more user commands.
45  */
46 
47 /*
48  * Process a shell escape by saving signals, ignoring signals,
49  * and forking a sh -c
50  */
51 int
52 shell(char *str)
53 {
54 	sig_t sigint = signal(SIGINT, SIG_IGN);
55 	char *sh;
56 	char cmd[BUFSIZ];
57 
58 	if (strlcpy(cmd, str, sizeof(cmd)) >= sizeof(cmd))
59 		return (1);
60 	if (bangexp(cmd, sizeof(cmd)) < 0)
61 		return (1);
62 	if ((sh = value("SHELL")) == NULL)
63 		sh = _PATH_CSHELL;
64 	(void)run_command(sh, 0, -1, -1, "-c", cmd, NULL);
65 	(void)signal(SIGINT, sigint);
66 	printf("!\n");
67 	return (0);
68 }
69 
70 /*
71  * Fork an interactive shell.
72  */
73 /*ARGSUSED*/
74 int
75 dosh(char *str __unused)
76 {
77 	sig_t sigint = signal(SIGINT, SIG_IGN);
78 	char *sh;
79 
80 	if ((sh = value("SHELL")) == NULL)
81 		sh = _PATH_CSHELL;
82 	(void)run_command(sh, 0, -1, -1, NULL);
83 	(void)signal(SIGINT, sigint);
84 	printf("\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 = sizeof(bangbuf);
103 	while (*cp != '\0') {
104 		if (*cp == '!') {
105 			if (n < (int)strlen(lastbang)) {
106 overf:
107 				printf("Command buffer overflow\n");
108 				return (-1);
109 			}
110 			changed++;
111 			if (strlcpy(cp2, lastbang, sizeof(bangbuf) - (cp2 - bangbuf))
112 			    >= sizeof(bangbuf) - (cp2 - bangbuf))
113 				goto overf;
114 			cp2 += strlen(lastbang);
115 			n -= strlen(lastbang);
116 			cp++;
117 			continue;
118 		}
119 		if (*cp == '\\' && cp[1] == '!') {
120 			if (--n <= 1)
121 				goto overf;
122 			*cp2++ = '!';
123 			cp += 2;
124 			changed++;
125 		}
126 		if (--n <= 1)
127 			goto overf;
128 		*cp2++ = *cp++;
129 	}
130 	*cp2 = 0;
131 	if (changed) {
132 		printf("!%s\n", bangbuf);
133 		(void)fflush(stdout);
134 	}
135 	if (strlcpy(str, bangbuf, strsize) >= strsize)
136 		goto overf;
137 	if (strlcpy(lastbang, bangbuf, sizeof(lastbang)) >= sizeof(lastbang))
138 		goto overf;
139 	return (0);
140 }
141 
142 /*
143  * Print out a nice help message from some file or another.
144  */
145 
146 int
147 help(void)
148 {
149 	int c;
150 	FILE *f;
151 
152 	if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
153 		warn("%s", _PATH_HELP);
154 		return (1);
155 	}
156 	while ((c = getc(f)) != EOF)
157 		putchar(c);
158 	(void)Fclose(f);
159 	return (0);
160 }
161 
162 /*
163  * Change user's working directory.
164  */
165 int
166 schdir(char **arglist)
167 {
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 	if (chdir(cp) < 0) {
178 		warn("%s", cp);
179 		return (1);
180 	}
181 	return (0);
182 }
183 
184 int
185 respond(int *msgvec)
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 subject we are replying to 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(int *msgvec)
284 {
285 	int *ip, mesg;
286 	struct message *mp;
287 
288 	if (edit) {
289 		printf("Cannot \"preserve\" in edit mode\n");
290 		return (1);
291 	}
292 	for (ip = msgvec; *ip != 0; ip++) {
293 		mesg = *ip;
294 		mp = &message[mesg-1];
295 		mp->m_flag |= MPRESERVE;
296 		mp->m_flag &= ~MBOX;
297 		dot = mp;
298 	}
299 	return (0);
300 }
301 
302 /*
303  * Mark all given messages as unread.
304  */
305 int
306 unread(int msgvec[])
307 {
308 	int *ip;
309 
310 	for (ip = msgvec; *ip != 0; ip++) {
311 		dot = &message[*ip-1];
312 		dot->m_flag &= ~(MREAD|MTOUCH);
313 		dot->m_flag |= MSTATUS;
314 	}
315 	return (0);
316 }
317 
318 /*
319  * Print the size of each message.
320  */
321 int
322 messize(int *msgvec)
323 {
324 	struct message *mp;
325 	int *ip, mesg;
326 
327 	for (ip = msgvec; *ip != 0; ip++) {
328 		mesg = *ip;
329 		mp = &message[mesg-1];
330 		printf("%d: %ld/%ld\n", mesg, mp->m_lines, mp->m_size);
331 	}
332 	return (0);
333 }
334 
335 /*
336  * Quit quickly.  If we are sourcing, just pop the input level
337  * by returning an error.
338  */
339 int
340 rexit(int e __unused)
341 {
342 	if (sourcing)
343 		return (1);
344 	exit(0);
345 	/*NOTREACHED*/
346 }
347 
348 /*
349  * Set or display a variable value.  Syntax is similar to that
350  * of csh.
351  */
352 int
353 set(char **arglist)
354 {
355 	struct var *vp;
356 	char *cp, *cp2;
357 	char varbuf[BUFSIZ], **ap, **p;
358 	int errs, h, s;
359 
360 	if (*arglist == NULL) {
361 		for (h = 0, s = 1; h < HSHSIZE; h++)
362 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
363 				s++;
364 		ap = (char **)salloc(s * sizeof(*ap));
365 		for (h = 0, p = ap; h < HSHSIZE; h++)
366 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
367 				*p++ = vp->v_name;
368 		*p = NULL;
369 		sort(ap);
370 		for (p = ap; *p != NULL; p++)
371 			printf("%s\t%s\n", *p, value(*p));
372 		return (0);
373 	}
374 	errs = 0;
375 	for (ap = arglist; *ap != NULL; ap++) {
376 		cp = *ap;
377 		cp2 = varbuf;
378 		while (cp2 < varbuf + sizeof(varbuf) - 1 && *cp != '=' && *cp != '\0')
379 			*cp2++ = *cp++;
380 		*cp2 = '\0';
381 		if (*cp == '\0')
382 			cp = "";
383 		else
384 			cp++;
385 		if (equal(varbuf, "")) {
386 			printf("Non-null variable name required\n");
387 			errs++;
388 			continue;
389 		}
390 		assign(varbuf, cp);
391 	}
392 	return (errs);
393 }
394 
395 /*
396  * Unset a bunch of variable values.
397  */
398 int
399 unset(char **arglist)
400 {
401 	struct var *vp, *vp2;
402 	int errs, h;
403 	char **ap;
404 
405 	errs = 0;
406 	for (ap = arglist; *ap != NULL; ap++) {
407 		if ((vp2 = lookup(*ap)) == NULL) {
408 			if (getenv(*ap))
409 				unsetenv(*ap);
410 			else if (!sourcing) {
411 				printf("\"%s\": undefined variable\n", *ap);
412 				errs++;
413 			}
414 			continue;
415 		}
416 		h = hash(*ap);
417 		if (vp2 == variables[h]) {
418 			variables[h] = variables[h]->v_link;
419 			vfree(vp2->v_name);
420 			vfree(vp2->v_value);
421 			(void)free(vp2);
422 			continue;
423 		}
424 		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
425 			;
426 		vp->v_link = vp2->v_link;
427 		vfree(vp2->v_name);
428 		vfree(vp2->v_value);
429 		(void)free(vp2);
430 	}
431 	return (errs);
432 }
433 
434 /*
435  * Put add users to a group.
436  */
437 int
438 group(char **argv)
439 {
440 	struct grouphead *gh;
441 	struct group *gp;
442 	char **ap, *gname, **p;
443 	int h, s;
444 
445 	if (*argv == NULL) {
446 		for (h = 0, s = 1; h < HSHSIZE; h++)
447 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
448 				s++;
449 		ap = (char **)salloc(s * sizeof(*ap));
450 		for (h = 0, p = ap; h < HSHSIZE; h++)
451 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
452 				*p++ = gh->g_name;
453 		*p = NULL;
454 		sort(ap);
455 		for (p = ap; *p != NULL; p++)
456 			printgroup(*p);
457 		return (0);
458 	}
459 	if (argv[1] == NULL) {
460 		printgroup(*argv);
461 		return (0);
462 	}
463 	gname = *argv;
464 	h = hash(gname);
465 	if ((gh = findgroup(gname)) == NULL) {
466 		if ((gh = calloc(1, sizeof(*gh))) == NULL)
467 			err(1, "Out of memory");
468 		gh->g_name = vcopy(gname);
469 		gh->g_list = NULL;
470 		gh->g_link = groups[h];
471 		groups[h] = gh;
472 	}
473 
474 	/*
475 	 * Insert names from the command list into the group.
476 	 * Who cares if there are duplicates?  They get tossed
477 	 * later anyway.
478 	 */
479 
480 	for (ap = argv+1; *ap != NULL; ap++) {
481 		if ((gp = calloc(1, sizeof(*gp))) == NULL)
482 			err(1, "Out of memory");
483 		gp->ge_name = vcopy(*ap);
484 		gp->ge_link = gh->g_list;
485 		gh->g_list = gp;
486 	}
487 	return (0);
488 }
489 
490 /*
491  * Sort the passed string vecotor into ascending dictionary
492  * order.
493  */
494 void
495 sort(char **list)
496 {
497 	char **ap;
498 
499 	for (ap = list; *ap != NULL; ap++)
500 		;
501 	if (ap-list < 2)
502 		return;
503 	qsort(list, ap-list, sizeof(*list), diction);
504 }
505 
506 /*
507  * Do a dictionary order comparison of the arguments from
508  * qsort.
509  */
510 int
511 diction(const void *a, const void *b)
512 {
513 	return (strcmp(*(const char **)a, *(const char **)b));
514 }
515 
516 /*
517  * The do nothing command for comments.
518  */
519 
520 /*ARGSUSED*/
521 int
522 null(int e __unused)
523 {
524 	return (0);
525 }
526 
527 /*
528  * Change to another file.  With no argument, print information about
529  * the current file.
530  */
531 int
532 file(char **argv)
533 {
534 
535 	if (argv[0] == NULL) {
536 		newfileinfo(0);
537 		return (0);
538 	}
539 	if (setfile(*argv) < 0)
540 		return (1);
541 	announce();
542 	return (0);
543 }
544 
545 /*
546  * Expand file names like echo
547  */
548 int
549 echo(char **argv)
550 {
551 	char **ap, *cp;
552 
553 	for (ap = argv; *ap != NULL; ap++) {
554 		cp = *ap;
555 		if ((cp = expand(cp)) != NULL) {
556 			if (ap != argv)
557 				printf(" ");
558 			printf("%s", cp);
559 		}
560 	}
561 	printf("\n");
562 	return (0);
563 }
564 
565 int
566 Respond(int *msgvec)
567 {
568 	if (value("Replyall") == NULL && value("flipr") == NULL)
569 		return (doRespond(msgvec));
570 	else
571 		return (dorespond(msgvec));
572 }
573 
574 /*
575  * Reply to a series of messages by simply mailing to the senders
576  * and not messing around with the To: and Cc: lists as in normal
577  * reply.
578  */
579 int
580 doRespond(int msgvec[])
581 {
582 	struct header head;
583 	struct message *mp;
584 	int *ap;
585 	char *cp, *mid;
586 
587 	head.h_to = NULL;
588 	for (ap = msgvec; *ap != 0; ap++) {
589 		mp = &message[*ap - 1];
590 		touch(mp);
591 		dot = mp;
592 		if ((cp = skin(hfield("from", mp))) == NULL)
593 			cp = skin(nameof(mp, 2));
594 		head.h_to = cat(head.h_to, extract(cp, GTO));
595 		mid = skin(hfield("message-id", mp));
596 	}
597 	if (head.h_to == NULL)
598 		return (0);
599 	mp = &message[msgvec[0] - 1];
600 	if ((head.h_subject = hfield("subject", mp)) == NULL)
601 		head.h_subject = hfield("subj", mp);
602 	head.h_subject = reedit(head.h_subject);
603 	head.h_cc = NULL;
604 	head.h_bcc = NULL;
605 	head.h_smopts = NULL;
606 	head.h_replyto = value("REPLYTO");
607 	head.h_inreplyto = mid;
608 	mail1(&head, 1);
609 	return (0);
610 }
611 
612 /*
613  * Conditional commands.  These allow one to parameterize one's
614  * .mailrc and do some things if sending, others if receiving.
615  */
616 int
617 ifcmd(char **argv)
618 {
619 	char *cp;
620 
621 	if (cond != CANY) {
622 		printf("Illegal nested \"if\"\n");
623 		return (1);
624 	}
625 	cond = CANY;
626 	cp = argv[0];
627 	switch (*cp) {
628 	case 'r': case 'R':
629 		cond = CRCV;
630 		break;
631 
632 	case 's': case 'S':
633 		cond = CSEND;
634 		break;
635 
636 	default:
637 		printf("Unrecognized if-keyword: \"%s\"\n", cp);
638 		return (1);
639 	}
640 	return (0);
641 }
642 
643 /*
644  * Implement 'else'.  This is pretty simple -- we just
645  * flip over the conditional flag.
646  */
647 int
648 elsecmd(void)
649 {
650 
651 	switch (cond) {
652 	case CANY:
653 		printf("\"Else\" without matching \"if\"\n");
654 		return (1);
655 
656 	case CSEND:
657 		cond = CRCV;
658 		break;
659 
660 	case CRCV:
661 		cond = CSEND;
662 		break;
663 
664 	default:
665 		printf("Mail's idea of conditions is screwed up\n");
666 		cond = CANY;
667 		break;
668 	}
669 	return (0);
670 }
671 
672 /*
673  * End of if statement.  Just set cond back to anything.
674  */
675 int
676 endifcmd(void)
677 {
678 
679 	if (cond == CANY) {
680 		printf("\"Endif\" without matching \"if\"\n");
681 		return (1);
682 	}
683 	cond = CANY;
684 	return (0);
685 }
686 
687 /*
688  * Set the list of alternate names.
689  */
690 int
691 alternates(char **namelist)
692 {
693 	int c;
694 	char **ap, **ap2, *cp;
695 
696 	c = argcount(namelist) + 1;
697 	if (c == 1) {
698 		if (altnames == 0)
699 			return (0);
700 		for (ap = altnames; *ap != NULL; ap++)
701 			printf("%s ", *ap);
702 		printf("\n");
703 		return (0);
704 	}
705 	if (altnames != 0)
706 		(void)free(altnames);
707 	if ((altnames = calloc((unsigned)c, sizeof(char *))) == NULL)
708 		err(1, "Out of memory");
709 	for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
710 		cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
711 		strcpy(cp, *ap);
712 		*ap2 = cp;
713 	}
714 	*ap2 = 0;
715 	return (0);
716 }
717