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