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