cmd3.c revision 78193
155714Skris/*
255714Skris * Copyright (c) 1980, 1993
355714Skris *	The Regents of the University of California.  All rights reserved.
455714Skris *
555714Skris * Redistribution and use in source and binary forms, with or without
655714Skris * modification, are permitted provided that the following conditions
755714Skris * are met:
876866Skris * 1. Redistributions of source code must retain the above copyright
976866Skris *    notice, this list of conditions and the following disclaimer.
1076866Skris * 2. Redistributions in binary form must reproduce the above copyright
1176866Skris *    notice, this list of conditions and the following disclaimer in the
1276866Skris *    documentation and/or other materials provided with the distribution.
1376866Skris * 3. All advertising materials mentioning features or use of this software
1476866Skris *    must display the following acknowledgement:
1576866Skris *	This product includes software developed by the University of
1676866Skris *	California, Berkeley and its contributors.
1776866Skris * 4. Neither the name of the University nor the names of its contributors
1876866Skris *    may be used to endorse or promote products derived from this software
1976866Skris *    without specific prior written permission.
2076866Skris *
2176866Skris * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2276866Skris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2376866Skris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2476866Skris * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2576866Skris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2676866Skris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2776866Skris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2876866Skris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2976866Skris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3076866Skris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3176866Skris * SUCH DAMAGE.
3276866Skris */
3368651Skris
3468651Skris#ifndef lint
3568651Skris#if 0
3668651Skrisstatic char sccsid[] = "@(#)cmd3.c	8.1 (Berkeley) 6/6/93";
3768651Skris#endif
3868651Skrisstatic const char rcsid[] =
3968651Skris  "$FreeBSD: head/usr.bin/mail/cmd3.c 78193 2001-06-14 01:08:30Z mikeh $";
4068651Skris#endif /* not lint */
4168651Skris
4268651Skris#include "rcv.h"
4368651Skris#include "extern.h"
4468651Skris
4568651Skris/*
4668651Skris * Mail -- a mail program
4768651Skris *
4859191Skris * Still more user commands.
4959191Skris */
5059191Skris
5159191Skris/*
5259191Skris * Process a shell escape by saving signals, ignoring signals,
5359191Skris * and forking a sh -c
5459191Skris */
5559191Skrisint
5659191Skrisshell(str)
5759191Skris	char *str;
5859191Skris{
5959191Skris	sig_t sigint = signal(SIGINT, SIG_IGN);
6059191Skris	char *sh;
6159191Skris	char cmd[BUFSIZ];
6259191Skris
6359191Skris	if (strlcpy(cmd, str, sizeof(cmd)) >= sizeof(cmd))
6459191Skris		return (1);
6559191Skris	if (bangexp(cmd, sizeof(cmd)) < 0)
6659191Skris		return (1);
6759191Skris	if ((sh = value("SHELL")) == NULL)
6859191Skris		sh = _PATH_CSHELL;
6959191Skris	(void)run_command(sh, 0, -1, -1, "-c", cmd, NULL);
7059191Skris	(void)signal(SIGINT, sigint);
7159191Skris	printf("!\n");
7259191Skris	return (0);
7359191Skris}
7459191Skris
7559191Skris/*
7659191Skris * Fork an interactive shell.
7759191Skris */
7859191Skris/*ARGSUSED*/
7959191Skrisint
8059191Skrisdosh(str)
8159191Skris	char *str;
8259191Skris{
8359191Skris	sig_t sigint = signal(SIGINT, SIG_IGN);
8459191Skris	char *sh;
8559191Skris
8659191Skris	if ((sh = value("SHELL")) == NULL)
8759191Skris		sh = _PATH_CSHELL;
8859191Skris	(void)run_command(sh, 0, -1, -1, NULL, NULL, NULL);
8959191Skris	(void)signal(SIGINT, sigint);
9059191Skris	printf("\n");
9159191Skris	return (0);
9259191Skris}
9355714Skris
9455714Skris/*
9555714Skris * Expand the shell escape by expanding unescaped !'s into the
9655714Skris * last issued command where possible.
9755714Skris */
9855714Skrisint
9955714Skrisbangexp(str, strsize)
10055714Skris	char *str;
10155714Skris	size_t strsize;
10255714Skris{
10355714Skris	char bangbuf[BUFSIZ];
10455714Skris	static char lastbang[BUFSIZ];
10555714Skris	char *cp, *cp2;
10655714Skris	int n, changed = 0;
10755714Skris
10855714Skris	cp = str;
10955714Skris	cp2 = bangbuf;
11055714Skris	n = sizeof(bangbuf);
11155714Skris	while (*cp != '\0') {
11255714Skris		if (*cp == '!') {
11355714Skris			if (n < strlen(lastbang)) {
11455714Skrisoverf:
11555714Skris				printf("Command buffer overflow\n");
11655714Skris				return (-1);
11755714Skris			}
11855714Skris			changed++;
11955714Skris			if (strlcpy(cp2, lastbang, sizeof(bangbuf) - (cp2 - bangbuf))
12055714Skris			    >= sizeof(bangbuf) - (cp2 - bangbuf))
12155714Skris				goto overf;
12255714Skris			cp2 += strlen(lastbang);
12355714Skris			n -= strlen(lastbang);
12455714Skris			cp++;
12555714Skris			continue;
12655714Skris		}
12755714Skris		if (*cp == '\\' && cp[1] == '!') {
12855714Skris			if (--n <= 1)
12955714Skris				goto overf;
13055714Skris			*cp2++ = '!';
13155714Skris			cp += 2;
13255714Skris			changed++;
13355714Skris		}
13455714Skris		if (--n <= 1)
13555714Skris			goto overf;
13655714Skris		*cp2++ = *cp++;
13755714Skris	}
13855714Skris	*cp2 = 0;
13955714Skris	if (changed) {
14055714Skris		printf("!%s\n", bangbuf);
14155714Skris		(void)fflush(stdout);
14255714Skris	}
14355714Skris	if (strlcpy(str, bangbuf, strsize) >= strsize)
14455714Skris		goto overf;
14555714Skris	if (strlcpy(lastbang, bangbuf, sizeof(lastbang)) >= sizeof(lastbang))
14655714Skris		goto overf;
14755714Skris	return (0);
14855714Skris}
14955714Skris
15055714Skris/*
151 * Print out a nice help message from some file or another.
152 */
153
154int
155help()
156{
157	int c;
158	FILE *f;
159
160	if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
161		warn("%s", _PATH_HELP);
162		return (1);
163	}
164	while ((c = getc(f)) != EOF)
165		putchar(c);
166	(void)Fclose(f);
167	return (0);
168}
169
170/*
171 * Change user's working directory.
172 */
173int
174schdir(arglist)
175	char **arglist;
176{
177	char *cp;
178
179	if (*arglist == NULL) {
180		if (homedir == NULL)
181			return (1);
182		cp = homedir;
183	} else
184		if ((cp = expand(*arglist)) == NULL)
185			return (1);
186	if (chdir(cp) < 0) {
187		warn("%s", cp);
188		return (1);
189	}
190	return (0);
191}
192
193int
194respond(msgvec)
195	int *msgvec;
196{
197	if (value("Replyall") == NULL)
198		return (dorespond(msgvec));
199	else
200		return (doRespond(msgvec));
201}
202
203/*
204 * Reply to a list of messages.  Extract each name from the
205 * message header and send them off to mail1()
206 */
207int
208dorespond(msgvec)
209	int *msgvec;
210{
211	struct message *mp;
212	char *cp, *rcv, *replyto;
213	char **ap;
214	struct name *np;
215	struct header head;
216
217	if (msgvec[1] != 0) {
218		printf("Sorry, can't reply to multiple messages at once\n");
219		return (1);
220	}
221	mp = &message[msgvec[0] - 1];
222	touch(mp);
223	dot = mp;
224	if ((rcv = skin(hfield("from", mp))) == NULL)
225		rcv = skin(nameof(mp, 1));
226	if ((replyto = skin(hfield("reply-to", mp))) != NULL)
227		np = extract(replyto, GTO);
228	else if ((cp = skin(hfield("to", mp))) != NULL)
229		np = extract(cp, GTO);
230	else
231		np = NULL;
232	np = elide(np);
233	/*
234	 * Delete my name from the reply list,
235	 * and with it, all my alternate names.
236	 */
237	np = delname(np, myname);
238	if (altnames)
239		for (ap = altnames; *ap != NULL; ap++)
240			np = delname(np, *ap);
241	if (np != NULL && replyto == NULL)
242		np = cat(np, extract(rcv, GTO));
243	else if (np == NULL) {
244		if (replyto != NULL)
245			printf("Empty reply-to field -- replying to author\n");
246		np = extract(rcv, GTO);
247	}
248	head.h_to = np;
249	if ((head.h_subject = hfield("subject", mp)) == NULL)
250		head.h_subject = hfield("subj", mp);
251	head.h_subject = reedit(head.h_subject);
252	if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
253		np = elide(extract(cp, GCC));
254		np = delname(np, myname);
255		if (altnames != 0)
256			for (ap = altnames; *ap != NULL; ap++)
257				np = delname(np, *ap);
258		head.h_cc = np;
259	} else
260		head.h_cc = NULL;
261	head.h_bcc = NULL;
262	head.h_smopts = NULL;
263	head.h_replyto = value("REPLYTO");
264	head.h_inreplyto = skin(hfield("message-id", mp));
265	mail1(&head, 1);
266	return (0);
267}
268
269/*
270 * Modify the subject we are replying to to begin with Re: if
271 * it does not already.
272 */
273char *
274reedit(subj)
275	char *subj;
276{
277	char *newsubj;
278
279	if (subj == NULL)
280		return (NULL);
281	if ((subj[0] == 'r' || subj[0] == 'R') &&
282	    (subj[1] == 'e' || subj[1] == 'E') &&
283	    subj[2] == ':')
284		return (subj);
285	newsubj = salloc(strlen(subj) + 5);
286	sprintf(newsubj, "Re: %s", subj);
287	return (newsubj);
288}
289
290/*
291 * Preserve the named messages, so that they will be sent
292 * back to the system mailbox.
293 */
294int
295preserve(msgvec)
296	int *msgvec;
297{
298	int *ip, mesg;
299	struct message *mp;
300
301	if (edit) {
302		printf("Cannot \"preserve\" in edit mode\n");
303		return (1);
304	}
305	for (ip = msgvec; *ip != 0; ip++) {
306		mesg = *ip;
307		mp = &message[mesg-1];
308		mp->m_flag |= MPRESERVE;
309		mp->m_flag &= ~MBOX;
310		dot = mp;
311	}
312	return (0);
313}
314
315/*
316 * Mark all given messages as unread.
317 */
318int
319unread(msgvec)
320	int	msgvec[];
321{
322	int *ip;
323
324	for (ip = msgvec; *ip != 0; ip++) {
325		dot = &message[*ip-1];
326		dot->m_flag &= ~(MREAD|MTOUCH);
327		dot->m_flag |= MSTATUS;
328	}
329	return (0);
330}
331
332/*
333 * Print the size of each message.
334 */
335int
336messize(msgvec)
337	int *msgvec;
338{
339	struct message *mp;
340	int *ip, mesg;
341
342	for (ip = msgvec; *ip != 0; ip++) {
343		mesg = *ip;
344		mp = &message[mesg-1];
345		printf("%d: %ld/%ld\n", mesg, mp->m_lines, mp->m_size);
346	}
347	return (0);
348}
349
350/*
351 * Quit quickly.  If we are sourcing, just pop the input level
352 * by returning an error.
353 */
354int
355rexit(e)
356	int e;
357{
358	if (sourcing)
359		return (1);
360	exit(e);
361	/*NOTREACHED*/
362}
363
364/*
365 * Set or display a variable value.  Syntax is similar to that
366 * of csh.
367 */
368int
369set(arglist)
370	char **arglist;
371{
372	struct var *vp;
373	char *cp, *cp2;
374	char varbuf[BUFSIZ], **ap, **p;
375	int errs, h, s;
376
377	if (*arglist == NULL) {
378		for (h = 0, s = 1; h < HSHSIZE; h++)
379			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
380				s++;
381		ap = (char **)salloc(s * sizeof(*ap));
382		for (h = 0, p = ap; h < HSHSIZE; h++)
383			for (vp = variables[h]; vp != NULL; vp = vp->v_link)
384				*p++ = vp->v_name;
385		*p = NULL;
386		sort(ap);
387		for (p = ap; *p != NULL; p++)
388			printf("%s\t%s\n", *p, value(*p));
389		return (0);
390	}
391	errs = 0;
392	for (ap = arglist; *ap != NULL; ap++) {
393		cp = *ap;
394		cp2 = varbuf;
395		while (cp2 < varbuf + sizeof(varbuf) - 1 && *cp != '=' && *cp != '\0')
396			*cp2++ = *cp++;
397		*cp2 = '\0';
398		if (*cp == '\0')
399			cp = "";
400		else
401			cp++;
402		if (equal(varbuf, "")) {
403			printf("Non-null variable name required\n");
404			errs++;
405			continue;
406		}
407		assign(varbuf, cp);
408	}
409	return (errs);
410}
411
412/*
413 * Unset a bunch of variable values.
414 */
415int
416unset(arglist)
417	char **arglist;
418{
419	struct var *vp, *vp2;
420	int errs, h;
421	char **ap;
422
423	errs = 0;
424	for (ap = arglist; *ap != NULL; ap++) {
425		if ((vp2 = lookup(*ap)) == NULL) {
426			if (!sourcing) {
427				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			vfree(vp2->v_name);
436			vfree(vp2->v_value);
437			(void)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		vfree(vp2->v_name);
444		vfree(vp2->v_value);
445		(void)free(vp2);
446	}
447	return (errs);
448}
449
450/*
451 * Put add users to a group.
452 */
453int
454group(argv)
455	char **argv;
456{
457	struct grouphead *gh;
458	struct group *gp;
459	char **ap, *gname, **p;
460	int h, s;
461
462	if (*argv == NULL) {
463		for (h = 0, s = 1; h < HSHSIZE; h++)
464			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
465				s++;
466		ap = (char **)salloc(s * sizeof(*ap));
467		for (h = 0, p = ap; h < HSHSIZE; h++)
468			for (gh = groups[h]; gh != NULL; gh = gh->g_link)
469				*p++ = gh->g_name;
470		*p = NULL;
471		sort(ap);
472		for (p = ap; *p != NULL; p++)
473			printgroup(*p);
474		return (0);
475	}
476	if (argv[1] == NULL) {
477		printgroup(*argv);
478		return (0);
479	}
480	gname = *argv;
481	h = hash(gname);
482	if ((gh = findgroup(gname)) == NULL) {
483		gh = calloc(sizeof(*gh), 1);
484		gh->g_name = vcopy(gname);
485		gh->g_list = NULL;
486		gh->g_link = groups[h];
487		groups[h] = gh;
488	}
489
490	/*
491	 * Insert names from the command list into the group.
492	 * Who cares if there are duplicates?  They get tossed
493	 * later anyway.
494	 */
495
496	for (ap = argv+1; *ap != NULL; ap++) {
497		gp = calloc(sizeof(*gp), 1);
498		gp->ge_name = vcopy(*ap);
499		gp->ge_link = gh->g_list;
500		gh->g_list = gp;
501	}
502	return (0);
503}
504
505/*
506 * Sort the passed string vecotor into ascending dictionary
507 * order.
508 */
509void
510sort(list)
511	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, ap-list, sizeof(*list), diction);
520}
521
522/*
523 * Do a dictionary order comparison of the arguments from
524 * qsort.
525 */
526int
527diction(a, b)
528	const void *a, *b;
529{
530	return (strcmp(*(const char **)a, *(const char **)b));
531}
532
533/*
534 * The do nothing command for comments.
535 */
536
537/*ARGSUSED*/
538int
539null(e)
540	int e;
541{
542	return (0);
543}
544
545/*
546 * Change to another file.  With no argument, print information about
547 * the current file.
548 */
549int
550file(argv)
551	char **argv;
552{
553
554	if (argv[0] == NULL) {
555		newfileinfo();
556		return (0);
557	}
558	if (setfile(*argv) < 0)
559		return (1);
560	announce();
561	return (0);
562}
563
564/*
565 * Expand file names like echo
566 */
567int
568echo(argv)
569	char **argv;
570{
571	char **ap, *cp;
572
573	for (ap = argv; *ap != NULL; ap++) {
574		cp = *ap;
575		if ((cp = expand(cp)) != NULL) {
576			if (ap != argv)
577				printf(" ");
578			printf("%s", cp);
579		}
580	}
581	printf("\n");
582	return (0);
583}
584
585int
586Respond(msgvec)
587	int *msgvec;
588{
589	if (value("Replyall") == NULL)
590		return (doRespond(msgvec));
591	else
592		return (dorespond(msgvec));
593}
594
595/*
596 * Reply to a series of messages by simply mailing to the senders
597 * and not messing around with the To: and Cc: lists as in normal
598 * reply.
599 */
600int
601doRespond(msgvec)
602	int msgvec[];
603{
604	struct header head;
605	struct message *mp;
606	int *ap;
607	char *cp, *mid;
608
609	head.h_to = NULL;
610	for (ap = msgvec; *ap != 0; ap++) {
611		mp = &message[*ap - 1];
612		touch(mp);
613		dot = mp;
614		if ((cp = skin(hfield("from", mp))) == NULL)
615			cp = skin(nameof(mp, 2));
616		head.h_to = cat(head.h_to, extract(cp, GTO));
617		mid = skin(hfield("message-id", mp));
618	}
619	if (head.h_to == NULL)
620		return (0);
621	mp = &message[msgvec[0] - 1];
622	if ((head.h_subject = hfield("subject", mp)) == NULL)
623		head.h_subject = hfield("subj", mp);
624	head.h_subject = reedit(head.h_subject);
625	head.h_cc = NULL;
626	head.h_bcc = NULL;
627	head.h_smopts = NULL;
628	head.h_replyto = value("REPLYTO");
629	head.h_inreplyto = mid;
630	mail1(&head, 1);
631	return (0);
632}
633
634/*
635 * Conditional commands.  These allow one to parameterize one's
636 * .mailrc and do some things if sending, others if receiving.
637 */
638int
639ifcmd(argv)
640	char **argv;
641{
642	char *cp;
643
644	if (cond != CANY) {
645		printf("Illegal nested \"if\"\n");
646		return (1);
647	}
648	cond = CANY;
649	cp = argv[0];
650	switch (*cp) {
651	case 'r': case 'R':
652		cond = CRCV;
653		break;
654
655	case 's': case 'S':
656		cond = CSEND;
657		break;
658
659	default:
660		printf("Unrecognized if-keyword: \"%s\"\n", cp);
661		return (1);
662	}
663	return (0);
664}
665
666/*
667 * Implement 'else'.  This is pretty simple -- we just
668 * flip over the conditional flag.
669 */
670int
671elsecmd()
672{
673
674	switch (cond) {
675	case CANY:
676		printf("\"Else\" without matching \"if\"\n");
677		return (1);
678
679	case CSEND:
680		cond = CRCV;
681		break;
682
683	case CRCV:
684		cond = CSEND;
685		break;
686
687	default:
688		printf("Mail's idea of conditions is screwed up\n");
689		cond = CANY;
690		break;
691	}
692	return (0);
693}
694
695/*
696 * End of if statement.  Just set cond back to anything.
697 */
698int
699endifcmd()
700{
701
702	if (cond == CANY) {
703		printf("\"Endif\" without matching \"if\"\n");
704		return (1);
705	}
706	cond = CANY;
707	return (0);
708}
709
710/*
711 * Set the list of alternate names.
712 */
713int
714alternates(namelist)
715	char **namelist;
716{
717	int c;
718	char **ap, **ap2, *cp;
719
720	c = argcount(namelist) + 1;
721	if (c == 1) {
722		if (altnames == 0)
723			return (0);
724		for (ap = altnames; *ap != NULL; ap++)
725			printf("%s ", *ap);
726		printf("\n");
727		return (0);
728	}
729	if (altnames != 0)
730		(void)free(altnames);
731	altnames = calloc((unsigned)c, sizeof(char *));
732	for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
733		cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
734		strcpy(cp, *ap);
735		*ap2 = cp;
736	}
737	*ap2 = 0;
738	return (0);
739}
740