cmd3.c revision 1.32
1/*	$NetBSD: cmd3.c,v 1.32 2006/10/31 20:07:32 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.32 2006/10/31 20:07:32 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 * Mark all given messages as read.
363 */
364int
365markread(void *v)
366{
367	int *msgvec = v;
368	int *ip;
369
370	for (ip = msgvec; *ip != 0; ip++) {
371		dot = &message[*ip - 1];
372		dot->m_flag &= ~(MNEW|MTOUCH);
373		dot->m_flag |= MREAD|MSTATUS;
374	}
375	return(0);
376}
377
378/*
379 * Print the size of each message.
380 */
381int
382messize(void *v)
383{
384	int *msgvec = v;
385	struct message *mp;
386	int *ip, mesg;
387
388	for (ip = msgvec; *ip != 0; ip++) {
389		mesg = *ip;
390		mp = &message[mesg - 1];
391		(void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
392		    (unsigned long long)mp->m_size);
393	}
394	return(0);
395}
396
397/*
398 * Quit quickly.  If we are sourcing, just pop the input level
399 * by returning an error.
400 */
401int
402/*ARGSUSED*/
403rexit(void *v __unused)
404{
405	if (sourcing)
406		return(1);
407	exit(0);
408	/*NOTREACHED*/
409}
410
411/*
412 * Set or display a variable value.  Syntax is similar to that
413 * of csh.
414 */
415int
416set(void *v)
417{
418	char **arglist = v;
419	struct var *vp;
420	const char *cp;
421	char varbuf[BUFSIZ], **ap, **p;
422	int errs, h, s;
423	size_t l;
424
425	if (*arglist == NULL) {
426		for (h = 0, s = 1; h < HSHSIZE; h++)
427			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
428				s++;
429		ap = salloc(s * sizeof *ap);
430		for (h = 0, p = ap; h < HSHSIZE; h++)
431			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
432				*p++ = vp->v_name;
433		*p = NULL;
434		sort(ap);
435		for (p = ap; *p != NULL; p++)
436			(void)printf("%s\t%s\n", *p, value(*p));
437		return(0);
438	}
439	errs = 0;
440	for (ap = arglist; *ap != NULL; ap++) {
441		cp = *ap;
442		while (*cp != '=' && *cp != '\0')
443			++cp;
444		l = cp - *ap;
445		if (l >= sizeof varbuf)
446			l = sizeof varbuf - 1;
447		(void)strncpy(varbuf, *ap, l);
448		varbuf[l] = '\0';
449		if (*cp == '\0')
450			cp = "";
451		else
452			cp++;
453		if (equal(varbuf, "")) {
454			(void)printf("Non-null variable name required\n");
455			errs++;
456			continue;
457		}
458		assign(varbuf, cp);
459	}
460	return(errs);
461}
462
463/*
464 * Unset a bunch of variable values.
465 */
466int
467unset(void *v)
468{
469	char **arglist = v;
470	struct var *vp, *vp2;
471	int errs, h;
472	char **ap;
473
474	errs = 0;
475	for (ap = arglist; *ap != NULL; ap++) {
476		if ((vp2 = lookup(*ap)) == NULL) {
477			if (getenv(*ap)) {
478				(void)unsetenv(*ap);
479			} else if (!sourcing) {
480				(void)printf("\"%s\": undefined variable\n", *ap);
481				errs++;
482			}
483			continue;
484		}
485		h = hash(*ap);
486		if (vp2 == variables[h]) {
487			variables[h] = variables[h]->v_link;
488			v_free(vp2->v_name);
489                        v_free(vp2->v_value);
490			free(vp2);
491			continue;
492		}
493		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
494			;
495		vp->v_link = vp2->v_link;
496                v_free(vp2->v_name);
497                v_free(vp2->v_value);
498		free(vp2);
499	}
500	return(errs);
501}
502
503/*
504 * Show a variable value.
505 */
506int
507show(void *v)
508{
509	char **arglist = v;
510	struct var *vp;
511	char **ap, **p;
512	int h, s;
513
514	if (*arglist == NULL) {
515		for (h = 0, s = 1; h < HSHSIZE; h++)
516			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
517				s++;
518		ap = salloc(s * sizeof *ap);
519		for (h = 0, p = ap; h < HSHSIZE; h++)
520			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
521				*p++ = vp->v_name;
522		*p = NULL;
523		sort(ap);
524		for (p = ap; *p != NULL; p++)
525			(void)printf("%s=%s\n", *p, value(*p));
526		return(0);
527	}
528
529	for (ap = arglist; *ap != NULL; ap++) {
530		char *val = value(*ap);
531		(void)printf("%s=%s\n", *ap, val ? val : "<null>");
532	}
533	return 0;
534}
535
536
537/*
538 * Put add users to a group.
539 */
540int
541group(void *v)
542{
543	char **argv = v;
544	struct grouphead *gh;
545	struct group *gp;
546	int h;
547	int s;
548	char **ap, *gname, **p;
549
550	if (*argv == NULL) {
551		for (h = 0, s = 1; h < HSHSIZE; h++)
552			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
553				s++;
554		ap = salloc(s * sizeof *ap);
555		for (h = 0, p = ap; h < HSHSIZE; h++)
556			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
557				*p++ = gh->g_name;
558		*p = NULL;
559		sort(ap);
560		for (p = ap; *p != NULL; p++)
561			printgroup(*p);
562		return(0);
563	}
564	if (argv[1] == NULL) {
565		printgroup(*argv);
566		return(0);
567	}
568	gname = *argv;
569	h = hash(gname);
570	if ((gh = findgroup(gname)) == NULL) {
571		gh = (struct grouphead *) ecalloc(1, sizeof *gh);
572		gh->g_name = vcopy(gname);
573		gh->g_list = NULL;
574		gh->g_link = groups[h];
575		groups[h] = gh;
576	}
577
578	/*
579	 * Insert names from the command list into the group.
580	 * Who cares if there are duplicates?  They get tossed
581	 * later anyway.
582	 */
583
584	for (ap = argv + 1; *ap != NULL; ap++) {
585		gp = (struct group *) ecalloc(1, sizeof *gp);
586		gp->ge_name = vcopy(*ap);
587		gp->ge_link = gh->g_list;
588		gh->g_list = gp;
589	}
590	return(0);
591}
592
593/*
594 * The unalias command takes a list of alises
595 * and discards the remembered groups of users.
596 */
597int
598unalias(void *v)
599{
600	char **ap;
601
602	for (ap = v; *ap != NULL; ap++)
603		(void)delgroup(*ap);
604	return 0;
605}
606
607/*
608 * Delete the named group alias. Return zero if the group was
609 * successfully deleted, or -1 if there was no such group.
610 */
611static int
612delgroup(const char *name)
613{
614	struct grouphead *gh, *p;
615	struct group *g;
616	int h;
617
618	h = hash(name);
619	for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
620		if (strcmp(gh->g_name, name) == 0) {
621			if (p == NULL)
622				groups[h] = gh->g_link;
623			else
624				p->g_link = gh->g_link;
625			while (gh->g_list != NULL) {
626				g = gh->g_list;
627				gh->g_list = g->ge_link;
628				free(g->ge_name);
629				free(g);
630			}
631			free(gh->g_name);
632			free(gh);
633			return 0;
634		}
635	return -1;
636}
637
638/*
639 * Sort the passed string vecotor into ascending dictionary
640 * order.
641 */
642void
643sort(char **list)
644{
645	char **ap;
646
647	for (ap = list; *ap != NULL; ap++)
648		;
649	if (ap-list < 2)
650		return;
651	qsort(list, (size_t)(ap-list), sizeof(*list), diction);
652}
653
654/*
655 * Do a dictionary order comparison of the arguments from
656 * qsort.
657 */
658static int
659diction(const void *a, const void *b)
660{
661	return(strcmp(*(const char *const *)a, *(const char *const *)b));
662}
663
664/*
665 * The do nothing command for comments.
666 */
667
668/*ARGSUSED*/
669int
670null(void *v __unused)
671{
672	return 0;
673}
674
675/*
676 * Change to another file.  With no argument, print information about
677 * the current file.
678 */
679int
680file(void *v)
681{
682	char **argv = v;
683
684	if (argv[0] == NULL) {
685		(void)newfileinfo(0);
686		return 0;
687	}
688	if (setfile(*argv) < 0)
689		return 1;
690	announce();
691	return 0;
692}
693
694/*
695 * Expand file names like echo
696 */
697int
698echo(void *v)
699{
700	char **argv = v;
701	char **ap;
702	const char *cp;
703
704	for (ap = argv; *ap != NULL; ap++) {
705		cp = *ap;
706		if ((cp = expand(cp)) != NULL) {
707			if (ap != argv)
708				(void)putchar(' ');
709			(void)printf("%s", cp);
710		}
711	}
712	(void)putchar('\n');
713	return 0;
714}
715
716int
717Respond(void *v)
718{
719	int *msgvec = v;
720	if (value("Replyall") == NULL)
721		return (_Respond(msgvec));
722	else
723		return (_respond(msgvec));
724}
725
726/*
727 * Reply to a series of messages by simply mailing to the senders
728 * and not messing around with the To: and Cc: lists as in normal
729 * reply.
730 */
731int
732_Respond(int msgvec[])
733{
734	struct header head;
735	struct message *mp;
736	int *ap;
737	char *cp;
738
739	head.h_to = NULL;
740	for (ap = msgvec; *ap != 0; ap++) {
741		mp = &message[*ap - 1];
742		touch(mp);
743		dot = mp;
744		if ((cp = skin(hfield("from", mp))) == NULL)
745			cp = skin(nameof(mp, 2));
746		head.h_to = cat(head.h_to, extract(cp, GTO));
747	}
748	if (head.h_to == NULL)
749		return 0;
750	mp = &message[msgvec[0] - 1];
751	if ((head.h_subject = hfield("subject", mp)) == NULL)
752		head.h_subject = hfield("subj", mp);
753	head.h_subject = reedit(head.h_subject);
754	head.h_cc = NULL;
755	head.h_bcc = NULL;
756	head.h_smopts = set_smopts(mp);
757#ifdef MIME_SUPPORT
758	head.h_attach = NULL;
759#endif
760	mail1(&head, 1);
761	return 0;
762}
763
764/*
765 * Conditional commands.  These allow one to parameterize one's
766 * .mailrc and do some things if sending, others if receiving.
767 */
768int
769ifcmd(void *v)
770{
771	char **argv = v;
772	char *cp;
773
774	if (cond != CANY) {
775		(void)printf("Illegal nested \"if\"\n");
776		return(1);
777	}
778	cond = CANY;
779	cp = argv[0];
780	switch (*cp) {
781	case 'r': case 'R':
782		cond = CRCV;
783		break;
784
785	case 's': case 'S':
786		cond = CSEND;
787		break;
788
789	default:
790		(void)printf("Unrecognized if-keyword: \"%s\"\n", cp);
791		return(1);
792	}
793	return(0);
794}
795
796/*
797 * Implement 'else'.  This is pretty simple -- we just
798 * flip over the conditional flag.
799 */
800int
801/*ARGSUSED*/
802elsecmd(void *v __unused)
803{
804
805	switch (cond) {
806	case CANY:
807		(void)printf("\"Else\" without matching \"if\"\n");
808		return(1);
809
810	case CSEND:
811		cond = CRCV;
812		break;
813
814	case CRCV:
815		cond = CSEND;
816		break;
817
818	default:
819		(void)printf("Mail's idea of conditions is screwed up\n");
820		cond = CANY;
821		break;
822	}
823	return(0);
824}
825
826/*
827 * End of if statement.  Just set cond back to anything.
828 */
829int
830/*ARGSUSED*/
831endifcmd(void *v __unused)
832{
833
834	if (cond == CANY) {
835		(void)printf("\"Endif\" without matching \"if\"\n");
836		return(1);
837	}
838	cond = CANY;
839	return(0);
840}
841
842/*
843 * Set the list of alternate names.
844 */
845int
846alternates(void *v)
847{
848	char **namelist = v;
849	int c;
850	char **ap, **ap2, *cp;
851
852	c = argcount(namelist) + 1;
853	if (c == 1) {
854		if (altnames == 0)
855			return(0);
856		for (ap = altnames; *ap; ap++)
857			(void)printf("%s ", *ap);
858		(void)printf("\n");
859		return(0);
860	}
861	if (altnames != 0)
862		free(altnames);
863	altnames = (char **) ecalloc((unsigned) c, sizeof (char *));
864	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
865		cp = ecalloc((unsigned) strlen(*ap) + 1, sizeof (char));
866		(void)strcpy(cp, *ap);
867		*ap2 = cp;
868	}
869	*ap2 = 0;
870	return(0);
871}
872