cmd3.c revision 1.28
1/*	$NetBSD: cmd3.c,v 1.28 2006/03/03 13:36:27 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.28 2006/03/03 13:36:27 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
199/*
200 * Reply to a list of messages.  Extract each name from the
201 * message header and send them off to mail1()
202 */
203int
204_respond(int *msgvec)
205{
206	struct message *mp;
207	char *cp, *rcv, *replyto;
208	char **ap;
209	struct name *np;
210	struct header head;
211
212	if (msgvec[1] != 0) {
213		(void)printf("Sorry, can't reply to multiple messages at once\n");
214		return(1);
215	}
216	mp = &message[msgvec[0] - 1];
217	touch(mp);
218	dot = mp;
219	if ((rcv = skin(hfield("from", mp))) == NULL)
220		rcv = skin(nameof(mp, 1));
221	if ((replyto = skin(hfield("reply-to", mp))) != NULL)
222		np = extract(replyto, GTO);
223	else if ((cp = skin(hfield("to", mp))) != NULL)
224		np = extract(cp, GTO);
225	else
226		np = NULL;
227	np = elide(np);
228	/*
229	 * Delete my name from the reply list,
230	 * and with it, all my alternate names.
231	 */
232	np = delname(np, myname);
233	if (altnames)
234		for (ap = altnames; *ap; ap++)
235			np = delname(np, *ap);
236	if (np != NULL && replyto == NULL)
237		np = cat(np, extract(rcv, GTO));
238	else if (np == NULL) {
239		if (replyto != NULL)
240			(void)printf("Empty reply-to field -- replying to author\n");
241		np = extract(rcv, GTO);
242	}
243	head.h_to = np;
244	if ((head.h_subject = hfield("subject", mp)) == NULL)
245		head.h_subject = hfield("subj", mp);
246	head.h_subject = reedit(head.h_subject);
247	if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
248		np = elide(extract(cp, GCC));
249		np = delname(np, myname);
250		if (altnames != 0)
251			for (ap = altnames; *ap; ap++)
252				np = delname(np, *ap);
253		head.h_cc = np;
254	} else
255		head.h_cc = NULL;
256	head.h_bcc = NULL;
257	head.h_smopts = NULL;
258	mail1(&head, 1);
259	return(0);
260}
261
262/*
263 * Modify the subject we are replying to to begin with Re: if
264 * it does not already.
265 */
266char *
267reedit(char *subj)
268{
269	char *newsubj;
270
271	if (subj == NULL)
272		return NULL;
273	if ((subj[0] == 'r' || subj[0] == 'R') &&
274	    (subj[1] == 'e' || subj[1] == 'E') &&
275	    subj[2] == ':')
276		return subj;
277	newsubj = salloc(strlen(subj) + 5);
278	(void)strcpy(newsubj, "Re: ");
279	(void)strcpy(newsubj + 4, 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	struct message *mp;
292	int *ip, mesg;
293
294	if (edit) {
295		(void)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		(void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
339		    /*LINTED*/
340		    (unsigned long long)mp->m_size);
341	}
342	return(0);
343}
344
345/*
346 * Quit quickly.  If we are sourcing, just pop the input level
347 * by returning an error.
348 */
349int
350/*ARGSUSED*/
351rexit(void *v)
352{
353	if (sourcing)
354		return(1);
355	exit(0);
356	/*NOTREACHED*/
357}
358
359/*
360 * Set or display a variable value.  Syntax is similar to that
361 * of csh.
362 */
363int
364set(void *v)
365{
366	char **arglist = v;
367	struct var *vp;
368	const char *cp;
369	char varbuf[BUFSIZ], **ap, **p;
370	int errs, h, s;
371	size_t l;
372
373	if (*arglist == NULL) {
374		for (h = 0, s = 1; h < HSHSIZE; h++)
375			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
376				s++;
377		ap = salloc(s * sizeof *ap);
378		for (h = 0, p = ap; h < HSHSIZE; h++)
379			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
380				*p++ = vp->v_name;
381		*p = NULL;
382		sort(ap);
383		for (p = ap; *p != NULL; p++)
384			(void)printf("%s\t%s\n", *p, value(*p));
385		return(0);
386	}
387	errs = 0;
388	for (ap = arglist; *ap != NULL; ap++) {
389		cp = *ap;
390		while (*cp != '=' && *cp != '\0')
391			++cp;
392		l = cp - *ap;
393		if (l >= sizeof varbuf)
394			l = sizeof varbuf - 1;
395		(void)strncpy(varbuf, *ap, l);
396		varbuf[l] = '\0';
397		if (*cp == '\0')
398			cp = "";
399		else
400			cp++;
401		if (equal(varbuf, "")) {
402			(void)printf("Non-null variable name required\n");
403			errs++;
404			continue;
405		}
406		assign(varbuf, cp);
407	}
408	return(errs);
409}
410
411/*
412 * Unset a bunch of variable values.
413 */
414int
415unset(void *v)
416{
417	char **arglist = v;
418	struct var *vp, *vp2;
419	int errs, h;
420	char **ap;
421
422	errs = 0;
423	for (ap = arglist; *ap != NULL; ap++) {
424		if ((vp2 = lookup(*ap)) == NULL) {
425			if (getenv(*ap)) {
426				(void)unsetenv(*ap);
427			} else if (!sourcing) {
428				(void)printf("\"%s\": undefined variable\n", *ap);
429				errs++;
430			}
431			continue;
432		}
433		h = hash(*ap);
434		if (vp2 == variables[h]) {
435			variables[h] = variables[h]->v_link;
436			v_free(vp2->v_name);
437                        v_free(vp2->v_value);
438			free(vp2);
439			continue;
440		}
441		for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
442			;
443		vp->v_link = vp2->v_link;
444                v_free(vp2->v_name);
445                v_free(vp2->v_value);
446		free(vp2);
447	}
448	return(errs);
449}
450
451/*
452 * Put add users to a group.
453 */
454int
455group(void *v)
456{
457	char **argv = v;
458	struct grouphead *gh;
459	struct group *gp;
460	int h;
461	int s;
462	char **ap, *gname, **p;
463
464	if (*argv == NULL) {
465		for (h = 0, s = 1; h < HSHSIZE; h++)
466			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
467				s++;
468		ap = salloc(s * sizeof *ap);
469		for (h = 0, p = ap; h < HSHSIZE; h++)
470			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
471				*p++ = gh->g_name;
472		*p = NULL;
473		sort(ap);
474		for (p = ap; *p != NULL; p++)
475			printgroup(*p);
476		return(0);
477	}
478	if (argv[1] == NULL) {
479		printgroup(*argv);
480		return(0);
481	}
482	gname = *argv;
483	h = hash(gname);
484	if ((gh = findgroup(gname)) == NULL) {
485		gh = (struct grouphead *) calloc(1, sizeof *gh);
486		gh->g_name = vcopy(gname);
487		gh->g_list = NULL;
488		gh->g_link = groups[h];
489		groups[h] = gh;
490	}
491
492	/*
493	 * Insert names from the command list into the group.
494	 * Who cares if there are duplicates?  They get tossed
495	 * later anyway.
496	 */
497
498	for (ap = argv+1; *ap != NULL; ap++) {
499		gp = (struct group *) calloc(1, sizeof *gp);
500		gp->ge_name = vcopy(*ap);
501		gp->ge_link = gh->g_list;
502		gh->g_list = gp;
503	}
504	return(0);
505}
506
507/*
508 * The unalias command takes a list of alises
509 * and discards the remembered groups of users.
510 */
511int
512unalias(void *v)
513{
514	char **ap;
515
516	for (ap = v; *ap != NULL; ap++)
517		(void)delgroup(*ap);
518	return 0;
519}
520
521/*
522 * Delete the named group alias. Return zero if the group was
523 * successfully deleted, or -1 if there was no such group.
524 */
525static int
526delgroup(const char *name)
527{
528	struct grouphead *gh, *p;
529	struct group *g;
530	int h;
531
532	h = hash(name);
533	for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
534		if (strcmp(gh->g_name, name) == 0) {
535			if (p == NULL)
536				groups[h] = gh->g_link;
537			else
538				p->g_link = gh->g_link;
539			while (gh->g_list != NULL) {
540				g = gh->g_list;
541				gh->g_list = g->ge_link;
542				free(g->ge_name);
543				free(g);
544			}
545			free(gh->g_name);
546			free(gh);
547			return 0;
548		}
549	return -1;
550}
551
552/*
553 * Sort the passed string vecotor into ascending dictionary
554 * order.
555 */
556void
557sort(char **list)
558{
559	char **ap;
560
561	for (ap = list; *ap != NULL; ap++)
562		;
563	if (ap-list < 2)
564		return;
565	qsort(list, (size_t)(ap-list), sizeof(*list), diction);
566}
567
568/*
569 * Do a dictionary order comparison of the arguments from
570 * qsort.
571 */
572static int
573diction(const void *a, const void *b)
574{
575	return(strcmp(*(const char *const *)a, *(const char *const *)b));
576}
577
578/*
579 * The do nothing command for comments.
580 */
581
582/*ARGSUSED*/
583int
584null(void *v)
585{
586	return 0;
587}
588
589/*
590 * Change to another file.  With no argument, print information about
591 * the current file.
592 */
593int
594file(void *v)
595{
596	char **argv = v;
597
598	if (argv[0] == NULL) {
599		(void)newfileinfo(0);
600		return 0;
601	}
602	if (setfile(*argv) < 0)
603		return 1;
604	announce();
605	return 0;
606}
607
608/*
609 * Expand file names like echo
610 */
611int
612echo(void *v)
613{
614	char **argv = v;
615	char **ap;
616	const char *cp;
617
618	for (ap = argv; *ap != NULL; ap++) {
619		cp = *ap;
620		if ((cp = expand(cp)) != NULL) {
621			if (ap != argv)
622				(void)putchar(' ');
623			(void)printf("%s", cp);
624		}
625	}
626	(void)putchar('\n');
627	return 0;
628}
629
630int
631Respond(void *v)
632{
633	int *msgvec = v;
634	if (value("Replyall") == NULL)
635		return (_Respond(msgvec));
636	else
637		return (_respond(msgvec));
638}
639
640/*
641 * Reply to a series of messages by simply mailing to the senders
642 * and not messing around with the To: and Cc: lists as in normal
643 * reply.
644 */
645int
646_Respond(int msgvec[])
647{
648	struct header head;
649	struct message *mp;
650	int *ap;
651	char *cp;
652
653	head.h_to = NULL;
654	for (ap = msgvec; *ap != 0; ap++) {
655		mp = &message[*ap - 1];
656		touch(mp);
657		dot = mp;
658		if ((cp = skin(hfield("from", mp))) == NULL)
659			cp = skin(nameof(mp, 2));
660		head.h_to = cat(head.h_to, extract(cp, GTO));
661	}
662	if (head.h_to == NULL)
663		return 0;
664	mp = &message[msgvec[0] - 1];
665	if ((head.h_subject = hfield("subject", mp)) == NULL)
666		head.h_subject = hfield("subj", mp);
667	head.h_subject = reedit(head.h_subject);
668	head.h_cc = NULL;
669	head.h_bcc = NULL;
670	head.h_smopts = NULL;
671	mail1(&head, 1);
672	return 0;
673}
674
675/*
676 * Conditional commands.  These allow one to parameterize one's
677 * .mailrc and do some things if sending, others if receiving.
678 */
679int
680ifcmd(void *v)
681{
682	char **argv = v;
683	char *cp;
684
685	if (cond != CANY) {
686		(void)printf("Illegal nested \"if\"\n");
687		return(1);
688	}
689	cond = CANY;
690	cp = argv[0];
691	switch (*cp) {
692	case 'r': case 'R':
693		cond = CRCV;
694		break;
695
696	case 's': case 'S':
697		cond = CSEND;
698		break;
699
700	default:
701		(void)printf("Unrecognized if-keyword: \"%s\"\n", cp);
702		return(1);
703	}
704	return(0);
705}
706
707/*
708 * Implement 'else'.  This is pretty simple -- we just
709 * flip over the conditional flag.
710 */
711int
712/*ARGSUSED*/
713elsecmd(void *v)
714{
715
716	switch (cond) {
717	case CANY:
718		(void)printf("\"Else\" without matching \"if\"\n");
719		return(1);
720
721	case CSEND:
722		cond = CRCV;
723		break;
724
725	case CRCV:
726		cond = CSEND;
727		break;
728
729	default:
730		(void)printf("Mail's idea of conditions is screwed up\n");
731		cond = CANY;
732		break;
733	}
734	return(0);
735}
736
737/*
738 * End of if statement.  Just set cond back to anything.
739 */
740int
741/*ARGSUSED*/
742endifcmd(void *v)
743{
744
745	if (cond == CANY) {
746		(void)printf("\"Endif\" without matching \"if\"\n");
747		return(1);
748	}
749	cond = CANY;
750	return(0);
751}
752
753/*
754 * Set the list of alternate names.
755 */
756int
757alternates(void *v)
758{
759	char **namelist = v;
760	int c;
761	char **ap, **ap2, *cp;
762
763	c = argcount(namelist) + 1;
764	if (c == 1) {
765		if (altnames == 0)
766			return(0);
767		for (ap = altnames; *ap; ap++)
768			(void)printf("%s ", *ap);
769		(void)printf("\n");
770		return(0);
771	}
772	if (altnames != 0)
773		free(altnames);
774	altnames = (char **) calloc((unsigned) c, sizeof (char *));
775	for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
776		cp = calloc((unsigned) strlen(*ap) + 1, sizeof (char));
777		(void)strcpy(cp, *ap);
778		*ap2 = cp;
779	}
780	*ap2 = 0;
781	return(0);
782}
783