xref: /netbsd-src/usr.bin/mail/cmd3.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
1 /*	$NetBSD: cmd3.c,v 1.25 2003/11/10 21:40:22 ross 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.25 2003/11/10 21:40:22 ross 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, 0, 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, 0, 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;
364 	char varbuf[BUFSIZ], **ap, **p;
365 	int errs, h, s;
366 	size_t l;
367 
368 	if (*arglist == NULL) {
369 		for (h = 0, s = 1; h < HSHSIZE; h++)
370 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
371 				s++;
372 		ap = (char **) salloc(s * sizeof *ap);
373 		for (h = 0, p = ap; h < HSHSIZE; h++)
374 			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
375 				*p++ = vp->v_name;
376 		*p = NULL;
377 		sort(ap);
378 		for (p = ap; *p != NULL; p++)
379 			printf("%s\t%s\n", *p, value(*p));
380 		return(0);
381 	}
382 	errs = 0;
383 	for (ap = arglist; *ap != NULL; ap++) {
384 		cp = *ap;
385 		while (*cp != '=' && *cp != '\0')
386 			++cp;
387 		l = cp - *ap;
388 		if (l >= sizeof varbuf)
389 			l = sizeof varbuf - 1;
390 		strncpy(varbuf, *ap, l);
391 		varbuf[l] = '\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