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