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