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