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