1/*	$NetBSD: cmd2.c,v 1.23 2007/10/27 15:14:50 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[] = "@(#)cmd2.c	8.1 (Berkeley) 6/6/93";
36#else
37__RCSID("$NetBSD: cmd2.c,v 1.23 2007/10/27 15:14:50 christos Exp $");
38#endif
39#endif /* not lint */
40
41#include "rcv.h"
42#include <util.h>
43#include "extern.h"
44#ifdef MIME_SUPPORT
45#include "mime.h"
46#endif
47#include "thread.h"
48
49/*
50 * Mail -- a mail program
51 *
52 * More user commands.
53 */
54
55/*
56 * If any arguments were given, go to the next applicable argument
57 * following dot, otherwise, go to the next applicable message.
58 * If given as first command with no arguments, print first message.
59 */
60PUBLIC int
61next(void *v)
62{
63	int *msgvec;
64	struct message *mp;
65	int *ip, *ip2;
66	int list[2], mdot;
67
68	msgvec = v;
69	if (*msgvec != 0) {
70
71		/*
72		 * If some messages were supplied, find the
73		 * first applicable one following dot using
74		 * wrap around.
75		 */
76		mdot = get_msgnum(dot);
77
78		/*
79		 * Find the first message in the supplied
80		 * message list which follows dot.
81		 */
82
83		for (ip = msgvec; *ip != 0; ip++)
84			if (*ip > mdot)
85				break;
86		if (*ip == 0)
87			ip = msgvec;
88		ip2 = ip;
89		do {
90			mp = get_message(*ip2);
91			if ((mp->m_flag & MDELETED) == 0) {
92				dot = mp;
93				goto hitit;
94			}
95			if (*ip2 != 0)
96				ip2++;
97			if (*ip2 == 0)
98				ip2 = msgvec;
99		} while (ip2 != ip);
100		(void)printf("No messages applicable\n");
101		return 1;
102	}
103
104	/*
105	 * If this is the first command, select message 1.
106	 * Note that this must exist for us to get here at all.
107	 */
108
109	if (!sawcom)
110		goto hitit;
111
112	/*
113	 * Just find the next good message after dot, no
114	 * wraparound.
115	 */
116
117	for (mp = next_message(dot); mp; mp = next_message(mp))
118		if ((mp->m_flag & (MDELETED|MSAVED)) == 0)
119			break;
120
121	if (mp == NULL) {
122		(void)printf("At EOF\n");
123		return 0;
124	}
125	dot = mp;
126hitit:
127	/*
128	 * Print dot.
129	 */
130
131	list[0] = get_msgnum(dot);
132	list[1] = 0;
133	return type(list);
134}
135
136/*
137 * Snarf the file from the end of the command line and
138 * return a pointer to it.  If there is no file attached,
139 * just return NULL.  Put a null in front of the file
140 * name so that the message list processing won't see it,
141 * unless the file name is the only thing on the line, in
142 * which case, return 0 in the reference flag variable.
143 */
144static char *
145snarf(char linebuf[], int *flag, const char *string)
146{
147	char *cp;
148
149	*flag = 1;
150	cp = strlen(linebuf) + linebuf - 1;
151
152	/*
153	 * Strip away trailing blanks.
154	 */
155	while (cp >= linebuf && isspace((unsigned char)*cp))
156		cp--;
157	*++cp = '\0';
158
159	/*
160	 * Now search for the beginning of the file name.
161	 */
162	while (cp > linebuf && !isspace((unsigned char)*cp))
163		cp--;
164	if (*cp == '\0') {
165		(void)printf("No %s specified.\n", string);
166		return NULL;
167	}
168	if (isspace((unsigned char)*cp))
169		*cp++ = '\0';
170	else
171		*flag = 0;
172	return cp;
173}
174
175struct save1_core_args_s {
176	FILE *obuf;
177	struct ignoretab *igtab;
178	int markmsg;
179};
180static int
181save1_core(struct message *mp, void *v)
182{
183	struct save1_core_args_s *args;
184
185	args = v;
186	touch(mp);
187
188	if (sendmessage(mp, args->obuf, args->igtab, NULL, NULL) < 0)
189		return -1;
190
191	if (args->markmsg)
192		mp->m_flag |= MSAVED;
193
194	return 0;
195}
196
197/*
198 * Save/copy the indicated messages at the end of the passed file name.
199 * If markmsg is true, mark the message "saved."
200 */
201static int
202save1(char str[], int markmsg, const char *cmd, struct ignoretab *igtab)
203{
204	int *ip;
205	const char *fn;
206	const char *disp;
207	int f, *msgvec;
208	int msgCount;
209	FILE *obuf;
210
211	msgCount = get_msgCount();
212	msgvec = salloc((msgCount + 2) * sizeof(*msgvec));
213	if ((fn = snarf(str, &f, "file")) == NULL)
214		return 1;
215	if (!f) {
216		*msgvec = first(0, MMNORM);
217		if (*msgvec == 0) {
218			(void)printf("No messages to %s.\n", cmd);
219			return 1;
220		}
221		msgvec[1] = 0;
222	}
223	if (f && getmsglist(str, msgvec, 0) < 0)
224		return 1;
225	if ((fn = expand(fn)) == NULL)
226		return 1;
227	(void)printf("\"%s\" ", fn);
228	(void)fflush(stdout);
229	if (access(fn, 0) >= 0)
230		disp = "[Appended]";
231	else
232		disp = "[New file]";
233	if ((obuf = Fopen(fn, "a")) == NULL) {
234		warn(NULL);
235		return 1;
236	}
237	for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
238		struct save1_core_args_s args;
239		struct message *mp;
240
241		args.obuf = obuf;
242		args.igtab = igtab;
243		args.markmsg = markmsg;
244		mp = get_message(*ip);
245		if (thread_recursion(mp, save1_core, &args)) {
246			warn("%s", fn);
247			(void)Fclose(obuf);
248			return 1;
249		}
250	}
251	(void)fflush(obuf);
252	if (ferror(obuf))
253		warn("%s", fn);
254	(void)Fclose(obuf);
255	(void)printf("%s\n", disp);
256	return 0;
257}
258
259/*
260 * Save a message in a file.  Mark the message as saved
261 * so we can discard when the user quits.
262 */
263PUBLIC int
264save(void *v)
265{
266	char *str;
267
268	str = v;
269	return save1(str, 1, "save", saveignore);
270}
271
272/*
273 * Save a message in a file.  Mark the message as saved
274 * so we can discard when the user quits.  Save all fields
275 * overriding saveignore and saveretain.
276 */
277PUBLIC int
278Save(void *v)
279{
280	char *str;
281
282	str = v;
283	return save1(str, 1, "Save", NULL);
284}
285
286/*
287 * Copy a message to a file without affected its saved-ness
288 */
289PUBLIC int
290copycmd(void *v)
291{
292	char *str;
293
294	str = v;
295	return save1(str, 0, "copy", saveignore);
296}
297
298/*
299 * Write the indicated messages at the end of the passed
300 * file name, minus header and trailing blank line.
301 */
302PUBLIC int
303swrite(void *v)
304{
305	char *str;
306
307	str = v;
308	return save1(str, 1, "write", ignoreall);
309}
310
311/*
312 * Delete the indicated messages.
313 * Set dot to some nice place afterwards.
314 * Internal interface.
315 */
316static int
317delm(int *msgvec)
318{
319	struct message *mp;
320	int *ip;
321	int last;
322
323	last = 0;
324	for (ip = msgvec; *ip != 0; ip++) {
325		mp = set_m_flag(*ip,
326		    ~(MPRESERVE|MSAVED|MBOX|MDELETED|MTOUCH), MDELETED|MTOUCH);
327		touch(mp);
328		last = *ip;
329	}
330	if (last != 0) {
331		dot = get_message(last);
332		last = first(0, MDELETED);
333		if (last != 0) {
334			dot = get_message(last);
335			return 0;
336		}
337		else {
338			dot = get_message(1);
339			return -1;
340		}
341	}
342
343	/*
344	 * Following can't happen -- it keeps lint happy
345	 */
346	return -1;
347}
348
349/*
350 * Delete messages.
351 */
352PUBLIC int
353delete(void *v)
354{
355	int *msgvec;
356
357	msgvec = v;
358	(void)delm(msgvec);
359	return 0;
360}
361
362/*
363 * Delete messages, then type the new dot.
364 */
365PUBLIC int
366deltype(void *v)
367{
368	int *msgvec;
369	int list[2];
370	int lastdot;
371
372	msgvec = v;
373	lastdot = get_msgnum(dot);
374	if (delm(msgvec) >= 0) {
375		list[0] = get_msgnum(dot);
376		if (list[0] > lastdot) {
377			touch(dot);
378			list[1] = 0;
379			return type(list);
380		}
381		(void)printf("At EOF\n");
382	} else
383		(void)printf("No more messages\n");
384	return 0;
385}
386
387/*
388 * Undelete the indicated messages.
389 */
390PUBLIC int
391undeletecmd(void *v)
392{
393	int msgCount;
394	int *msgvec;
395	int *ip;
396
397	msgvec = v;
398	msgCount = get_msgCount();
399	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
400		dot = set_m_flag(*ip, ~MDELETED, 0);
401		touch(dot);
402		dot->m_flag &= ~MDELETED;
403	}
404	return 0;
405}
406
407/*************************************************************************/
408
409/*
410 * Interactively dump core on "core"
411 */
412/*ARGSUSED*/
413PUBLIC int
414core(void *v __unused)
415{
416	int pid;
417
418	switch (pid = vfork()) {
419	case -1:
420		warn("fork");
421		return 1;
422	case 0:
423		abort();
424		_exit(1);
425	}
426	(void)printf("Okie dokie");
427	(void)fflush(stdout);
428	(void)wait_child(pid);
429	if (WCOREDUMP(wait_status))
430		(void)printf(" -- Core dumped.\n");
431	else
432		(void)printf(" -- Can't dump core.\n");
433	return 0;
434}
435
436/*
437 * Clobber the stack.
438 */
439static void
440clob1(int n)
441{
442	char buf[512];
443	char *cp;
444
445	if (n <= 0)
446		return;
447	for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
448		continue;
449	clob1(n - 1);
450}
451
452/*
453 * Clobber as many bytes of stack as the user requests.
454 */
455PUBLIC int
456clobber(void *v)
457{
458	char **argv;
459	int times;
460
461	argv = v;
462	if (argv[0] == 0)
463		times = 1;
464	else
465		times = (atoi(argv[0]) + 511) / 512;
466	clob1(times);
467	return 0;
468}
469
470/*
471 * Compare two names for sorting ignored field list.
472 */
473static int
474igcomp(const void *l, const void *r)
475{
476	return strcmp(*(const char *const *)l, *(const char *const *)r);
477}
478
479/*
480 * Print out all currently retained fields.
481 */
482static int
483igshow(struct ignoretab *tab, const char *which)
484{
485	int h;
486	struct ignore *igp;
487	char **ap, **ring;
488
489	if (tab->i_count == 0) {
490		(void)printf("No fields currently being %s.\n", which);
491		return 0;
492	}
493	ring = salloc((tab->i_count + 1) * sizeof(char *));
494	ap = ring;
495	for (h = 0; h < HSHSIZE; h++)
496		for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
497			*ap++ = igp->i_field;
498	*ap = 0;
499	qsort(ring, tab->i_count, sizeof(char *), igcomp);
500	for (ap = ring; *ap != 0; ap++)
501		(void)printf("%s\n", *ap);
502	return 0;
503}
504
505/*
506 * core ignore routine.
507 */
508static int
509ignore1(char *list[], struct ignoretab *tab, const char *which)
510{
511	char **ap;
512
513	if (*list == NULL)
514		return igshow(tab, which);
515
516	for (ap = list; *ap != 0; ap++)
517		add_ignore(*ap, tab);
518
519	return 0;
520}
521
522/*
523 * Add the given header fields to the retained list.
524 * If no arguments, print the current list of retained fields.
525 */
526PUBLIC int
527retfield(void *v)
528{
529	char **list;
530
531	list = v;
532	return ignore1(list, ignore + 1, "retained");
533}
534
535/*
536 * Add the given header fields to the ignored list.
537 * If no arguments, print the current list of ignored fields.
538 */
539PUBLIC int
540igfield(void *v)
541{
542	char **list;
543
544	list = v;
545	return ignore1(list, ignore, "ignored");
546}
547
548/*
549 * Add the given header fields to the save retained list.
550 * If no arguments, print the current list of save retained fields.
551 */
552PUBLIC int
553saveretfield(void *v)
554{
555	char **list;
556
557	list = v;
558	return ignore1(list, saveignore + 1, "retained");
559}
560
561/*
562 * Add the given header fields to the save ignored list.
563 * If no arguments, print the current list of save ignored fields.
564 */
565PUBLIC int
566saveigfield(void *v)
567{
568	char **list;
569
570	list = v;
571	return ignore1(list, saveignore, "ignored");
572}
573
574#ifdef MIME_SUPPORT
575
576static char*
577check_dirname(char *filename)
578{
579	struct stat sb;
580	char *fname;
581	char canon_buf[MAXPATHLEN];
582	char *canon_name;
583
584	canon_name = canon_buf;
585	fname = filename;
586	if (fname[0] == '~' && fname[1] == '/') {
587		if (homedir && homedir[0] != '~')
588			(void)easprintf(&fname, "%s/%s",
589			    homedir, fname + 2);
590	}
591	if (realpath(fname, canon_name) == NULL) {
592		warn("realpath: %s", filename);
593		canon_name = NULL;
594		goto done;
595	}
596	if (stat(canon_name, &sb) == -1) {
597		warn("stat: %s", canon_name);
598		canon_name = NULL;
599		goto done;
600	}
601	if (!S_ISDIR(sb.st_mode)) {
602		warnx("stat: %s is not a directory", canon_name);
603		canon_name = NULL;
604		goto done;
605	}
606	if (access(canon_name, W_OK|X_OK) == -1) {
607		warnx("access: %s is not writable", canon_name);
608		canon_name = NULL;
609		goto done;
610	}
611 done:
612	if (fname != filename)
613		free(fname);
614
615	return canon_name ? savestr(canon_name) : NULL;
616}
617
618struct detach1_core_args_s {
619	struct message *parent;
620	struct ignoretab *igtab;
621	const char *dstdir;
622};
623static int
624detach1_core(struct message *mp, void *v)
625{
626	struct mime_info *mip;
627	struct detach1_core_args_s *args;
628
629	args = v;
630	touch(mp);
631	show_msgnum(stdout, mp, args->parent);
632	mip = mime_decode_open(mp);
633	mime_detach_msgnum(mip, sget_msgnum(mp, args->parent));
634	(void)mime_sendmessage(mp, NULL, args->igtab, args->dstdir, mip);
635	mime_decode_close(mip);
636	return 0;
637}
638
639/*
640 * detach attachments.
641 */
642static int
643detach1(void *v, int do_unnamed)
644{
645	int recursive;
646	int f;
647	int msgCount;
648	int *msgvec;
649	int *ip;
650	char *str;
651	char *dstdir;
652
653	str = v;
654
655	/*
656	 * Get the destination directory.
657	 */
658	if ((dstdir = snarf(str, &f, "directory")) == NULL &&
659	    (dstdir = value(ENAME_MIME_DETACH_DIR)) == NULL &&
660	    (dstdir = origdir) == NULL)
661		return 1;
662
663	if ((dstdir = check_dirname(dstdir)) == NULL)
664		return 1;
665
666	/*
667	 * Setup the message list.
668	 */
669	msgCount = get_msgCount();
670	msgvec = salloc((msgCount + 2) * sizeof(*msgvec));
671	if (!f) {
672		*msgvec = first(0, MMNORM);
673		if (*msgvec == 0) {
674			(void)printf("No messages to detach.\n");
675			return 1;
676		}
677		msgvec[1] = 0;
678	}
679	if (f && getmsglist(str, msgvec, 0) < 0)
680		return 1;
681
682	if (mime_detach_control() != 0)
683		return 1;
684
685	/*
686	 * do 'dot' if nothing else was selected.
687	 */
688	if (msgvec[0] == 0 && dot != NULL) {
689		msgvec[0] = get_msgnum(dot);
690		msgvec[1] = 0;
691	}
692	recursive = do_recursion();
693	for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
694		struct detach1_core_args_s args;
695		struct message *mp;
696
697		mp = get_message(*ip);
698		dot = mp;
699		args.parent = recursive ? mp : NULL;
700		args.igtab = do_unnamed ? detachall : ignoreall;
701		args.dstdir = dstdir;
702		(void)thread_recursion(mp, detach1_core, &args);
703	}
704	return 0;
705}
706
707/*
708 * detach named attachments.
709 */
710PUBLIC int
711detach(void *v)
712{
713
714	return detach1(v, 0);
715}
716
717/*
718 * detach all attachments.
719 */
720PUBLIC int
721Detach(void *v)
722{
723
724	return detach1(v, 1);
725}
726#endif /* MIME_SUPPORT */
727