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