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