xref: /illumos-gate/usr/src/cmd/mailx/util.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 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 1985-2002 Sun Microsystems, Inc. All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 #include "rcv.h"
44 #include <locale.h>
45 
46 /*
47  * mailx -- a modified version of a University of California at Berkeley
48  *	mail program
49  *
50  * Auxiliary functions.
51  */
52 
53 static char	*phrase(char *name, int token, int comma);
54 static char	*ripoff(register char *buf);
55 
56 /*
57  * Return a pointer to a dynamic copy of the argument.
58  */
59 
60 char *
61 savestr(char *str)
62 {
63 	register char *cp, *cp2, *top;
64 
65 	for (cp = str; *cp; cp++)
66 		;
67 	top = (char *)salloc((unsigned)(cp-str + 1));
68 	if (top == NOSTR)
69 		return(NOSTR);
70 	for (cp = str, cp2 = top; *cp; cp++)
71 		*cp2++ = *cp;
72 	*cp2 = 0;
73 	return(top);
74 }
75 
76 /*
77  * Announce a fatal error and die.
78  */
79 
80 void
81 panic(char *str)
82 {
83 	fprintf(stderr, gettext("mailx: Panic - %s\n"), str);
84 	exit(1);
85 	/* NOTREACHED */
86 }
87 
88 /*
89  * Touch the named message by setting its MTOUCH flag.
90  * Touched messages have the effect of not being sent
91  * back to the system mailbox on exit.
92  */
93 
94 void
95 touch(int mesg)
96 {
97 	register struct message *mp;
98 
99 	if (mesg < 1 || mesg > msgCount)
100 		return;
101 	mp = &message[mesg-1];
102 	mp->m_flag |= MTOUCH;
103 	if ((mp->m_flag & MREAD) == 0)
104 		mp->m_flag |= MREAD|MSTATUS;
105 }
106 
107 /*
108  * Test to see if the passed file name is a directory.
109  * Return true if it is.
110  */
111 
112 int
113 isdir(char name[])
114 {
115 	struct stat sbuf;
116 
117 	if (stat(name, &sbuf) < 0)
118 		return(0);
119 	return((sbuf.st_mode & S_IFMT) == S_IFDIR);
120 }
121 
122 /*
123  * Count the number of arguments in the given string raw list.
124  */
125 
126 int
127 argcount(char **argv)
128 {
129 	register char **ap;
130 
131 	for (ap = argv; *ap != NOSTR; ap++)
132 		;
133 	return(ap-argv);
134 }
135 
136 /*
137  * Return the desired header line from the passed message
138  * pointer (or NOSTR if the desired header field is not available).
139  * Read all the header lines and concatenate multiple instances of
140  * the requested header.
141  */
142 
143 char *
144 hfield(char field[], struct message *mp, char *(*add)(char *, char *))
145 {
146 	register FILE *ibuf;
147 	char linebuf[LINESIZE];
148 	register long lc;
149 	char *r = NOSTR;
150 
151 	ibuf = setinput(mp);
152 	if ((lc = mp->m_lines) <= 0)
153 		return(NOSTR);
154 	if (readline(ibuf, linebuf) < 0)
155 		return(NOSTR);
156 	lc--;
157 	while ((lc = gethfield(ibuf, linebuf, lc)) >= 0)
158 		if (ishfield(linebuf, field))
159 			r = (*add)(r, hcontents(linebuf));
160 	return r;
161 }
162 
163 /*
164  * Return the next header field found in the given message.
165  * Return > 0 if something found, <= 0 elsewise.
166  * Must deal with \ continuations & other such fraud.
167  */
168 
169 int
170 gethfield(
171 	register FILE *f,
172 	char linebuf[],
173 	register long rem)
174 {
175 	char line2[LINESIZE];
176 	register char *cp, *cp2;
177 	register int c;
178 
179 	for (;;) {
180 		if (rem <= 0)
181 			return(-1);
182 		if (readline(f, linebuf) < 0)
183 			return(-1);
184 		rem--;
185 		if (strlen(linebuf) == 0)
186 			return(-1);
187 		if (isspace(linebuf[0]))
188 			continue;
189 		if (!headerp(linebuf))
190 			return(-1);
191 
192 		/*
193 		 * I guess we got a headline.
194 		 * Handle wraparounding
195 		 */
196 
197 		for (;;) {
198 			if (rem <= 0)
199 				break;
200 			c = getc(f);
201 			ungetc(c, f);
202 			if (!isspace(c) || c == '\n')
203 				break;
204 			if (readline(f, line2) < 0)
205 				break;
206 			rem--;
207 			cp2 = line2;
208 			for (cp2 = line2; *cp2 != 0 && isspace(*cp2); cp2++)
209 				;
210 			if (strlen(linebuf) + strlen(cp2) >=
211 			    (unsigned)LINESIZE-2)
212 				break;
213 			cp = &linebuf[strlen(linebuf)];
214 			while (cp > linebuf &&
215 			    (isspace(cp[-1]) || cp[-1] == '\\'))
216 				cp--;
217 			*cp++ = ' ';
218 			for (cp2 = line2; *cp2 != 0 && isspace(*cp2); cp2++)
219 				;
220 			nstrcpy(cp, LINESIZE - (cp - linebuf), cp2);
221 		}
222 		if ((c = strlen(linebuf)) > 0) {
223 			cp = &linebuf[c-1];
224 			while (cp > linebuf && isspace(*cp))
225 				cp--;
226 			*++cp = 0;
227 		}
228 		return(rem);
229 	}
230 	/* NOTREACHED */
231 }
232 
233 /*
234  * Check whether the passed line is a header line of
235  * the desired breed.
236  */
237 
238 int
239 ishfield(char linebuf[], char field[])
240 {
241 	register char *cp;
242 
243 	if ((cp = strchr(linebuf, ':')) == NOSTR)
244 		return(0);
245 	if (cp == linebuf)
246 		return(0);
247 	*cp = 0;
248 	if (icequal(linebuf, field)) {
249 		*cp = ':';
250 		return(1);
251 	}
252 	*cp = ':';
253 	return(0);
254 }
255 
256 /*
257  * Extract the non label information from the given header field
258  * and return it.
259  */
260 
261 char *
262 hcontents(char hfield[])
263 {
264 	register char *cp;
265 
266 	if ((cp = strchr(hfield, ':')) == NOSTR)
267 		return(NOSTR);
268 	cp++;
269 	while (*cp && isspace(*cp))
270 		cp++;
271 	return(cp);
272 }
273 
274 /*
275  * Compare two strings, ignoring case.
276  */
277 
278 int
279 icequal(register char *s1, register char *s2)
280 {
281 
282 	while (toupper(*s1++) == toupper(*s2))
283 		if (*s2++ == 0)
284 			return(1);
285 	return(0);
286 }
287 
288 /*
289  * Copy a string, lowercasing it as we go. Here dstsize is the size of
290  * the destination buffer dst.
291  */
292 void
293 istrcpy(char *dst, int dstsize, char *src)
294 {
295 	register char *cp, *cp2;
296 
297 	cp2 = dst;
298 	cp = src;
299 
300 	while (--dstsize > 0 && *cp != '\0')
301 		*cp2++ = tolower(*cp++);
302 	*cp2 = '\0';
303 }
304 
305 /*
306  * The following code deals with input stacking to do source
307  * commands.  All but the current file pointer are saved on
308  * the stack.
309  */
310 
311 static	int	ssp = -1;		/* Top of file stack */
312 static struct sstack {
313 	FILE	*s_file;		/* File we were in. */
314 	int	s_cond;			/* Saved state of conditionals */
315 	int	s_loading;		/* Loading .mailrc, etc. */
316 } *sstack;
317 
318 /*
319  * Pushdown current input file and switch to a new one.
320  * Set the global flag "sourcing" so that others will realize
321  * that they are no longer reading from a tty (in all probability).
322  */
323 
324 int
325 source(char name[])
326 {
327 	register FILE *fi;
328 	register char *cp;
329 
330 	if ((cp = expand(name)) == NOSTR)
331 		return(1);
332 	if ((fi = fopen(cp, "r")) == NULL) {
333 		printf(gettext("Unable to open %s\n"), cp);
334 		return(1);
335 	}
336 
337 	if (!maxfiles) {
338 		if ((maxfiles = (int)ulimit(4, 0)) < 0)
339 #ifndef _NFILE
340 # define _NFILE 20
341 #endif
342 			maxfiles = _NFILE;
343 		sstack = (struct sstack *)calloc(maxfiles, sizeof(struct sstack));
344 		if (sstack == NULL) {
345 			printf(gettext(
346 			    "Couldn't allocate memory for sourcing stack\n"));
347 			fclose(fi);
348 			return(1);
349 		}
350 	}
351 
352 	sstack[++ssp].s_file = input;
353 	sstack[ssp].s_cond = cond;
354 	sstack[ssp].s_loading = loading;
355 	loading = 0;
356 	cond = CANY;
357 	input = fi;
358 	sourcing++;
359 	return(0);
360 }
361 
362 /*
363  * Pop the current input back to the previous level.
364  * Update the "sourcing" flag as appropriate.
365  */
366 
367 int
368 unstack(void)
369 {
370 	if (ssp < 0) {
371 		printf(gettext("\"Source\" stack over-pop.\n"));
372 		sourcing = 0;
373 		return(1);
374 	}
375 	fclose(input);
376 	if (cond != CANY)
377 		printf(gettext("Unmatched \"if\"\n"));
378 	cond = sstack[ssp].s_cond;
379 	loading = sstack[ssp].s_loading;
380 	input = sstack[ssp--].s_file;
381 	if (ssp < 0)
382 		sourcing = loading;
383 	return(0);
384 }
385 
386 /*
387  * Touch the indicated file.
388  * This is nifty for the shell.
389  * If we have the utime() system call, this is better served
390  * by using that, since it will work for empty files.
391  * On non-utime systems, we must sleep a second, then read.
392  */
393 
394 void
395 alter(char name[])
396 {
397 	int rc = utime(name, utimep);
398 	extern int errno;
399 
400 	if (rc != 0) {
401 		fprintf(stderr, gettext("Cannot utime %s in aux:alter\n"),
402 name);
403 		fprintf(stderr, gettext("Errno: %d\n"), errno);
404 	}
405 }
406 
407 /*
408  * Examine the passed line buffer and
409  * return true if it is all blanks and tabs.
410  */
411 
412 int
413 blankline(const char linebuf[])
414 {
415 	register const char *cp;
416 
417 	for (cp = linebuf; *cp; cp++)
418 		if (!any(*cp, " \t"))
419 			return(0);
420 	return(1);
421 }
422 
423 /*
424  * Skin an arpa net address according to the RFC 822 interpretation
425  * of "host-phrase."
426  */
427 static char *
428 phrase(char *name, int token, int comma)
429 {
430 	register char c;
431 	register char *cp, *cp2;
432 	char *bufend, *nbufp;
433 	int gotlt, lastsp, didq;
434 	char nbuf[LINESIZE];
435 	int nesting;
436 
437 	if (name == NOSTR)
438 		return(NOSTR);
439 	if (strlen(name) >= (unsigned)LINESIZE)
440 		nbufp = (char *)salloc(strlen(name));
441 	else
442 		nbufp = nbuf;
443 	gotlt = 0;
444 	lastsp = 0;
445 	bufend = nbufp;
446 	for (cp = name, cp2 = bufend; (c = *cp++) != 0;) {
447 		switch (c) {
448 		case '(':
449 			/*
450 				Start of a comment, ignore it.
451 			*/
452 			nesting = 1;
453 			while ((c = *cp) != 0) {
454 				cp++;
455 				switch(c) {
456 				case '\\':
457 					if (*cp == 0) goto outcm;
458 					cp++;
459 					break;
460 				case '(':
461 					nesting++;
462 					break;
463 				case ')':
464 					--nesting;
465 					break;
466 				}
467 				if (nesting <= 0) break;
468 			}
469 		outcm:
470 			lastsp = 0;
471 			break;
472 		case '"':
473 			/*
474 				Start a quoted string.
475 				Copy it in its entirety.
476 			*/
477 			didq = 0;
478 			while ((c = *cp) != 0) {
479 				cp++;
480 				switch (c) {
481 				case '\\':
482 					if ((c = *cp) == 0) goto outqs;
483 					cp++;
484 					break;
485 				case '"':
486 					goto outqs;
487 				}
488 				if (gotlt == 0 || gotlt == '<') {
489 					if (lastsp) {
490 						lastsp = 0;
491 						*cp2++ = ' ';
492 					}
493 					if (!didq) {
494 						*cp2++ = '"';
495 						didq++;
496 					}
497 					*cp2++ = c;
498 				}
499 			}
500 		outqs:
501 			if (didq)
502 				*cp2++ = '"';
503 			lastsp = 0;
504 			break;
505 
506 		case ' ':
507 		case '\t':
508 		case '\n':
509 			if (token && (!comma || c == '\n')) {
510 			done:
511 				cp[-1] = 0;
512 				return cp;
513 			}
514 			lastsp = 1;
515 			break;
516 
517 		case ',':
518 			*cp2++ = c;
519 			if (gotlt != '<') {
520 				if (token)
521 					goto done;
522 				bufend = cp2;
523 				gotlt = 0;
524 			}
525 			break;
526 
527 		case '<':
528 			cp2 = bufend;
529 			gotlt = c;
530 			lastsp = 0;
531 			break;
532 
533 		case '>':
534 			if (gotlt == '<') {
535 				gotlt = c;
536 				break;
537 			}
538 
539 			/* FALLTHROUGH . . . */
540 
541 		default:
542 			if (gotlt == 0 || gotlt == '<') {
543 				if (lastsp) {
544 					lastsp = 0;
545 					*cp2++ = ' ';
546 				}
547 				*cp2++ = c;
548 			}
549 			break;
550 		}
551 	}
552 	*cp2 = 0;
553 	return (token ? --cp : equal(name, nbufp) ? name :
554 	    nbufp == nbuf ? savestr(nbuf) : nbufp);
555 }
556 
557 char *
558 skin(char *name)
559 {
560 	return phrase(name, 0, 0);
561 }
562 
563 /*
564  * Here sz is the buffer size of word.
565  */
566 char *
567 yankword(char *name, char *word, int sz, int comma)
568 {
569 	char *cp;
570 
571 	if (name == 0)
572 		return 0;
573 	while (isspace(*name))
574 		name++;
575 	if (*name == 0)
576 		return 0;
577 	cp = phrase(name, 1, comma);
578 	nstrcpy(word, sz, name);
579 	return cp;
580 }
581 
582 int
583 docomma(char *s)
584 {
585 	return s && strpbrk(s, "(<,");
586 }
587 
588 /*
589  * Fetch the sender's name from the passed message.
590  */
591 
592 char *
593 nameof(register struct message *mp)
594 {
595 	char namebuf[LINESIZE];
596 	char linebuf[LINESIZE];
597 	register char *cp, *cp2;
598 	register FILE *ibuf;
599 	int first = 1, wint = 0;
600 	char	*tmp;
601 
602 	if (value("from") && (cp = hfield("from", mp, addto)) != NOSTR)
603 		return ripoff(cp);
604 	ibuf = setinput(mp);
605 	copy("", namebuf);
606 	if (readline(ibuf, linebuf) <= 0)
607 		return(savestr(namebuf));
608 newname:
609 	for (cp = linebuf; *cp != ' '; cp++)
610 		;
611 	while (any(*cp, " \t"))
612 		cp++;
613 	for (cp2 = &namebuf[strlen(namebuf)]; *cp && !any(*cp, " \t") &&
614 	    cp2-namebuf < LINESIZE-1; *cp2++ = *cp++)
615 		;
616 	*cp2 = '\0';
617 	for (;;) {
618 		if (readline(ibuf, linebuf) <= 0)
619 			break;
620 		if (substr(linebuf,"forwarded by ") != -1)
621 			continue;
622 		if (linebuf[0] == 'F')
623 			cp = linebuf;
624 		else if (linebuf[0] == '>')
625 			cp = linebuf + 1;
626 		else
627 			break;
628 		if (strncmp(cp, "From ", 5) != 0)
629 			break;
630 		if ((wint = substr(cp, "remote from ")) != -1) {
631 			cp += wint + 12;
632 			if (first) {
633 				copy(cp, namebuf);
634 				first = 0;
635 			} else {
636 				tmp = strrchr(namebuf, '!') + 1;
637 				nstrcpy(tmp,
638 					sizeof (namebuf) - (tmp  - namebuf),
639 					cp);
640 			}
641 			nstrcat(namebuf, sizeof (namebuf), "!");
642 			goto newname;
643 		} else
644 			break;
645 	}
646 	for (cp = namebuf; *cp == '!'; cp++);
647 	while (ishost(host, cp))
648 		cp = strchr(cp, '!') + 1;
649 	if (value("mustbang") && !strchr(cp, '!')) {
650 		snprintf(linebuf, sizeof (linebuf), "%s!%s",
651 			host, cp);
652 		cp = linebuf;
653 	}
654 	if (cp2 = hfield("from", mp, addto))
655 		return(splice(cp, cp2));
656 	else
657 		return(savestr(cp));
658 }
659 
660 /*
661  * Splice an address into a commented recipient header.
662  */
663 char *
664 splice(char *addr, char *hdr)
665 {
666 	char buf[LINESIZE];
667 	char *cp, *cp2;
668 
669 	if (cp = strchr(hdr, '<')) {
670 		cp2 = strchr(cp, '>');
671 		if (cp2 == NULL) {
672 			nstrcpy(buf, sizeof (buf), addr);
673 		} else {
674 			snprintf(buf, sizeof (buf), "%.*s%s%s",
675 				cp - hdr + 1, hdr, addr, cp2);
676 		}
677 	} else if (cp = strchr(hdr, '(')) {
678 		snprintf(buf, sizeof (buf), "%s %s",
679 			addr, cp);
680 	} else
681 		nstrcpy(buf, sizeof (buf), addr);
682 	return savestr(ripoff(buf));
683 }
684 
685 static char *
686 ripoff(register char *buf)
687 {
688 	register char *cp;
689 
690 	cp = buf + strlen(buf);
691 	while (--cp >= buf && isspace(*cp));
692 	if (cp >= buf && *cp == ',')
693 		cp--;
694 	*++cp = 0;
695 	return buf;
696 }
697 
698 /*
699  * Are any of the characters in the two strings the same?
700  */
701 
702 int
703 anyof(register char *s1, register char *s2)
704 {
705 	register int c;
706 
707 	while ((c = *s1++) != 0)
708 		if (any(c, s2))
709 			return(1);
710 	return(0);
711 }
712 
713 /*
714  * See if the given header field is supposed to be ignored.
715  * Fields of the form "Content-*" can't be ignored when saving.
716  */
717 int
718 isign(char *field, int saving)
719 {
720 	char realfld[BUFSIZ];
721 
722 	/*
723 	 * Lower-case the string, so that "Status" and "status"
724 	 * will hash to the same place.
725 	 */
726 	istrcpy(realfld, sizeof (realfld), field);
727 
728 	if (saving && strncmp(realfld, "content-", 8) == 0)
729 		return (0);
730 
731 	if (nretained > 0)
732 		return (!member(realfld, retain));
733 	else
734 		return (member(realfld, ignore));
735 }
736 
737 int
738 member(register char *realfield, register struct ignore **table)
739 {
740 	register struct ignore *igp;
741 
742 	for (igp = table[hash(realfield)]; igp != 0; igp = igp->i_link)
743 		if (equal(igp->i_field, realfield))
744 			return (1);
745 
746 	return (0);
747 }
748 
749 /*
750  * This routine looks for string2 in string1.
751  * If found, it returns the position string2 is found at,
752  * otherwise it returns a -1.
753  */
754 int
755 substr(char *string1, char *string2)
756 {
757 	int i, j, len1, len2;
758 
759 	len1 = strlen(string1);
760 	len2 = strlen(string2);
761 	for (i = 0; i < len1 - len2 + 1; i++) {
762 		for (j = 0; j < len2 && string1[i+j] == string2[j]; j++)
763 			;
764 		if (j == len2)
765 			return(i);
766 	}
767 	return(-1);
768 }
769 
770 /*
771  * Copies src to the dstsize buffer at dst. The copy will never
772  * overflow the destination buffer and the buffer will always be null
773  * terminated.
774  */
775 char *
776 nstrcpy(char *dst, int dstsize, char *src)
777 {
778 	char *cp, *cp2;
779 
780 	cp2 = dst;
781 	cp = src;
782 
783 	while (--dstsize > 0 && *cp != '\0')
784 		*cp2++ = *cp++;
785 	*cp2 = '\0';
786 	return(dst);
787 }
788 
789 /*
790  * Appends src to the dstsize buffer at dst. The append will never
791  * overflow the destination buffer and the buffer will always be null
792  * terminated.
793  */
794 char *
795 nstrcat(char *dst, int dstsize, char *src)
796 {
797 	char *cp, *cp2;
798 
799 	cp2 = dst;
800 	cp = src;
801 
802 	while (*cp2 != '\0') {
803 		cp2++;
804 		dstsize--;
805 	}
806 	while (--dstsize > 0 && *cp != '\0')
807 		*cp2++ = *cp++;
808 	*cp2 = '\0';
809 	return(dst);
810 }
811