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