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