xref: /illumos-gate/usr/src/cmd/mailx/cmd1.c (revision 5328fc53d11d7151861fa272e4fb0248b8f0e145)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2014 Joyent, Inc.
25  */
26 
27 /*
28  * Copyright 2001 Sun Microsystems, Inc.  All rights reserved.
29  * Use is subject to license terms.
30  */
31 
32 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
33 /*	  All Rights Reserved  	*/
34 
35 /*
36  * University Copyright- Copyright (c) 1982, 1986, 1988
37  * The Regents of the University of California
38  * All Rights Reserved
39  *
40  * University Acknowledgment- Portions of this document are derived from
41  * software developed by the University of California, Berkeley, and its
42  * contributors.
43  */
44 
45 #include <err.h>
46 #include "rcv.h"
47 #include <locale.h>
48 
49 /*
50  * mailx -- a modified version of a University of California at Berkeley
51  *	mail program
52  *
53  * User commands.
54  */
55 
56 static char	*dispname(char *hdr);
57 static void	print(register struct message *mp, FILE *obuf, int doign);
58 static int	type1(int *msgvec, int doign, int page);
59 static int	topputs(const char *line, FILE *obuf);
60 
61 void	brokpipe(int sig);
62 jmp_buf	pipestop;
63 
64 /*
65  * Print the current active headings.
66  * Don't change dot if invoker didn't give an argument.
67  */
68 
69 static int curscreen = 0, oldscreensize = 0;
70 
71 int
72 headers(int *msgvec)
73 {
74 	register int n, mesg, flag;
75 	register struct message *mp;
76 	int size;
77 
78 	size = screensize();
79 	n = msgvec[0];
80 	if (n != 0)
81 		curscreen = (n-1)/size;
82 	if (curscreen < 0)
83 		curscreen = 0;
84 	mp = &message[curscreen * size];
85 	if (mp >= &message[msgCount])
86 		mp = &message[msgCount - size];
87 	if (mp < &message[0])
88 		mp = &message[0];
89 	flag = 0;
90 	mesg = mp - &message[0];
91 	if (dot != &message[n-1])
92 		dot = mp;
93 	if (Hflag)
94 		mp = message;
95 	for (; mp < &message[msgCount]; mp++) {
96 		mesg++;
97 		if (mp->m_flag & MDELETED)
98 			continue;
99 		if (flag++ >= size && !Hflag)
100 			break;
101 		printhead(mesg);
102 		sreset();
103 	}
104 	if (flag == 0) {
105 		printf(gettext("No more mail.\n"));
106 		return (1);
107 	}
108 	return (0);
109 }
110 
111 /*
112  * Scroll to the next/previous screen
113  */
114 
115 int
116 scroll(char arg[])
117 {
118 	register int s, size;
119 	int cur[1];
120 
121 	cur[0] = 0;
122 	size = screensize();
123 	s = curscreen;
124 	switch (*arg) {
125 	case 0:
126 	case '+':
127 		s++;
128 		if (s * size > msgCount) {
129 			printf(gettext("On last screenful of messages\n"));
130 			return (0);
131 		}
132 		curscreen = s;
133 		break;
134 
135 	case '-':
136 		if (--s < 0) {
137 			printf(gettext("On first screenful of messages\n"));
138 			return (0);
139 		}
140 		curscreen = s;
141 		break;
142 
143 	default:
144 		printf(gettext("Unrecognized scrolling command \"%s\"\n"), arg);
145 		return (1);
146 	}
147 	return (headers(cur));
148 }
149 
150 /*
151  * Compute what the screen size should be.
152  * We use the following algorithm:
153  *	If user specifies with screen option, use that.
154  *	If baud rate < 1200, use  5
155  *	If baud rate = 1200, use 10
156  *	If baud rate > 1200, use 20
157  */
158 int
159 screensize(void)
160 {
161 	register char *cp;
162 	register int newscreensize, tmp;
163 #ifdef	TIOCGWINSZ
164 	struct winsize ws;
165 #endif
166 
167 	if ((cp = value("screen")) != NOSTR && (tmp = atoi(cp)) > 0)
168 		newscreensize = tmp;
169 	else if (baud < B1200)
170 		newscreensize = 5;
171 	else if (baud == B1200)
172 		newscreensize = 10;
173 #ifdef	TIOCGWINSZ
174 	else if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) == 0 && ws.ws_row > 4)
175 		newscreensize = ws.ws_row - 4;
176 #endif
177 	else
178 		newscreensize = 20;
179 	/* renormalize the value of curscreen */
180 	if (newscreensize != oldscreensize) {
181 		curscreen = curscreen * oldscreensize / newscreensize;
182 		oldscreensize = newscreensize;
183 	}
184 	return (newscreensize);
185 }
186 
187 /*
188  * Print out the headlines for each message
189  * in the passed message list.
190  */
191 
192 int
193 from(int *msgvec)
194 {
195 	int *ip;
196 
197 	for (ip = msgvec; *ip != 0; ip++) {
198 		printhead(*ip);
199 		sreset();
200 	}
201 	if (--ip >= msgvec)
202 		dot = &message[*ip - 1];
203 	return (0);
204 }
205 
206 /*
207  * Print out the header of a specific message.
208  * This is a slight improvement to the standard one.
209  */
210 
211 void
212 printhead(int mesg)
213 {
214 	struct message *mp;
215 	FILE *ibuf;
216 	char headline[LINESIZE], *subjline, dispc, curind;
217 	char *fromline;
218 	char pbuf[LINESIZE];
219 	char name[LINESIZE];
220 	headline_t *hl;
221 	register char *cp;
222 	int showto;
223 
224 	if (headline_alloc(&hl) != 0) {
225 		err(1, "could not allocate memory");
226 	}
227 
228 	mp = &message[mesg-1];
229 	ibuf = setinput(mp);
230 	readline(ibuf, headline);
231 	if ((subjline = hfield("subject", mp, addone)) == NOSTR &&
232 	    (subjline = hfield("subj", mp, addone)) == NOSTR &&
233 	    (subjline = hfield("message-status", mp, addone)) == NOSTR)
234 		subjline = "";
235 
236 	curind = (!Hflag && dot == mp) ? '>' : ' ';
237 	dispc = ' ';
238 	showto = 0;
239 	if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
240 		dispc = 'R';
241 	if (!(int)value("bsdcompat") && (mp->m_flag & (MREAD|MNEW)) == MREAD)
242 		dispc = 'O';
243 	if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
244 		dispc = 'N';
245 	if ((mp->m_flag & (MREAD|MNEW)) == 0)
246 		dispc = 'U';
247 	if (mp->m_flag & MSAVED)
248 		if ((int)value("bsdcompat"))
249 			dispc = '*';
250 		else
251 			dispc = 'S';
252 	if (mp->m_flag & MPRESERVE)
253 		if ((int)value("bsdcompat"))
254 			dispc = 'P';
255 		else
256 			dispc = 'H';
257 	if (mp->m_flag & MBOX)
258 		dispc = 'M';
259 	if (parse_headline(headline, hl) == -1) {
260 		headline_reset(hl);
261 	}
262 	if (custr_len(hl->hl_date) == 0) {
263 		if (custr_append(hl->hl_date, "<Unknown date>") != 0) {
264 			err(1, "could not print header");
265 		}
266 	}
267 
268 	/*
269 	 * Netnews interface?
270 	 */
271 
272 	if (newsflg) {
273 		if ((fromline = hfield("newsgroups", mp, addone)) == NOSTR &&
274 		    (fromline = hfield("article-id", mp, addone)) == NOSTR)
275 			fromline = "<>";
276 		else
277 			for (cp = fromline; *cp; cp++) { /* limit length */
278 				if (any(*cp, " ,\n")) {
279 					*cp = '\0';
280 					break;
281 				}
282 			}
283 	/*
284 	 * else regular.
285 	 */
286 
287 	} else {
288 		fromline = nameof(mp);
289 		if (value("showto") &&
290 		    samebody(myname, skin(fromline), FALSE) &&
291 		    (cp = hfield("to", mp, addto))) {
292 			showto = 1;
293 			yankword(cp, fromline = name, sizeof (name),
294 				docomma(cp));
295 		}
296 		if (value("showname"))
297 			fromline = dispname(fromline);
298 		else
299 			fromline = skin(fromline);
300 	}
301 	printf("%c%c%3d ", curind, dispc, mesg);
302 	if ((int)value("showfull")) {
303 		if (showto)
304 			printf("To %-15s ", fromline);
305 		else
306 			printf("%-18s ", fromline);
307 	} else {
308 		if (showto)
309 			printf("To %-15.15s ", fromline);
310 		else
311 			printf("%-18.18s ", fromline);
312 	}
313 	if (mp->m_text) {
314 		printf("%16.16s %4ld/%-5ld %-.25s\n",
315 		    custr_cstr(hl->hl_date), mp->m_lines, mp->m_size,
316 		    subjline);
317 	} else {
318 		printf("%16.16s binary/%-5ld %-.25s\n", custr_cstr(hl->hl_date),
319 		    mp->m_size, subjline);
320 	}
321 
322 	headline_free(hl);
323 }
324 
325 /*
326  * Return the full name from an RFC-822 header line
327  * or the last two (or one) component of the address.
328  */
329 
330 static char *
331 dispname(char *hdr)
332 {
333 	char *cp, *cp2;
334 
335 	if (hdr == 0)
336 		return (0);
337 	if (((cp = strchr(hdr, '<')) != 0) && (cp > hdr)) {
338 		*cp = 0;
339 		if ((*hdr == '"') && ((cp = strrchr(++hdr, '"')) != 0))
340 			*cp = 0;
341 		return (hdr);
342 	} else if ((cp = strchr(hdr, '(')) != 0) {
343 		hdr = ++cp;
344 		if ((cp = strchr(hdr, '+')) != 0)
345 			*cp = 0;
346 		if ((cp = strrchr(hdr, ')')) != 0)
347 			*cp = 0;
348 		return (hdr);
349 	}
350 	cp = skin(hdr);
351 	if ((cp2 = strrchr(cp, '!')) != 0) {
352 		while (cp2 >= cp && *--cp2 != '!');
353 		cp = ++cp2;
354 	}
355 	return (cp);
356 }
357 
358 /*
359  * Print out the value of dot.
360  */
361 
362 int
363 pdot(void)
364 {
365 	printf("%d\n", dot - &message[0] + 1);
366 	return (0);
367 }
368 
369 /*
370  * Print out all the possible commands.
371  */
372 
373 int
374 pcmdlist(void)
375 {
376 	register const struct cmd *cp;
377 	register int cc;
378 
379 	printf("Commands are:\n");
380 	for (cc = 0, cp = cmdtab; cp->c_name != NULL; cp++) {
381 		cc += strlen(cp->c_name) + 2;
382 		if (cc > 72) {
383 			printf("\n");
384 			cc = strlen(cp->c_name) + 2;
385 		}
386 		if ((cp+1)->c_name != NOSTR)
387 			printf("%s, ", cp->c_name);
388 		else
389 			printf("%s\n", cp->c_name);
390 	}
391 	return (0);
392 }
393 
394 /*
395  * Paginate messages, honor ignored fields.
396  */
397 int
398 more(int *msgvec)
399 {
400 	return (type1(msgvec, 1, 1));
401 }
402 
403 /*
404  * Paginate messages, even printing ignored fields.
405  */
406 int
407 More(int *msgvec)
408 {
409 
410 	return (type1(msgvec, 0, 1));
411 }
412 
413 /*
414  * Type out messages, honor ignored fields.
415  */
416 int
417 type(int *msgvec)
418 {
419 
420 	return (type1(msgvec, 1, 0));
421 }
422 
423 /*
424  * Type out messages, even printing ignored fields.
425  */
426 int
427 Type(int *msgvec)
428 {
429 
430 	return (type1(msgvec, 0, 0));
431 }
432 
433 /*
434  * Type out the messages requested.
435  */
436 static int
437 type1(int *msgvec, int doign, int page)
438 {
439 	int *ip;
440 	register struct message *mp;
441 	register int mesg;
442 	register char *cp;
443 	long nlines;
444 	FILE *obuf;
445 	void (*sigint)(int), (*sigpipe)(int);
446 	int setsigs = 0;
447 
448 	obuf = stdout;
449 	if (setjmp(pipestop)) {
450 		if (obuf != stdout) {
451 			pipef = NULL;
452 			npclose(obuf);
453 		}
454 		goto ret0;
455 	}
456 	if (intty && outtty && (page || (cp = value("crt")) != NOSTR)) {
457 		if (!page) {
458 			nlines = 0;
459 			for (ip = msgvec, nlines = 0;
460 			    *ip && ip-msgvec < msgCount; ip++)
461 				nlines += message[*ip - 1].m_lines;
462 		}
463 		if (page ||
464 		    nlines > (*cp == '\0' ? screensize() - 2 : atoi(cp))) {
465 			obuf = npopen(MORE, "w");
466 			if (obuf == NULL) {
467 				perror(MORE);
468 				obuf = stdout;
469 			} else {
470 				pipef = obuf;
471 				sigint = sigset(SIGINT, SIG_IGN);
472 				sigpipe = sigset(SIGPIPE, brokpipe);
473 				setsigs++;
474 			}
475 		}
476 	}
477 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
478 		mesg = *ip;
479 		touch(mesg);
480 		mp = &message[mesg-1];
481 		dot = mp;
482 		print(mp, obuf, doign);
483 	}
484 	if (obuf != stdout) {
485 		pipef = NULL;
486 		npclose(obuf);
487 	}
488 ret0:
489 	if (setsigs) {
490 		sigset(SIGPIPE, sigpipe);
491 		sigset(SIGINT, sigint);
492 	}
493 	return (0);
494 }
495 
496 /*
497  * Respond to a broken pipe signal --
498  * probably caused by user quitting more.
499  */
500 void
501 #ifdef	__cplusplus
502 brokpipe(int)
503 #else
504 /* ARGSUSED */
505 brokpipe(int s)
506 #endif
507 {
508 #ifdef OLD_BSD_SIGS
509 	sigrelse(SIGPIPE);
510 #endif
511 	longjmp(pipestop, 1);
512 }
513 
514 /*
515  * Print the indicated message on standard output.
516  */
517 
518 static void
519 print(register struct message *mp, FILE *obuf, int doign)
520 {
521 
522 	if (value("quiet") == NOSTR && (!doign || !isign("message", 0)))
523 		fprintf(obuf, "Message %2d:\n", mp - &message[0] + 1);
524 	touch(mp - &message[0] + 1);
525 	if (mp->m_text) {
526 		(void) msend(mp, obuf, doign ? M_IGNORE : 0, fputs);
527 	} else {
528 		fprintf(obuf, "\n%s\n", gettext(binmsg));
529 	}
530 }
531 
532 /*
533  * Print the top so many lines of each desired message.
534  * The number of lines is taken from the variable "toplines"
535  * and defaults to 5.
536  */
537 
538 static	long	top_linecount, top_maxlines, top_lineb;
539 static	jmp_buf	top_buf;
540 
541 int
542 top(int *msgvec)
543 {
544 	register int *ip;
545 	register struct message *mp;
546 	register int mesg;
547 	char *valtop;
548 
549 	top_maxlines = 5;
550 	valtop = value("toplines");
551 	if (valtop != NOSTR) {
552 		top_maxlines = atoi(valtop);
553 		if (top_maxlines < 0 || top_maxlines > 10000)
554 			top_maxlines = 5;
555 	}
556 	top_lineb = 1;
557 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
558 		mesg = *ip;
559 		touch(mesg);
560 		mp = &message[mesg-1];
561 		dot = mp;
562 		if (value("quiet") == NOSTR)
563 			printf("Message %2d:\n", mesg);
564 		if (!top_lineb)
565 			printf("\n");
566 		top_linecount = 0;
567 		if (setjmp(top_buf) == 0) {
568 			if (mp->m_text) {
569 				(void) msend(mp, stdout, M_IGNORE, topputs);
570 			} else {
571 				printf("\n%s\n", gettext(binmsg));
572 			}
573 		}
574 	}
575 	return (0);
576 }
577 
578 int
579 topputs(const char *line, FILE *obuf)
580 {
581 	if (top_linecount++ >= top_maxlines)
582 		longjmp(top_buf, 1);
583 	top_lineb = blankline(line);
584 	return (fputs(line, obuf));
585 }
586 
587 /*
588  * Touch all the given messages so that they will
589  * get mboxed.
590  */
591 
592 int
593 stouch(int msgvec[])
594 {
595 	register int *ip;
596 
597 	for (ip = msgvec; *ip != 0; ip++) {
598 		dot = &message[*ip-1];
599 		dot->m_flag |= MTOUCH;
600 		dot->m_flag &= ~MPRESERVE;
601 	}
602 	return (0);
603 }
604 
605 /*
606  * Make sure all passed messages get mboxed.
607  */
608 
609 int
610 mboxit(int msgvec[])
611 {
612 	register int *ip;
613 
614 	for (ip = msgvec; *ip != 0; ip++) {
615 		dot = &message[*ip-1];
616 		dot->m_flag |= MTOUCH|MBOX;
617 		dot->m_flag &= ~MPRESERVE;
618 	}
619 	return (0);
620 }
621 
622 /*
623  * List the folders the user currently has.
624  */
625 int
626 folders(char **arglist)
627 {
628 	char dirname[BUFSIZ], cmd[BUFSIZ];
629 
630 	if (getfold(dirname) < 0) {
631 		printf(gettext("No value set for \"folder\"\n"));
632 		return (-1);
633 	}
634 	if (*arglist) {
635 		nstrcat(dirname, sizeof (dirname), "/");
636 		nstrcat(dirname, sizeof (dirname), *arglist);
637 	}
638 	snprintf(cmd, sizeof (cmd), "%s %s", LS, dirname);
639 	return (system(cmd));
640 }
641