xref: /openbsd/usr.bin/mail/cmd3.c (revision 404b540a)
1 /*	$OpenBSD: cmd3.c,v 1.23 2009/09/05 10:49:45 tobias 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.23 2009/09/05 10:49:45 tobias 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 	/*
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; 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 			puts("Empty reply-to field -- replying to author");
234 		np = extract(rcv, GTO);
235 	}
236 	np = elide(np);
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 (strncasecmp(subj, "re:", 3) == 0)
269 		return(subj);
270 	len = strlen(subj) + 5;
271 	newsubj = salloc(len);
272 	strlcpy(newsubj, "Re: ", len);
273 	strlcat(newsubj, subj, len);
274 	return(newsubj);
275 }
276 
277 /*
278  * Mark new the named messages, so that they will be left in the system
279  * mailbox as unread.
280  */
281 int
282 marknew(void *v)
283 {
284 	int *msgvec = v;
285 	int *ip;
286 
287 	for (ip = msgvec; *ip != NULL; ip++) {
288 		dot = &message[*ip-1];
289 		dot->m_flag &= ~(MBOX|MREAD|MTOUCH);
290 		dot->m_flag |= MNEW|MSTATUS;
291 	}
292 	return(0);
293 }
294 
295 /*
296  * Preserve the named messages, so that they will be sent
297  * back to the system mailbox.
298  */
299 int
300 preserve(void *v)
301 {
302 	int *msgvec = v;
303 	int *ip, mesg;
304 	struct message *mp;
305 
306 	if (edit) {
307 		puts("Cannot \"preserve\" in edit mode");
308 		return(1);
309 	}
310 	for (ip = msgvec; *ip != NULL; ip++) {
311 		mesg = *ip;
312 		mp = &message[mesg-1];
313 		mp->m_flag |= MPRESERVE;
314 		mp->m_flag &= ~MBOX;
315 		dot = mp;
316 	}
317 	return(0);
318 }
319 
320 /*
321  * Mark all given messages as unread.
322  */
323 int
324 unread(void *v)
325 {
326 	int *msgvec = v;
327 	int *ip;
328 
329 	for (ip = msgvec; *ip != NULL; ip++) {
330 		dot = &message[*ip-1];
331 		dot->m_flag &= ~(MREAD|MTOUCH);
332 		dot->m_flag |= MSTATUS;
333 	}
334 	return(0);
335 }
336 
337 /*
338  * Print the size of each message.
339  */
340 int
341 messize(void *v)
342 {
343 	int *msgvec = v;
344 	struct message *mp;
345 	int *ip, mesg;
346 
347 	for (ip = msgvec; *ip != NULL; ip++) {
348 		mesg = *ip;
349 		mp = &message[mesg-1];
350 		printf("%d: %d/%d\n", mesg, mp->m_lines, mp->m_size);
351 	}
352 	return(0);
353 }
354 
355 /*
356  * Quit quickly.  If we are sourcing, just pop the input level
357  * by returning an error.
358  */
359 int
360 rexit(void *v)
361 {
362 
363 	if (sourcing)
364 		return(1);
365 	exit(0);
366 	/*NOTREACHED*/
367 }
368 
369 /*
370  * Set or display a variable value.  Syntax is similar to that
371  * of csh.
372  */
373 int
374 set(void *v)
375 {
376 	char **arglist = v;
377 	struct var *vp;
378 	char *cp, *cp2;
379 	char varbuf[BUFSIZ], **ap, **p;
380 	int errs, h, s;
381 
382 	if (*arglist == NULL) {
383 		for (h = 0, s = 1; h < HSHSIZE; h++)
384 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
385 				s++;
386 		ap = (char **)salloc(s * sizeof(*ap));
387 		for (h = 0, p = ap; h < HSHSIZE; h++)
388 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
389 				*p++ = vp->v_name;
390 		*p = NULL;
391 		sort(ap);
392 		for (p = ap; *p != NULL; p++)
393 			printf("%s\t%s\n", *p, value(*p));
394 		return(0);
395 	}
396 	errs = 0;
397 	for (ap = arglist; *ap != NULL; ap++) {
398 		cp = *ap;
399 		cp2 = varbuf;
400 		while (*cp != '=' && *cp != '\0')
401 			*cp2++ = *cp++;
402 		*cp2 = '\0';
403 		if (*cp == '\0')
404 			cp = "";
405 		else
406 			cp++;
407 		if (equal(varbuf, "")) {
408 			puts("Non-null variable name required");
409 			errs++;
410 			continue;
411 		}
412 		assign(varbuf, cp);
413 	}
414 	return(errs);
415 }
416 
417 /*
418  * Unset a bunch of variable values.
419  */
420 int
421 unset(void *v)
422 {
423 	char **arglist = v;
424 	struct var *vp, *vp2;
425 	int errs, h;
426 	char **ap;
427 
428 	errs = 0;
429 	for (ap = arglist; *ap != NULL; ap++) {
430 		if ((vp2 = lookup(*ap)) == NULL) {
431 			if (!sourcing) {
432 				printf("\"%s\": undefined variable\n", *ap);
433 				errs++;
434 			}
435 			continue;
436 		}
437 		h = hash(*ap);
438 		if (vp2 == variables[h]) {
439 			variables[h] = variables[h]->v_link;
440 			vfree(vp2->v_name);
441 			vfree(vp2->v_value);
442 			(void)free(vp2);
443 			continue;
444 		}
445 		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
446 			;
447 		vp->v_link = vp2->v_link;
448 		vfree(vp2->v_name);
449 		vfree(vp2->v_value);
450 		(void)free(vp2);
451 	}
452 	return(errs);
453 }
454 
455 /*
456  * Put add users to a group.
457  */
458 int
459 group(void *v)
460 {
461 	char **argv = v;
462 	struct grouphead *gh;
463 	struct group *gp;
464 	char **ap, *gname, **p;
465 	int h, s;
466 
467 	if (*argv == NULL) {
468 		for (h = 0, s = 1; h < HSHSIZE; h++)
469 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
470 				s++;
471 		ap = (char **)salloc(s * sizeof(*ap));
472 		for (h = 0, p = ap; h < HSHSIZE; h++)
473 			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
474 				*p++ = gh->g_name;
475 		*p = NULL;
476 		sort(ap);
477 		for (p = ap; *p != NULL; p++)
478 			printgroup(*p);
479 		return(0);
480 	}
481 	if (argv[1] == NULL) {
482 		printgroup(*argv);
483 		return(0);
484 	}
485 	gname = *argv;
486 	h = hash(gname);
487 	if ((gh = findgroup(gname)) == NULL) {
488 		if ((gh = (struct grouphead *)calloc(1, sizeof(*gh))) == NULL)
489 			errx(1, "Out of memory");
490 		gh->g_name = vcopy(gname);
491 		gh->g_list = NULL;
492 		gh->g_link = groups[h];
493 		groups[h] = gh;
494 	}
495 
496 	/*
497 	 * Insert names from the command list into the group.
498 	 * Who cares if there are duplicates?  They get tossed
499 	 * later anyway.
500 	 */
501 
502 	for (ap = argv+1; *ap != NULL; ap++) {
503 		if ((gp = (struct group *)calloc(1, sizeof(*gp))) == NULL)
504 			errx(1, "Out of memory");
505 		gp->ge_name = vcopy(*ap);
506 		gp->ge_link = gh->g_list;
507 		gh->g_list = gp;
508 	}
509 	return(0);
510 }
511 
512 /*
513  * Sort the passed string vector into ascending dictionary
514  * order.
515  */
516 void
517 sort(char **list)
518 {
519 	char **ap;
520 
521 	for (ap = list; *ap != NULL; ap++)
522 		;
523 	if (ap-list < 2)
524 		return;
525 	qsort(list, ap-list, sizeof(*list), diction);
526 }
527 
528 /*
529  * Do a dictionary order comparison of the arguments from
530  * qsort.
531  */
532 static int
533 diction(const void *a, const void *b)
534 {
535 
536 	return(strcmp(*(char **)a, *(char **)b));
537 }
538 
539 /*
540  * The do nothing command for comments.
541  */
542 /*ARGSUSED*/
543 int
544 null(void *v)
545 {
546 
547 	return(0);
548 }
549 
550 /*
551  * Change to another file.  With no argument, print information about
552  * the current file.
553  */
554 int
555 file(void *v)
556 {
557 	char **argv = v;
558 
559 	if (argv[0] == NULL) {
560 		newfileinfo(0);
561 		clearnew();
562 		return(0);
563 	}
564 	if (setfile(*argv) < 0)
565 		return(1);
566 	announce();
567 	return(0);
568 }
569 
570 /*
571  * Expand file names like echo
572  */
573 int
574 echo(void *v)
575 {
576 	char **argv = v;
577 	char **ap, *cp;
578 
579 	for (ap = argv; *ap != NULL; ap++) {
580 		cp = *ap;
581 		if ((cp = expand(cp)) != NULL) {
582 			if (ap != argv)
583 				putchar(' ');
584 			fputs(cp, stdout);
585 		}
586 	}
587 	putchar('\n');
588 	return(0);
589 }
590 
591 int
592 Respond(void *v)
593 {
594 	int *msgvec = v;
595 
596 	if (value("Replyall") == NULL)
597 		return(_Respond(msgvec));
598 	else
599 		return(_respond(msgvec));
600 }
601 
602 /*
603  * Reply to a series of messages by simply mailing to the senders
604  * and not messing around with the To: and Cc: lists as in normal
605  * reply.
606  */
607 int
608 _Respond(int *msgvec)
609 {
610 	struct header head;
611 	struct message *mp;
612 	int *ap;
613 	char *cp;
614 
615 	head.h_to = NULL;
616 	for (ap = msgvec; *ap != 0; ap++) {
617 		mp = &message[*ap - 1];
618 		touch(mp);
619 		dot = mp;
620 		if ((cp = skin(hfield("from", mp))) == NULL)
621 			cp = skin(nameof(mp, 2));
622 		head.h_to = cat(head.h_to, extract(cp, GTO));
623 	}
624 	if (head.h_to == NULL)
625 		return(0);
626 	mp = &message[msgvec[0] - 1];
627 	if ((head.h_subject = hfield("subject", mp)) == NULL)
628 		head.h_subject = hfield("subj", mp);
629 	head.h_subject = reedit(head.h_subject);
630 	head.h_cc = NULL;
631 	head.h_bcc = NULL;
632 	head.h_smopts = NULL;
633 	mail1(&head, 1);
634 	return(0);
635 }
636 
637 /*
638  * Conditional commands.  These allow one to parameterize one's
639  * .mailrc and do some things if sending, others if receiving.
640  */
641 int
642 ifcmd(void *v)
643 {
644 	char **argv = v;
645 	char *cp;
646 
647 	if (cond != CANY) {
648 		puts("Illegal nested \"if\"");
649 		return(1);
650 	}
651 	cond = CANY;
652 	cp = argv[0];
653 	switch (*cp) {
654 	case 'r': case 'R':
655 		cond = CRCV;
656 		break;
657 
658 	case 's': case 'S':
659 		cond = CSEND;
660 		break;
661 
662 	default:
663 		printf("Unrecognized if-keyword: \"%s\"\n", cp);
664 		return(1);
665 	}
666 	return(0);
667 }
668 
669 /*
670  * Implement 'else'.  This is pretty simple -- we just
671  * flip over the conditional flag.
672  */
673 int
674 elsecmd(void *v)
675 {
676 
677 	switch (cond) {
678 	case CANY:
679 		puts("\"Else\" without matching \"if\"");
680 		return(1);
681 
682 	case CSEND:
683 		cond = CRCV;
684 		break;
685 
686 	case CRCV:
687 		cond = CSEND;
688 		break;
689 
690 	default:
691 		puts("mail's idea of conditions is screwed up");
692 		cond = CANY;
693 		break;
694 	}
695 	return(0);
696 }
697 
698 /*
699  * End of if statement.  Just set cond back to anything.
700  */
701 int
702 endifcmd(void *v)
703 {
704 
705 	if (cond == CANY) {
706 		puts("\"Endif\" without matching \"if\"");
707 		return(1);
708 	}
709 	cond = CANY;
710 	return(0);
711 }
712 
713 /*
714  * Set the list of alternate names.
715  */
716 int
717 alternates(void *v)
718 {
719 	char **namelist = v;
720 	char **ap, **ap2;
721 	int c;
722 
723 	c = argcount(namelist) + 1;
724 	if (c == 1) {
725 		if (altnames == 0)
726 			return(0);
727 		for (ap = altnames; *ap; ap++)
728 			printf("%s ", *ap);
729 		putchar('\n');
730 		return(0);
731 	}
732 	if (altnames != 0)
733 		(void)free(altnames);
734 	if ((altnames = (char **)calloc(c, sizeof(char *))) == NULL)
735 		errx(1, "Out of memory");
736 	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
737 		if ((*ap2 = strdup(*ap)) == NULL)
738 			errx(1, "Out of memory");
739 	}
740 	*ap2 = 0;
741 	return(0);
742 }
743