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