xref: /original-bsd/usr.sbin/sendmail/src/util.c (revision 260a368e)
1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988 Regents of the University of California.
4  * All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)util.c	6.15 (Berkeley) 04/27/93";
11 #endif /* not lint */
12 
13 # include "sendmail.h"
14 # include <sysexits.h>
15 /*
16 **  STRIPQUOTES -- Strip quotes & quote bits from a string.
17 **
18 **	Runs through a string and strips off unquoted quote
19 **	characters and quote bits.  This is done in place.
20 **
21 **	Parameters:
22 **		s -- the string to strip.
23 **
24 **	Returns:
25 **		none.
26 **
27 **	Side Effects:
28 **		none.
29 **
30 **	Called By:
31 **		deliver
32 */
33 
34 stripquotes(s)
35 	char *s;
36 {
37 	register char *p;
38 	register char *q;
39 	register char c;
40 
41 	if (s == NULL)
42 		return;
43 
44 	p = q = s;
45 	do
46 	{
47 		c = *p++;
48 		if (c == '\\')
49 			c = *p++;
50 		else if (c == '"')
51 			continue;
52 		*q++ = c;
53 	} while (c != '\0');
54 }
55 /*
56 **  CAPITALIZE -- return a copy of a string, properly capitalized.
57 **
58 **	Parameters:
59 **		s -- the string to capitalize.
60 **
61 **	Returns:
62 **		a pointer to a properly capitalized string.
63 **
64 **	Side Effects:
65 **		none.
66 */
67 
68 char *
69 capitalize(s)
70 	register char *s;
71 {
72 	static char buf[50];
73 	register char *p;
74 
75 	p = buf;
76 
77 	for (;;)
78 	{
79 		while (!(isascii(*s) && isalpha(*s)) && *s != '\0')
80 			*p++ = *s++;
81 		if (*s == '\0')
82 			break;
83 		*p++ = toupper(*s);
84 		s++;
85 		while (isascii(*s) && isalpha(*s))
86 			*p++ = *s++;
87 	}
88 
89 	*p = '\0';
90 	return (buf);
91 }
92 /*
93 **  XALLOC -- Allocate memory and bitch wildly on failure.
94 **
95 **	THIS IS A CLUDGE.  This should be made to give a proper
96 **	error -- but after all, what can we do?
97 **
98 **	Parameters:
99 **		sz -- size of area to allocate.
100 **
101 **	Returns:
102 **		pointer to data region.
103 **
104 **	Side Effects:
105 **		Memory is allocated.
106 */
107 
108 char *
109 xalloc(sz)
110 	register int sz;
111 {
112 	register char *p;
113 
114 	p = malloc((unsigned) sz);
115 	if (p == NULL)
116 	{
117 		syserr("Out of memory!!");
118 		abort();
119 		/* exit(EX_UNAVAILABLE); */
120 	}
121 	return (p);
122 }
123 /*
124 **  COPYPLIST -- copy list of pointers.
125 **
126 **	This routine is the equivalent of newstr for lists of
127 **	pointers.
128 **
129 **	Parameters:
130 **		list -- list of pointers to copy.
131 **			Must be NULL terminated.
132 **		copycont -- if TRUE, copy the contents of the vector
133 **			(which must be a string) also.
134 **
135 **	Returns:
136 **		a copy of 'list'.
137 **
138 **	Side Effects:
139 **		none.
140 */
141 
142 char **
143 copyplist(list, copycont)
144 	char **list;
145 	bool copycont;
146 {
147 	register char **vp;
148 	register char **newvp;
149 
150 	for (vp = list; *vp != NULL; vp++)
151 		continue;
152 
153 	vp++;
154 
155 	newvp = (char **) xalloc((int) (vp - list) * sizeof *vp);
156 	bcopy((char *) list, (char *) newvp, (int) (vp - list) * sizeof *vp);
157 
158 	if (copycont)
159 	{
160 		for (vp = newvp; *vp != NULL; vp++)
161 			*vp = newstr(*vp);
162 	}
163 
164 	return (newvp);
165 }
166 /*
167 **  COPYQUEUE -- copy address queue.
168 **
169 **	This routine is the equivalent of newstr for address queues
170 **	addresses marked with QDONTSEND aren't copied
171 **
172 **	Parameters:
173 **		addr -- list of address structures to copy.
174 **
175 **	Returns:
176 **		a copy of 'addr'.
177 **
178 **	Side Effects:
179 **		none.
180 */
181 
182 ADDRESS *
183 copyqueue(addr)
184 	ADDRESS *addr;
185 {
186 	register ADDRESS *newaddr;
187 	ADDRESS *ret;
188 	register ADDRESS **tail = &ret;
189 
190 	while (addr != NULL)
191 	{
192 		if (!bitset(QDONTSEND, addr->q_flags))
193 		{
194 			newaddr = (ADDRESS *) xalloc(sizeof(ADDRESS));
195 			STRUCTCOPY(*addr, *newaddr);
196 			*tail = newaddr;
197 			tail = &newaddr->q_next;
198 		}
199 		addr = addr->q_next;
200 	}
201 	*tail = NULL;
202 
203 	return ret;
204 }
205 /*
206 **  PRINTAV -- print argument vector.
207 **
208 **	Parameters:
209 **		av -- argument vector.
210 **
211 **	Returns:
212 **		none.
213 **
214 **	Side Effects:
215 **		prints av.
216 */
217 
218 printav(av)
219 	register char **av;
220 {
221 	while (*av != NULL)
222 	{
223 		if (tTd(0, 44))
224 			printf("\n\t%08x=", *av);
225 		else
226 			(void) putchar(' ');
227 		xputs(*av++);
228 	}
229 	(void) putchar('\n');
230 }
231 /*
232 **  LOWER -- turn letter into lower case.
233 **
234 **	Parameters:
235 **		c -- character to turn into lower case.
236 **
237 **	Returns:
238 **		c, in lower case.
239 **
240 **	Side Effects:
241 **		none.
242 */
243 
244 char
245 lower(c)
246 	register char c;
247 {
248 	return((isascii(c) && isupper(c)) ? tolower(c) : c);
249 }
250 /*
251 **  XPUTS -- put string doing control escapes.
252 **
253 **	Parameters:
254 **		s -- string to put.
255 **
256 **	Returns:
257 **		none.
258 **
259 **	Side Effects:
260 **		output to stdout
261 */
262 
263 xputs(s)
264 	register char *s;
265 {
266 	register int c;
267 	register struct metamac *mp;
268 	extern struct metamac MetaMacros[];
269 
270 	if (s == NULL)
271 	{
272 		printf("<null>");
273 		return;
274 	}
275 	while ((c = (*s++ & 0377)) != '\0')
276 	{
277 		if (!isascii(c))
278 		{
279 			if (c == MATCHREPL || c == MACROEXPAND)
280 			{
281 				putchar('$');
282 				continue;
283 			}
284 			for (mp = MetaMacros; mp->metaname != '\0'; mp++)
285 			{
286 				if ((mp->metaval & 0377) == c)
287 				{
288 					printf("$%c", mp->metaname);
289 					break;
290 				}
291 			}
292 			if (mp->metaname != '\0')
293 				continue;
294 			(void) putchar('\\');
295 			c &= 0177;
296 		}
297 		if (isprint(c))
298 		{
299 			putchar(c);
300 			continue;
301 		}
302 
303 		/* wasn't a meta-macro -- find another way to print it */
304 		switch (c)
305 		{
306 		  case '\0':
307 			continue;
308 
309 		  case '\n':
310 			c = 'n';
311 			break;
312 
313 		  case '\r':
314 			c = 'r';
315 			break;
316 
317 		  case '\t':
318 			c = 't';
319 			break;
320 
321 		  default:
322 			(void) putchar('^');
323 			(void) putchar(c ^ 0100);
324 			continue;
325 		}
326 	}
327 	(void) fflush(stdout);
328 }
329 /*
330 **  MAKELOWER -- Translate a line into lower case
331 **
332 **	Parameters:
333 **		p -- the string to translate.  If NULL, return is
334 **			immediate.
335 **
336 **	Returns:
337 **		none.
338 **
339 **	Side Effects:
340 **		String pointed to by p is translated to lower case.
341 **
342 **	Called By:
343 **		parse
344 */
345 
346 makelower(p)
347 	register char *p;
348 {
349 	register char c;
350 
351 	if (p == NULL)
352 		return;
353 	for (; (c = *p) != '\0'; p++)
354 		if (isascii(c) && isupper(c))
355 			*p = tolower(c);
356 }
357 /*
358 **  BUILDFNAME -- build full name from gecos style entry.
359 **
360 **	This routine interprets the strange entry that would appear
361 **	in the GECOS field of the password file.
362 **
363 **	Parameters:
364 **		p -- name to build.
365 **		login -- the login name of this user (for &).
366 **		buf -- place to put the result.
367 **
368 **	Returns:
369 **		none.
370 **
371 **	Side Effects:
372 **		none.
373 */
374 
375 buildfname(gecos, login, buf)
376 	register char *gecos;
377 	char *login;
378 	char *buf;
379 {
380 	register char *p;
381 	register char *bp = buf;
382 	int l;
383 
384 	if (*gecos == '*')
385 		gecos++;
386 
387 	/* find length of final string */
388 	l = 0;
389 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
390 	{
391 		if (*p == '&')
392 			l += strlen(login);
393 		else
394 			l++;
395 	}
396 
397 	/* now fill in buf */
398 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
399 	{
400 		if (*p == '&')
401 		{
402 			(void) strcpy(bp, login);
403 			*bp = toupper(*bp);
404 			while (*bp != '\0')
405 				bp++;
406 		}
407 		else
408 			*bp++ = *p;
409 	}
410 	*bp = '\0';
411 }
412 /*
413 **  SAFEFILE -- return true if a file exists and is safe for a user.
414 **
415 **	Parameters:
416 **		fn -- filename to check.
417 **		uid -- uid to compare against.
418 **		mode -- mode bits that must match.
419 **
420 **	Returns:
421 **		0 if fn exists, is owned by uid, and matches mode.
422 **		An errno otherwise.  The actual errno is cleared.
423 **
424 **	Side Effects:
425 **		none.
426 */
427 
428 int
429 safefile(fn, uid, mode)
430 	char *fn;
431 	uid_t uid;
432 	int mode;
433 {
434 	struct stat stbuf;
435 
436 	if (stat(fn, &stbuf) < 0)
437 	{
438 		int ret = errno;
439 
440 		errno = 0;
441 		return ret;
442 	}
443 	if (stbuf.st_uid == uid && (stbuf.st_mode & mode) == mode)
444 		return 0;
445 	return EPERM;
446 }
447 /*
448 **  FIXCRLF -- fix <CR><LF> in line.
449 **
450 **	Looks for the <CR><LF> combination and turns it into the
451 **	UNIX canonical <NL> character.  It only takes one line,
452 **	i.e., it is assumed that the first <NL> found is the end
453 **	of the line.
454 **
455 **	Parameters:
456 **		line -- the line to fix.
457 **		stripnl -- if true, strip the newline also.
458 **
459 **	Returns:
460 **		none.
461 **
462 **	Side Effects:
463 **		line is changed in place.
464 */
465 
466 fixcrlf(line, stripnl)
467 	char *line;
468 	bool stripnl;
469 {
470 	register char *p;
471 
472 	p = strchr(line, '\n');
473 	if (p == NULL)
474 		return;
475 	if (p > line && p[-1] == '\r')
476 		p--;
477 	if (!stripnl)
478 		*p++ = '\n';
479 	*p = '\0';
480 }
481 /*
482 **  DFOPEN -- determined file open
483 **
484 **	This routine has the semantics of fopen, except that it will
485 **	keep trying a few times to make this happen.  The idea is that
486 **	on very loaded systems, we may run out of resources (inodes,
487 **	whatever), so this tries to get around it.
488 */
489 
490 FILE *
491 dfopen(filename, mode)
492 	char *filename;
493 	char *mode;
494 {
495 	register int tries;
496 	register FILE *fp;
497 	struct stat st;
498 
499 	for (tries = 0; tries < 10; tries++)
500 	{
501 		sleep((unsigned) (10 * tries));
502 		errno = 0;
503 		fp = fopen(filename, mode);
504 		if (fp != NULL)
505 			break;
506 		if (errno != ENFILE && errno != EINTR)
507 			break;
508 	}
509 	if (fp != NULL && fstat(fileno(fp), &st) >= 0 && S_ISREG(st.st_mode))
510 	{
511 		int locktype;
512 		extern bool lockfile();
513 
514 		/* lock the file to avoid accidental conflicts */
515 		if (*mode == 'w' || *mode == 'a')
516 			locktype = LOCK_EX;
517 		else
518 			locktype = LOCK_SH;
519 		(void) lockfile(fileno(fp), filename, locktype);
520 		errno = 0;
521 	}
522 	return (fp);
523 }
524 /*
525 **  PUTLINE -- put a line like fputs obeying SMTP conventions
526 **
527 **	This routine always guarantees outputing a newline (or CRLF,
528 **	as appropriate) at the end of the string.
529 **
530 **	Parameters:
531 **		l -- line to put.
532 **		fp -- file to put it onto.
533 **		m -- the mailer used to control output.
534 **
535 **	Returns:
536 **		none
537 **
538 **	Side Effects:
539 **		output of l to fp.
540 */
541 
542 putline(l, fp, m)
543 	register char *l;
544 	FILE *fp;
545 	MAILER *m;
546 {
547 	register char *p;
548 	register char svchar;
549 
550 	/* strip out 0200 bits -- these can look like TELNET protocol */
551 	if (bitnset(M_7BITS, m->m_flags))
552 	{
553 		for (p = l; svchar = *p; ++p)
554 			if (svchar & 0200)
555 				*p = svchar &~ 0200;
556 	}
557 
558 	do
559 	{
560 		/* find the end of the line */
561 		p = strchr(l, '\n');
562 		if (p == NULL)
563 			p = &l[strlen(l)];
564 
565 		/* check for line overflow */
566 		while (m->m_linelimit > 0 && (p - l) > m->m_linelimit)
567 		{
568 			register char *q = &l[m->m_linelimit - 1];
569 
570 			svchar = *q;
571 			*q = '\0';
572 			if (l[0] == '.' && bitnset(M_XDOT, m->m_flags))
573 				(void) putc('.', fp);
574 			fputs(l, fp);
575 			(void) putc('!', fp);
576 			fputs(m->m_eol, fp);
577 			*q = svchar;
578 			l = q;
579 		}
580 
581 		/* output last part */
582 		if (l[0] == '.' && bitnset(M_XDOT, m->m_flags))
583 			(void) putc('.', fp);
584 		for ( ; l < p; ++l)
585 			(void) putc(*l, fp);
586 		fputs(m->m_eol, fp);
587 		if (*l == '\n')
588 			++l;
589 	} while (l[0] != '\0');
590 }
591 /*
592 **  XUNLINK -- unlink a file, doing logging as appropriate.
593 **
594 **	Parameters:
595 **		f -- name of file to unlink.
596 **
597 **	Returns:
598 **		none.
599 **
600 **	Side Effects:
601 **		f is unlinked.
602 */
603 
604 xunlink(f)
605 	char *f;
606 {
607 	register int i;
608 
609 # ifdef LOG
610 	if (LogLevel > 98)
611 		syslog(LOG_DEBUG, "%s: unlink %s", CurEnv->e_id, f);
612 # endif /* LOG */
613 
614 	i = unlink(f);
615 # ifdef LOG
616 	if (i < 0 && LogLevel > 97)
617 		syslog(LOG_DEBUG, "%s: unlink-fail %d", f, errno);
618 # endif /* LOG */
619 }
620 /*
621 **  XFCLOSE -- close a file, doing logging as appropriate.
622 **
623 **	Parameters:
624 **		fp -- file pointer for the file to close
625 **		a, b -- miscellaneous crud to print for debugging
626 **
627 **	Returns:
628 **		none.
629 **
630 **	Side Effects:
631 **		fp is closed.
632 */
633 
634 xfclose(fp, a, b)
635 	FILE *fp;
636 	char *a, *b;
637 {
638 	if (tTd(53, 99))
639 		printf("xfclose(%x) %s %s\n", fp, a, b);
640 	if (fclose(fp) < 0 && tTd(53, 99))
641 		printf("xfclose FAILURE: %s\n", errstring(errno));
642 }
643 /*
644 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
645 **
646 **	Parameters:
647 **		buf -- place to put the input line.
648 **		siz -- size of buf.
649 **		fp -- file to read from.
650 **		timeout -- the timeout before error occurs.
651 **
652 **	Returns:
653 **		NULL on error (including timeout).  This will also leave
654 **			buf containing a null string.
655 **		buf otherwise.
656 **
657 **	Side Effects:
658 **		none.
659 */
660 
661 static jmp_buf	CtxReadTimeout;
662 
663 char *
664 sfgets(buf, siz, fp, timeout)
665 	char *buf;
666 	int siz;
667 	FILE *fp;
668 	time_t timeout;
669 {
670 	register EVENT *ev = NULL;
671 	register char *p;
672 	static int readtimeout();
673 
674 	/* set the timeout */
675 	if (timeout != 0)
676 	{
677 		if (setjmp(CtxReadTimeout) != 0)
678 		{
679 # ifdef LOG
680 			syslog(LOG_NOTICE,
681 			    "timeout waiting for input from %s\n",
682 			    CurHostName? CurHostName: "local");
683 # endif
684 			errno = 0;
685 			usrerr("451 timeout waiting for input");
686 			buf[0] = '\0';
687 			return (NULL);
688 		}
689 		ev = setevent(timeout, readtimeout, 0);
690 	}
691 
692 	/* try to read */
693 	p = NULL;
694 	while (p == NULL && !feof(fp) && !ferror(fp))
695 	{
696 		errno = 0;
697 		p = fgets(buf, siz, fp);
698 		if (errno == EINTR)
699 			clearerr(fp);
700 	}
701 
702 	/* clear the event if it has not sprung */
703 	clrevent(ev);
704 
705 	/* clean up the books and exit */
706 	LineNumber++;
707 	if (p == NULL)
708 	{
709 		buf[0] = '\0';
710 		return (NULL);
711 	}
712 	if (!EightBit)
713 		for (p = buf; *p != '\0'; p++)
714 			*p &= ~0200;
715 	return (buf);
716 }
717 
718 static
719 readtimeout()
720 {
721 	longjmp(CtxReadTimeout, 1);
722 }
723 /*
724 **  FGETFOLDED -- like fgets, but know about folded lines.
725 **
726 **	Parameters:
727 **		buf -- place to put result.
728 **		n -- bytes available.
729 **		f -- file to read from.
730 **
731 **	Returns:
732 **		input line(s) on success, NULL on error or EOF.
733 **		This will normally be buf -- unless the line is too
734 **			long, when it will be xalloc()ed.
735 **
736 **	Side Effects:
737 **		buf gets lines from f, with continuation lines (lines
738 **		with leading white space) appended.  CRLF's are mapped
739 **		into single newlines.  Any trailing NL is stripped.
740 */
741 
742 char *
743 fgetfolded(buf, n, f)
744 	char *buf;
745 	register int n;
746 	FILE *f;
747 {
748 	register char *p = buf;
749 	char *bp = buf;
750 	register int i;
751 
752 	n--;
753 	while ((i = getc(f)) != EOF)
754 	{
755 		if (i == '\r')
756 		{
757 			i = getc(f);
758 			if (i != '\n')
759 			{
760 				if (i != EOF)
761 					(void) ungetc(i, f);
762 				i = '\r';
763 			}
764 		}
765 		if (--n <= 0)
766 		{
767 			/* allocate new space */
768 			char *nbp;
769 			int nn;
770 
771 			nn = (p - bp);
772 			if (nn < MEMCHUNKSIZE)
773 				nn *= 2;
774 			else
775 				nn += MEMCHUNKSIZE;
776 			nbp = xalloc(nn);
777 			bcopy(bp, nbp, p - bp);
778 			p = &nbp[p - bp];
779 			if (bp != buf)
780 				free(bp);
781 			bp = nbp;
782 			n = nn - (p - bp);
783 		}
784 		*p++ = i;
785 		if (i == '\n')
786 		{
787 			LineNumber++;
788 			i = getc(f);
789 			if (i != EOF)
790 				(void) ungetc(i, f);
791 			if (i != ' ' && i != '\t')
792 				break;
793 		}
794 	}
795 	if (p == bp)
796 		return (NULL);
797 	*--p = '\0';
798 	return (bp);
799 }
800 /*
801 **  CURTIME -- return current time.
802 **
803 **	Parameters:
804 **		none.
805 **
806 **	Returns:
807 **		the current time.
808 **
809 **	Side Effects:
810 **		none.
811 */
812 
813 time_t
814 curtime()
815 {
816 	auto time_t t;
817 
818 	(void) time(&t);
819 	return (t);
820 }
821 /*
822 **  ATOBOOL -- convert a string representation to boolean.
823 **
824 **	Defaults to "TRUE"
825 **
826 **	Parameters:
827 **		s -- string to convert.  Takes "tTyY" as true,
828 **			others as false.
829 **
830 **	Returns:
831 **		A boolean representation of the string.
832 **
833 **	Side Effects:
834 **		none.
835 */
836 
837 bool
838 atobool(s)
839 	register char *s;
840 {
841 	if (*s == '\0' || strchr("tTyY", *s) != NULL)
842 		return (TRUE);
843 	return (FALSE);
844 }
845 /*
846 **  ATOOCT -- convert a string representation to octal.
847 **
848 **	Parameters:
849 **		s -- string to convert.
850 **
851 **	Returns:
852 **		An integer representing the string interpreted as an
853 **		octal number.
854 **
855 **	Side Effects:
856 **		none.
857 */
858 
859 atooct(s)
860 	register char *s;
861 {
862 	register int i = 0;
863 
864 	while (*s >= '0' && *s <= '7')
865 		i = (i << 3) | (*s++ - '0');
866 	return (i);
867 }
868 /*
869 **  WAITFOR -- wait for a particular process id.
870 **
871 **	Parameters:
872 **		pid -- process id to wait for.
873 **
874 **	Returns:
875 **		status of pid.
876 **		-1 if pid never shows up.
877 **
878 **	Side Effects:
879 **		none.
880 */
881 
882 waitfor(pid)
883 	int pid;
884 {
885 	auto int st;
886 	int i;
887 
888 	do
889 	{
890 		errno = 0;
891 		i = wait(&st);
892 	} while ((i >= 0 || errno == EINTR) && i != pid);
893 	if (i < 0)
894 		st = -1;
895 	return (st);
896 }
897 /*
898 **  BITINTERSECT -- tell if two bitmaps intersect
899 **
900 **	Parameters:
901 **		a, b -- the bitmaps in question
902 **
903 **	Returns:
904 **		TRUE if they have a non-null intersection
905 **		FALSE otherwise
906 **
907 **	Side Effects:
908 **		none.
909 */
910 
911 bool
912 bitintersect(a, b)
913 	BITMAP a;
914 	BITMAP b;
915 {
916 	int i;
917 
918 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
919 		if ((a[i] & b[i]) != 0)
920 			return (TRUE);
921 	return (FALSE);
922 }
923 /*
924 **  BITZEROP -- tell if a bitmap is all zero
925 **
926 **	Parameters:
927 **		map -- the bit map to check
928 **
929 **	Returns:
930 **		TRUE if map is all zero.
931 **		FALSE if there are any bits set in map.
932 **
933 **	Side Effects:
934 **		none.
935 */
936 
937 bool
938 bitzerop(map)
939 	BITMAP map;
940 {
941 	int i;
942 
943 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
944 		if (map[i] != 0)
945 			return (FALSE);
946 	return (TRUE);
947 }
948 /*
949 **  STRCONTAINEDIN -- tell if one string is contained in another
950 **
951 **	Parameters:
952 **		a -- possible substring.
953 **		b -- possible superstring.
954 **
955 **	Returns:
956 **		TRUE if a is contained in b.
957 **		FALSE otherwise.
958 */
959 
960 bool
961 strcontainedin(a, b)
962 	register char *a;
963 	register char *b;
964 {
965 	int l;
966 
967 	l = strlen(a);
968 	for (;;)
969 	{
970 		b = strchr(b, a[0]);
971 		if (b == NULL)
972 			return FALSE;
973 		if (strncmp(a, b, l) == 0)
974 			return TRUE;
975 		b++;
976 	}
977 }
978