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