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: stable/11/usr.bin/mail/cmd3.c 336953 2018-07-31 00:37:25Z markj $");
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		if ((gh = calloc(1, sizeof(*gh))) == NULL)
467			err(1, "Out of memory");
468		gh->g_name = vcopy(gname);
469		gh->g_list = NULL;
470		gh->g_link = groups[h];
471		groups[h] = gh;
472	}
473
474	/*
475	 * Insert names from the command list into the group.
476	 * Who cares if there are duplicates?  They get tossed
477	 * later anyway.
478	 */
479
480	for (ap = argv+1; *ap != NULL; ap++) {
481		if ((gp = calloc(1, sizeof(*gp))) == NULL)
482			err(1, "Out of memory");
483		gp->ge_name = vcopy(*ap);
484		gp->ge_link = gh->g_list;
485		gh->g_list = gp;
486	}
487	return (0);
488}
489
490/*
491 * Sort the passed string vecotor into ascending dictionary
492 * order.
493 */
494void
495sort(char **list)
496{
497	char **ap;
498
499	for (ap = list; *ap != NULL; ap++)
500		;
501	if (ap-list < 2)
502		return;
503	qsort(list, ap-list, sizeof(*list), diction);
504}
505
506/*
507 * Do a dictionary order comparison of the arguments from
508 * qsort.
509 */
510int
511diction(const void *a, const void *b)
512{
513	return (strcmp(*(const char **)a, *(const char **)b));
514}
515
516/*
517 * The do nothing command for comments.
518 */
519
520/*ARGSUSED*/
521int
522null(int e __unused)
523{
524	return (0);
525}
526
527/*
528 * Change to another file.  With no argument, print information about
529 * the current file.
530 */
531int
532file(char **argv)
533{
534
535	if (argv[0] == NULL) {
536		newfileinfo(0);
537		return (0);
538	}
539	if (setfile(*argv) < 0)
540		return (1);
541	announce();
542	return (0);
543}
544
545/*
546 * Expand file names like echo
547 */
548int
549echo(char **argv)
550{
551	char **ap, *cp;
552
553	for (ap = argv; *ap != NULL; ap++) {
554		cp = *ap;
555		if ((cp = expand(cp)) != NULL) {
556			if (ap != argv)
557				printf(" ");
558			printf("%s", cp);
559		}
560	}
561	printf("\n");
562	return (0);
563}
564
565int
566Respond(int *msgvec)
567{
568	if (value("Replyall") == NULL && value("flipr") == NULL)
569		return (doRespond(msgvec));
570	else
571		return (dorespond(msgvec));
572}
573
574/*
575 * Reply to a series of messages by simply mailing to the senders
576 * and not messing around with the To: and Cc: lists as in normal
577 * reply.
578 */
579int
580doRespond(int msgvec[])
581{
582	struct header head;
583	struct message *mp;
584	int *ap;
585	char *cp, *mid;
586
587	head.h_to = NULL;
588	for (ap = msgvec; *ap != 0; ap++) {
589		mp = &message[*ap - 1];
590		touch(mp);
591		dot = mp;
592		if ((cp = skin(hfield("from", mp))) == NULL)
593			cp = skin(nameof(mp, 2));
594		head.h_to = cat(head.h_to, extract(cp, GTO));
595		mid = skin(hfield("message-id", mp));
596	}
597	if (head.h_to == NULL)
598		return (0);
599	mp = &message[msgvec[0] - 1];
600	if ((head.h_subject = hfield("subject", mp)) == NULL)
601		head.h_subject = hfield("subj", mp);
602	head.h_subject = reedit(head.h_subject);
603	head.h_cc = NULL;
604	head.h_bcc = NULL;
605	head.h_smopts = NULL;
606	head.h_replyto = value("REPLYTO");
607	head.h_inreplyto = mid;
608	mail1(&head, 1);
609	return (0);
610}
611
612/*
613 * Conditional commands.  These allow one to parameterize one's
614 * .mailrc and do some things if sending, others if receiving.
615 */
616int
617ifcmd(char **argv)
618{
619	char *cp;
620
621	if (cond != CANY) {
622		printf("Illegal nested \"if\"\n");
623		return (1);
624	}
625	cond = CANY;
626	cp = argv[0];
627	switch (*cp) {
628	case 'r': case 'R':
629		cond = CRCV;
630		break;
631
632	case 's': case 'S':
633		cond = CSEND;
634		break;
635
636	default:
637		printf("Unrecognized if-keyword: \"%s\"\n", cp);
638		return (1);
639	}
640	return (0);
641}
642
643/*
644 * Implement 'else'.  This is pretty simple -- we just
645 * flip over the conditional flag.
646 */
647int
648elsecmd(void)
649{
650
651	switch (cond) {
652	case CANY:
653		printf("\"Else\" without matching \"if\"\n");
654		return (1);
655
656	case CSEND:
657		cond = CRCV;
658		break;
659
660	case CRCV:
661		cond = CSEND;
662		break;
663
664	default:
665		printf("Mail's idea of conditions is screwed up\n");
666		cond = CANY;
667		break;
668	}
669	return (0);
670}
671
672/*
673 * End of if statement.  Just set cond back to anything.
674 */
675int
676endifcmd(void)
677{
678
679	if (cond == CANY) {
680		printf("\"Endif\" without matching \"if\"\n");
681		return (1);
682	}
683	cond = CANY;
684	return (0);
685}
686
687/*
688 * Set the list of alternate names.
689 */
690int
691alternates(char **namelist)
692{
693	int c;
694	char **ap, **ap2, *cp;
695
696	c = argcount(namelist) + 1;
697	if (c == 1) {
698		if (altnames == 0)
699			return (0);
700		for (ap = altnames; *ap != NULL; ap++)
701			printf("%s ", *ap);
702		printf("\n");
703		return (0);
704	}
705	if (altnames != 0)
706		(void)free(altnames);
707	if ((altnames = calloc((unsigned)c, sizeof(char *))) == NULL)
708		err(1, "Out of memory");
709	for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
710		cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
711		strcpy(cp, *ap);
712		*ap2 = cp;
713	}
714	*ap2 = 0;
715	return (0);
716}
717