xref: /freebsd/usr.bin/mail/cmd3.c (revision 8d20be1e22095c27faf8fe8b2f0d089739cc742e)
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, NULL, 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 < 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 		gh = calloc(sizeof(*gh), 1);
467 		gh->g_name = vcopy(gname);
468 		gh->g_list = NULL;
469 		gh->g_link = groups[h];
470 		groups[h] = gh;
471 	}
472 
473 	/*
474 	 * Insert names from the command list into the group.
475 	 * Who cares if there are duplicates?  They get tossed
476 	 * later anyway.
477 	 */
478 
479 	for (ap = argv+1; *ap != NULL; ap++) {
480 		gp = calloc(sizeof(*gp), 1);
481 		gp->ge_name = vcopy(*ap);
482 		gp->ge_link = gh->g_list;
483 		gh->g_list = gp;
484 	}
485 	return (0);
486 }
487 
488 /*
489  * Sort the passed string vecotor into ascending dictionary
490  * order.
491  */
492 void
493 sort(char **list)
494 {
495 	char **ap;
496 
497 	for (ap = list; *ap != NULL; ap++)
498 		;
499 	if (ap-list < 2)
500 		return;
501 	qsort(list, ap-list, sizeof(*list), diction);
502 }
503 
504 /*
505  * Do a dictionary order comparison of the arguments from
506  * qsort.
507  */
508 int
509 diction(const void *a, const void *b)
510 {
511 	return (strcmp(*(const char **)a, *(const char **)b));
512 }
513 
514 /*
515  * The do nothing command for comments.
516  */
517 
518 /*ARGSUSED*/
519 int
520 null(int e __unused)
521 {
522 	return (0);
523 }
524 
525 /*
526  * Change to another file.  With no argument, print information about
527  * the current file.
528  */
529 int
530 file(char **argv)
531 {
532 
533 	if (argv[0] == NULL) {
534 		newfileinfo(0);
535 		return (0);
536 	}
537 	if (setfile(*argv) < 0)
538 		return (1);
539 	announce();
540 	return (0);
541 }
542 
543 /*
544  * Expand file names like echo
545  */
546 int
547 echo(char **argv)
548 {
549 	char **ap, *cp;
550 
551 	for (ap = argv; *ap != NULL; ap++) {
552 		cp = *ap;
553 		if ((cp = expand(cp)) != NULL) {
554 			if (ap != argv)
555 				printf(" ");
556 			printf("%s", cp);
557 		}
558 	}
559 	printf("\n");
560 	return (0);
561 }
562 
563 int
564 Respond(int *msgvec)
565 {
566 	if (value("Replyall") == NULL && value("flipr") == NULL)
567 		return (doRespond(msgvec));
568 	else
569 		return (dorespond(msgvec));
570 }
571 
572 /*
573  * Reply to a series of messages by simply mailing to the senders
574  * and not messing around with the To: and Cc: lists as in normal
575  * reply.
576  */
577 int
578 doRespond(int msgvec[])
579 {
580 	struct header head;
581 	struct message *mp;
582 	int *ap;
583 	char *cp, *mid;
584 
585 	head.h_to = NULL;
586 	for (ap = msgvec; *ap != 0; ap++) {
587 		mp = &message[*ap - 1];
588 		touch(mp);
589 		dot = mp;
590 		if ((cp = skin(hfield("from", mp))) == NULL)
591 			cp = skin(nameof(mp, 2));
592 		head.h_to = cat(head.h_to, extract(cp, GTO));
593 		mid = skin(hfield("message-id", mp));
594 	}
595 	if (head.h_to == NULL)
596 		return (0);
597 	mp = &message[msgvec[0] - 1];
598 	if ((head.h_subject = hfield("subject", mp)) == NULL)
599 		head.h_subject = hfield("subj", mp);
600 	head.h_subject = reedit(head.h_subject);
601 	head.h_cc = NULL;
602 	head.h_bcc = NULL;
603 	head.h_smopts = NULL;
604 	head.h_replyto = value("REPLYTO");
605 	head.h_inreplyto = mid;
606 	mail1(&head, 1);
607 	return (0);
608 }
609 
610 /*
611  * Conditional commands.  These allow one to parameterize one's
612  * .mailrc and do some things if sending, others if receiving.
613  */
614 int
615 ifcmd(char **argv)
616 {
617 	char *cp;
618 
619 	if (cond != CANY) {
620 		printf("Illegal nested \"if\"\n");
621 		return (1);
622 	}
623 	cond = CANY;
624 	cp = argv[0];
625 	switch (*cp) {
626 	case 'r': case 'R':
627 		cond = CRCV;
628 		break;
629 
630 	case 's': case 'S':
631 		cond = CSEND;
632 		break;
633 
634 	default:
635 		printf("Unrecognized if-keyword: \"%s\"\n", cp);
636 		return (1);
637 	}
638 	return (0);
639 }
640 
641 /*
642  * Implement 'else'.  This is pretty simple -- we just
643  * flip over the conditional flag.
644  */
645 int
646 elsecmd(void)
647 {
648 
649 	switch (cond) {
650 	case CANY:
651 		printf("\"Else\" without matching \"if\"\n");
652 		return (1);
653 
654 	case CSEND:
655 		cond = CRCV;
656 		break;
657 
658 	case CRCV:
659 		cond = CSEND;
660 		break;
661 
662 	default:
663 		printf("Mail's idea of conditions is screwed up\n");
664 		cond = CANY;
665 		break;
666 	}
667 	return (0);
668 }
669 
670 /*
671  * End of if statement.  Just set cond back to anything.
672  */
673 int
674 endifcmd(void)
675 {
676 
677 	if (cond == CANY) {
678 		printf("\"Endif\" without matching \"if\"\n");
679 		return (1);
680 	}
681 	cond = CANY;
682 	return (0);
683 }
684 
685 /*
686  * Set the list of alternate names.
687  */
688 int
689 alternates(char **namelist)
690 {
691 	int c;
692 	char **ap, **ap2, *cp;
693 
694 	c = argcount(namelist) + 1;
695 	if (c == 1) {
696 		if (altnames == 0)
697 			return (0);
698 		for (ap = altnames; *ap != NULL; ap++)
699 			printf("%s ", *ap);
700 		printf("\n");
701 		return (0);
702 	}
703 	if (altnames != 0)
704 		(void)free(altnames);
705 	altnames = calloc((unsigned)c, sizeof(char *));
706 	for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
707 		cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
708 		strcpy(cp, *ap);
709 		*ap2 = cp;
710 	}
711 	*ap2 = 0;
712 	return (0);
713 }
714