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