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