xref: /netbsd-src/usr.bin/mail/cmd1.c (revision c0179c282a5968435315a82f4128c61372c68fc3)
1 /*	$NetBSD: cmd1.c,v 1.27 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
35 static char sccsid[] = "@(#)cmd1.c	8.2 (Berkeley) 4/20/95";
36 #else
37 __RCSID("$NetBSD: cmd1.c,v 1.27 2006/10/31 20:07:32 christos Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include "rcv.h"
42 #include "extern.h"
43 #include "format.h"
44 #ifdef MIME_SUPPORT
45 #include "mime.h"
46 #endif
47 
48 /*
49  * Mail -- a mail program
50  *
51  * User commands.
52  */
53 
54 /*
55  * Print the current active headings.
56  * Don't change dot if invoker didn't give an argument.
57  */
58 
59 static int screen;
60 
61 int
62 headers(void *v)
63 {
64 	int *msgvec = v;
65 	int n, mesg, flag;
66 	struct message *mp;
67 	int size;
68 
69 	size = screensize();
70 	n = msgvec[0];
71 	if (n != 0)
72 		screen = (n-1)/size;
73 	if (screen < 0)
74 		screen = 0;
75 	mp = &message[screen * size];
76 	if (mp >= &message[msgCount])
77 		mp = &message[msgCount - size];
78 	if (mp < &message[0])
79 		mp = &message[0];
80 	flag = 0;
81 	mesg = mp - &message[0];
82 	if (dot != &message[n - 1])
83 		dot = mp;
84 	for (; mp < &message[msgCount]; mp++) {
85 		mesg++;
86 		if (mp->m_flag & MDELETED)
87 			continue;
88 		if (flag++ >= size)
89 			break;
90 		printhead(mesg);
91 	}
92 	if (flag == 0) {
93 		(void)printf("No more mail.\n");
94 		return(1);
95 	}
96 	return(0);
97 }
98 
99 /*
100  * Scroll to the next/previous screen
101  */
102 int
103 scroll(void *v)
104 {
105 	char *arg = v;
106 	int s, size;
107 	int cur[1];
108 
109 	cur[0] = 0;
110 	size = screensize();
111 	s = screen;
112 	switch (*arg) {
113 	case 0:
114 	case '+':
115 		s++;
116 		if (s * size >= msgCount) {
117 			(void)printf("On last screenful of messages\n");
118 			return(0);
119 		}
120 		screen = s;
121 		break;
122 
123 	case '-':
124 		if (--s < 0) {
125 			(void)printf("On first screenful of messages\n");
126 			return(0);
127 		}
128 		screen = s;
129 		break;
130 
131 	default:
132 		(void)printf("Unrecognized scrolling command \"%s\"\n", arg);
133 		return(1);
134 	}
135 	return(headers(cur));
136 }
137 
138 /*
139  * Compute screen size.
140  */
141 int
142 screensize(void)
143 {
144 	int s;
145 	char *cp;
146 
147 	if ((cp = value("screen")) != NULL && (s = atoi(cp)) > 0)
148 		return s;
149 	return screenheight - 4;
150 }
151 
152 /*
153  * Print out the headlines for each message
154  * in the passed message list.
155  */
156 int
157 from(void *v)
158 {
159 	int *msgvec = v;
160 	int *ip;
161 
162 	for (ip = msgvec; *ip != 0; ip++)
163 		printhead(*ip);
164 	if (--ip >= msgvec)
165 		dot = &message[*ip - 1];
166 	return(0);
167 }
168 
169 /*
170  * Print out the header of a specific message.
171  * This is a slight improvement to the standard one.
172  */
173 void
174 printhead(int mesg)
175 {
176 #if 1
177 	const char *fmtstr;
178 	char *msgline;
179 
180 	fmtstr = value(ENAME_HEADER_FORMAT);
181 	/*
182 	 * XXX - should we use the old code here if
183 	 * value(ENAME_HEADER_FORMAT) is null?
184 	 */
185 	if (fmtstr == NULL)
186 		fmtstr = DEFAULT_HEADER_FORMAT;
187 	msgline = smsgprintf(fmtstr, &message[mesg - 1]);
188 	if (screenwidth > 0)
189 		msgline[screenwidth] = '\0';
190 	(void)printf("%s\n", msgline);
191 #else
192 	struct message *mp;
193 	char headline[LINESIZE], wcount[LINESIZE], *subjline, dispc, curind;
194 	char pbuf[BUFSIZ];
195 	struct headline hl;
196 	int subjlen;
197 	char *name;
198 
199 	mp = &message[mesg - 1];
200 	(void)mail_readline(setinput(mp), headline, LINESIZE);
201 	if ((subjline = hfield("subject", mp)) == NULL)
202 		subjline = hfield("subj", mp);
203 
204 	/*
205 	 * Bletch!
206 	 */
207 	curind = dot == mp ? '>' : ' ';
208 	dispc = ' ';
209 	if (mp->m_flag & MSAVED)
210 		dispc = '*';
211 	if (mp->m_flag & MPRESERVE)
212 		dispc = 'P';
213 	if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
214 		dispc = 'N';
215 	if ((mp->m_flag & (MREAD|MNEW)) == 0)
216 		dispc = 'U';
217 	if (mp->m_flag & MBOX)
218 		dispc = 'M';
219 	parse(headline, &hl, pbuf);
220 	(void)snprintf(wcount, LINESIZE, "%3ld/%-5llu", mp->m_blines,
221 	    (unsigned long long)mp->m_size);
222 	subjlen = screenwidth - 50 - strlen(wcount);
223 	name = value("show-rcpt") != NULL ?
224 		skin(hfield("to", mp)) : nameof(mp, 0);
225 	if (subjline == NULL || subjlen < 0)		/* pretty pathetic */
226 		(void)printf("%c%c%3d %-20.20s  %16.16s %s\n",
227 			curind, dispc, mesg, name, hl.l_date, wcount);
228 	else
229 		(void)printf("%c%c%3d %-20.20s  %16.16s %s \"%.*s\"\n",
230 			curind, dispc, mesg, name, hl.l_date, wcount,
231 			subjlen, subjline);
232 #endif
233 }
234 
235 /*
236  * Print out the value of dot.
237  */
238 int
239 /*ARGSUSED*/
240 pdot(void *v __unused)
241 {
242 	(void)printf("%d\n", (int)(dot - &message[0] + 1));
243 	return(0);
244 }
245 
246 /*
247  * Print out all the possible commands.
248  */
249 int
250 /*ARGSUSED*/
251 pcmdlist(void *v __unused)
252 {
253 	const struct cmd *cp;
254 	int cc;
255 
256 	(void)printf("Commands are:\n");
257 	for (cc = 0, cp = cmdtab; cp->c_name != NULL; cp++) {
258 		cc += strlen(cp->c_name) + 2;
259 		if (cc > 72) {
260 			(void)printf("\n");
261 			cc = strlen(cp->c_name) + 2;
262 		}
263 		if ((cp + 1)->c_name != NULL)
264 			(void)printf("%s, ", cp->c_name);
265 		else
266 			(void)printf("%s\n", cp->c_name);
267 	}
268 	return(0);
269 }
270 
271 #ifdef MIME_SUPPORT
272 static int
273 de_mime(const char *name)
274 {
275 	const char *p;
276 	const char *list;
277 
278 #define DELIM	" \t,"	/* list of string delimiters */
279 
280 	list = value(ENAME_MIME_DECODE_MSG);
281 	if (list == NULL)
282 		return 0;
283 
284 	if (list[0] == '\0')
285 		return 1;
286 
287 	p = strcasestr(list, name);
288 	if (p == NULL)
289 		return 0;
290 
291 	return strchr(DELIM, p[strlen(name)]) && (
292 		p == list || strchr(DELIM, p[-1]));
293 
294 #undef DELIM
295 }
296 #endif /* MIME_SUPPORT */
297 
298 /*
299  * Paginate messages, honor ignored fields.
300  */
301 int
302 more(void *v)
303 {
304 	int *msgvec = v;
305 #ifdef MIME_SUPPORT
306 	return type1(msgvec, 1, 1, de_mime("more"));
307 #else
308 	return(type1(msgvec, 1, 1));
309 #endif
310 
311 }
312 
313 /*
314  * Paginate messages, even printing ignored fields.
315  */
316 int
317 More(void *v)
318 {
319 	int *msgvec = v;
320 
321 #ifdef MIME_SUPPORT
322 	return type1(msgvec, 0, 1, de_mime("more"));
323 #else
324 	return(type1(msgvec, 0, 1));
325 #endif
326 }
327 
328 /*
329  * Type out messages, honor ignored fields.
330  */
331 int
332 type(void *v)
333 {
334 	int *msgvec = v;
335 
336 #ifdef MIME_SUPPORT
337 	return type1(msgvec, 1, 0, de_mime("type"));
338 #else
339 	return(type1(msgvec, 1, 0));
340 #endif
341 }
342 
343 /*
344  * Type out messages, even printing ignored fields.
345  */
346 int
347 Type(void *v)
348 {
349 	int *msgvec = v;
350 
351 #ifdef MIME_SUPPORT
352 	return type1(msgvec, 0, 0, de_mime("type"));
353 #else
354 	return(type1(msgvec, 0, 0));
355 #endif
356 }
357 
358 
359 #ifdef MIME_SUPPORT
360 /*
361  * Paginate messages, honor ignored fields.
362  */
363 int
364 page(void *v)
365 {
366 	int *msgvec = v;
367 #ifdef MIME_SUPPORT
368 	return type1(msgvec, 1, 1, de_mime("page"));
369 #else
370 	return(type1(msgvec, 1, 1));
371 #endif
372 
373 }
374 
375 /*
376  * Paginate messages, even printing ignored fields.
377  */
378 int
379 Page(void *v)
380 {
381 	int *msgvec = v;
382 
383 #ifdef MIME_SUPPORT
384 	return type1(msgvec, 0, 1, de_mime("page"));
385 #else
386 	return(type1(msgvec, 0, 1));
387 #endif
388 }
389 
390 /*
391  * Type out messages, honor ignored fields.
392  */
393 int
394 print(void *v)
395 {
396 	int *msgvec = v;
397 
398 #ifdef MIME_SUPPORT
399 	return type1(msgvec, 1, 0, de_mime("print"));
400 #else
401 	return(type1(msgvec, 1, 0));
402 #endif
403 }
404 
405 /*
406  * Type out messages, even printing ignored fields.
407  */
408 int
409 Print(void *v)
410 {
411 	int *msgvec = v;
412 
413 #ifdef MIME_SUPPORT
414 	return type1(msgvec, 0, 0, de_mime("print"));
415 #else
416 	return(type1(msgvec, 0, 0));
417 #endif
418 }
419 
420 /*
421  * Identical to type(), but with opposite mime behavior.
422  */
423 int
424 view(void *v)
425 {
426 	int *msgvec = v;
427 	return type1(msgvec, 1, 0, !de_mime("print"));
428 }
429 
430 /*
431  * Identical to Type(), but with opposite mime behavior.
432  */
433 int
434 View(void *v)
435 {
436 	int *msgvec = v;
437 
438 	return type1(msgvec, 0, 0, !de_mime("print"));
439 }
440 #endif /* MIME_SUPPORT */
441 
442 /*
443  * Type out the messages requested.
444  */
445 jmp_buf	pipestop;
446 int
447 #ifdef MIME_SUPPORT
448 type1(int *msgvec, int doign, int dopage, int mime_decode)
449 #else
450 type1(int *msgvec, int doign, int dopage)
451 #endif
452 {
453 	int *ip;
454 	struct message *mp;
455 	const char *cp;
456 	int nlines;
457 
458 	/* Some volatile variables so longjmp will get the current not
459 	 * starting values.  Note it is the variable that is volatile,
460 	 * not what it is pointing at! */
461 #ifdef MIME_SUPPORT
462 	struct mime_info *volatile mip; /* avoid longjmp clobbering */
463 #endif
464 	FILE *volatile obuf;		/* avoid longjmp clobbering */
465 
466 #ifdef MIME_SUPPORT
467 	mip = NULL;
468 #endif
469 	obuf = stdout;
470 	if (setjmp(pipestop))
471 		goto close_pipe;
472 	if (value("interactive") != NULL &&
473 	    (dopage || (cp = value("crt")) != NULL)) {
474 		nlines = 0;
475 		if (!dopage) {
476 			for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++)
477 				nlines += message[*ip - 1].m_blines;
478 		}
479 		if (dopage || nlines > (*cp ? atoi(cp) : realscreenheight)) {
480 			cp = value("PAGER");
481 			if (cp == NULL || *cp == '\0')
482 				cp = _PATH_MORE;
483 			obuf = Popen(cp, "w");
484 			if (obuf == NULL) {
485 				warn("%s", cp);
486 				obuf = stdout;
487 			} else
488 				(void)signal(SIGPIPE, brokpipe);
489 		}
490 	}
491 	for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
492 		mp = &message[*ip - 1];
493 		touch(mp);
494 		dot = mp;
495 		if (value("quiet") == NULL)
496 			(void)fprintf(obuf, "Message %d:\n", *ip);
497 #ifdef MIME_SUPPORT
498 		if (mime_decode)
499 			mip = mime_decode_open(mp);
500 		(void)mime_sendmessage(mp, obuf, doign ? ignore : 0, NULL, mip);
501 		mime_decode_close(mip);
502 #else
503 		(void)sendmessage(mp, obuf, doign ? ignore : 0, NULL);
504 #endif
505 	}
506 close_pipe:
507 #ifdef MIME_SUPPORT
508 	if (mip != NULL || obuf != stdout) {
509 		/*
510 		 * Ignore SIGPIPE so it can't cause a duplicate close.
511 		 */
512 		(void)signal(SIGPIPE, SIG_IGN);
513 		mime_decode_close(mip);
514 		if (obuf != stdout)
515 			(void)Pclose(obuf);
516 		(void)signal(SIGPIPE, SIG_DFL);
517 
518 	}
519 #else
520 	if (obuf != stdout) {
521 		/*
522 		 * Ignore SIGPIPE so it can't cause a duplicate close.
523 		 */
524 		(void)signal(SIGPIPE, SIG_IGN);
525 		(void)Pclose(obuf);
526 		(void)signal(SIGPIPE, SIG_DFL);
527 	}
528 #endif
529 	return(0);
530 }
531 
532 /*
533  * Respond to a broken pipe signal --
534  * probably caused by quitting more.
535  */
536 void
537 /*ARGSUSED*/
538 brokpipe(int signo __unused)
539 {
540 	longjmp(pipestop, 1);
541 }
542 
543 /*
544  * Pipe the current message buffer to a command.
545  */
546 int
547 pipecmd(void *v)
548 {
549 	char *cmd = v;
550 	FILE *volatile obuf;	/* void longjmp clobbering - we want
551 				   the current value not start value */
552 	if (dot == NULL) {
553 		warn("pipcmd: no current message");
554 		return 1;
555 	}
556 
557 	obuf = stdout;
558 	if (setjmp(pipestop))
559 		goto close_pipe;
560 
561 	obuf = Popen(cmd, "w");
562 	if (obuf == NULL) {
563 		warn("pipecmd: Popen failed: %s", cmd);
564 		return 1;
565 	} else
566 		(void)signal(SIGPIPE, brokpipe);
567 
568 #ifdef MIME_SUPPORT
569 	(void)sendmessage(dot, obuf, ignoreall, NULL, NULL);
570 #else
571 	(void)sendmessage(dot, obuf, ignoreall, NULL);
572 #endif
573 
574  close_pipe:
575 	if (obuf != stdout) {
576 		/*
577 		 * Ignore SIGPIPE so it can't cause a duplicate close.
578 		 */
579 		(void)signal(SIGPIPE, SIG_IGN);
580 		(void)Pclose(obuf);
581 		(void)signal(SIGPIPE, SIG_DFL);
582 	}
583 	return 0;
584 }
585 
586 /*
587  * Print the top so many lines of each desired message.
588  * The number of lines is taken from the variable "toplines"
589  * and defaults to 5.
590  */
591 int
592 top(void *v)
593 {
594 	int *msgvec = v;
595 	int *ip;
596 	struct message *mp;
597 	int c, topl, lines, lineb;
598 	char *valtop, linebuf[LINESIZE];
599 	FILE *ibuf;
600 
601 	topl = 5;
602 	valtop = value("toplines");
603 	if (valtop != NULL) {
604 		topl = atoi(valtop);
605 		if (topl < 0 || topl > 10000)
606 			topl = 5;
607 	}
608 	lineb = 1;
609 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
610 		mp = &message[*ip - 1];
611 		touch(mp);
612 		dot = mp;
613 		if (value("quiet") == NULL)
614 			(void)printf("Message %d:\n", *ip);
615 		ibuf = setinput(mp);
616 		c = mp->m_lines;
617 		if (!lineb)
618 			(void)printf("\n");
619 		for (lines = 0; lines < c && lines <= topl; lines++) {
620 			if (mail_readline(ibuf, linebuf, LINESIZE) < 0)
621 				break;
622 			(void)puts(linebuf);
623 			lineb = blankline(linebuf);
624 		}
625 	}
626 	return(0);
627 }
628 
629 /*
630  * Touch all the given messages so that they will
631  * get mboxed.
632  */
633 int
634 stouch(void *v)
635 {
636 	int *msgvec = v;
637 	int *ip;
638 
639 	for (ip = msgvec; *ip != 0; ip++) {
640 		dot = &message[*ip - 1];
641 		dot->m_flag |= MTOUCH;
642 		dot->m_flag &= ~MPRESERVE;
643 	}
644 	return(0);
645 }
646 
647 /*
648  * Make sure all passed messages get mboxed.
649  */
650 int
651 mboxit(void *v)
652 {
653 	int *msgvec = v;
654 	int *ip;
655 
656 	for (ip = msgvec; *ip != 0; ip++) {
657 		dot = &message[*ip - 1];
658 		dot->m_flag |= MTOUCH|MBOX;
659 		dot->m_flag &= ~MPRESERVE;
660 	}
661 	return(0);
662 }
663 
664 /*
665  * List the folders the user currently has.
666  */
667 int
668 /*ARGSUSED*/
669 folders(void *v __unused)
670 {
671 	char dirname[PATHSIZE];
672 	const char *cmd;
673 
674 	if (getfold(dirname) < 0) {
675 		(void)printf("No value set for \"folder\"\n");
676 		return 1;
677 	}
678 	if ((cmd = value("LISTER")) == NULL)
679 		cmd = "ls";
680 	(void)run_command(cmd, 0, -1, -1, dirname, NULL);
681 	return 0;
682 }
683 
684 /*
685  * Update the mail file with any new messages that have
686  * come in since we started reading mail.
687  */
688 int
689 /*ARGSUSED*/
690 inc(void *v __unused)
691 {
692 	int nmsg, mdot;
693 
694 	nmsg = incfile();
695 
696 	if (nmsg == 0) {
697 	(void)printf("No new mail.\n");
698 	} else if (nmsg > 0) {
699 		mdot = newfileinfo(msgCount - nmsg);
700 		dot = &message[mdot - 1];
701 	} else {
702 	(void)printf("\"inc\" command failed...\n");
703 	}
704 
705 	return 0;
706 }
707