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