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