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