xref: /csrg-svn/lib/libc/gen/getcap.c (revision 55875)
1 /*-
2  * Copyright (c) 1992 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Casey Leedom of Lawrence Livermore National Laboratory.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #if defined(LIBC_SCCS) && !defined(lint)
38 static char sccsid[] = "@(#)getcap.c	5.1 (Berkeley) 8/6/92";
39 #endif /* LIBC_SCCS and not lint */
40 
41 #include <sys/types.h>
42 
43 #include <ctype.h>
44 #include <errno.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #define	BFRAG		1024
53 #define	BSIZE		80
54 #define	ESC		('[' & 037)	/* ASCII ESC */
55 #define	MAX_RECURSION	32		/* maximum getent recursion */
56 #define	SFRAG		100		/* cgetstr mallocs in SFRAG chunks */
57 
58 static size_t	 topreclen;	/* toprec length */
59 static char	*toprec;	/* Additional record specified by cgetset() */
60 static int	 gottoprec;	/* Flag indicating retrieval of toprecord */
61 
62 static int getent __P((char **, u_int *, char **, int, char *, int));
63 
64 /*
65  * Cgetset() allows the addition of a user specified buffer to be added
66  * to the database array, in effect "pushing" the buffer on top of the
67  * virtual database. 0 is returned on success, -1 on failure.
68  */
69 int
70 cgetset(ent)
71 	char *ent;
72 {
73 	if (ent == NULL) {
74 		if (toprec)
75 			free(toprec);
76                 toprec = NULL;
77                 topreclen = 0;
78                 return (0);
79         }
80         topreclen = strlen(ent);
81         if ((toprec = malloc (topreclen + 1)) == NULL) {
82 		errno = ENOMEM;
83                 return (-1);
84 	}
85         (void)strcpy(toprec, ent);
86         return (0);
87 }
88 
89 /*
90  * Cgetcap searches the capability record buf for the capability cap with
91  * type `type'.  A pointer to the value of cap is returned on success, NULL
92  * if the requested capability couldn't be found.
93  *
94  * Specifying a type of ':' means that nothing should follow cap (:cap:).
95  * In this case a pointer to the terminating ':' or NUL will be returned if
96  * cap is found.
97  *
98  * If (cap, '@') or (cap, terminator, '@') is found before (cap, terminator)
99  * return NULL.
100  */
101 char *
102 cgetcap(buf, cap, type)
103 	char *buf, *cap;
104 	int type;
105 {
106 	register char *bp, *cp;
107 
108 	bp = buf;
109 	for (;;) {
110 		/*
111 		 * Skip past the current capability field - it's either the
112 		 * name field if this is the first time through the loop, or
113 		 * the remainder of a field whose name failed to match cap.
114 		 */
115 		for (;;)
116 			if (*bp == '\0')
117 				return (NULL);
118 			else
119 				if (*bp++ == ':')
120 					break;
121 
122 		/*
123 		 * Try to match (cap, type) in buf.
124 		 */
125 		for (cp = cap; *cp == *bp && *bp != '\0'; cp++, bp++)
126 			continue;
127 		if (*cp != '\0')
128 			continue;
129 		if (*bp == '@')
130 			return (NULL);
131 		if (type == ':') {
132 			if (*bp != '\0' && *bp != ':')
133 				continue;
134 			return(bp);
135 		}
136 		if (*bp != type)
137 			continue;
138 		bp++;
139 		return (*bp == '@' ? NULL : bp);
140 	}
141 	/* NOTREACHED */
142 }
143 
144 /*
145  * Cgetent extracts the capability record name from the NULL terminated file
146  * array db_array and returns a pointer to a malloc'd copy of it in buf.
147  * Buf must be retained through all subsequent calls to cgetcap, cgetnum,
148  * cgetflag, and cgetstr, but may then be free'd.  0 is returned on success,
149  * -1 if the requested record couldn't be found, -2 if a system error was
150  * encountered (couldn't open/read a file, etc.), and -3 if a potential
151  * reference loop is detected.
152  */
153 int
154 cgetent(buf, db_array, name)
155 	char **buf, **db_array, *name;
156 {
157 	u_int dummy;
158 
159 	return (getent(buf, &dummy, db_array, -1, name, 0));
160 }
161 
162 /*
163  * Getent implements the functions of cgetent.  If fd is non-negative,
164  * *db_array has already been opened and fd is the open file descriptor.  We
165  * do this to save time and avoid using up file descriptors for tc=
166  * recursions.
167  *
168  * Getent returns the same success/failure codes as cgetent.  On success, a
169  * pointer to a malloc'ed capability record with all tc= capabilities fully
170  * expanded and its length (not including trailing ASCII NUL) are left in
171  * *cap and *len.
172  *
173  * Basic algorithm:
174  *	+ Allocate memory incrementally as needed in chunks of size BFRAG
175  *	  for capability buffer.
176  *	+ Recurse for each tc=name and interpolate result.  Stop when all
177  *	  names interpolated, a name can't be found, or depth exceeds
178  *	  MAX_RECURSION.
179  */
180 static int
181 getent(cap, len, db_array, fd, name, depth)
182 	char **cap, **db_array, *name;
183 	u_int *len;
184 	int fd, depth;
185 {
186 	register char *r_end, *rp, **db_p;
187 	int myfd, eof, foundit;
188 	char *record;
189 
190 	/*
191 	 * Return with ``loop detected'' error if we've recursed more than
192 	 * MAX_RECURSION times.
193 	 */
194 	if (depth > MAX_RECURSION)
195 		return (-3);
196 
197 	/*
198 	 * Check if we have a top record from cgetset().
199          */
200 	if (depth == 0 && toprec != NULL && !gottoprec) {
201 		if ((record = malloc (topreclen + BFRAG)) == NULL) {
202 			errno = ENOMEM;
203 			return (-2);
204 		}
205 		(void)strcpy(record, toprec);
206 		myfd = 0;
207 		db_p = db_array;
208 		rp = record + topreclen + 1;
209 		r_end = rp + BFRAG;
210 		goto tc_exp;
211 	}
212 	/*
213 	 * Allocate first chunk of memory.
214 	 */
215 	if ((record = malloc(BFRAG)) == NULL) {
216 		errno = ENOMEM;
217 		return (-2);
218 	}
219 	r_end = record + BFRAG;
220 	foundit = 0;
221 
222 	/*
223 	 * Loop through database array until finding the record.
224 	 */
225 
226 	for (db_p = db_array; *db_p != NULL; db_p++) {
227 		eof = 0;
228 
229 		/*
230 		 * Open database if not already open.
231 		 */
232 		if (fd >= 0) {
233 			(void)lseek(fd, 0L, L_SET);
234 			myfd = 0;
235 		} else {
236 			fd = open(*db_p, O_RDONLY, 0);
237 			if (fd < 0) {
238 				free(record);
239 				return (-2);
240 			}
241 			myfd = 1;
242 		}
243 
244 		/*
245 		 * Find the requested capability record ...
246 		 */
247 		{
248 		char buf[BUFSIZ];
249 		register char *b_end, *bp;
250 		register int c;
251 
252 		/*
253 		 * Loop invariants:
254 		 *	There is always room for one more character in record.
255 		 *	R_end always points just past end of record.
256 		 *	Rp always points just past last character in record.
257 		 *	B_end always points just past last character in buf.
258 		 *	Bp always points at next character in buf.
259 		 */
260 		b_end = buf;
261 		bp = buf;
262 		for (;;) {
263 
264 			/*
265 			 * Read in a line implementing (\, newline)
266 			 * line continuation.
267 			 */
268 			rp = record;
269 			for (;;) {
270 				if (bp >= b_end) {
271 					int n;
272 
273 					n = read(fd, buf, sizeof(buf));
274 					if (n <= 0) {
275 						if (myfd)
276 							(void)close(fd);
277 						if (n < 0) {
278 							free(record);
279 							return (-2);
280 						} else {
281 							fd = -1;
282 							eof = 1;
283 							break;
284 						}
285 					}
286 					b_end = buf+n;
287 					bp = buf;
288 				}
289 
290 				c = *bp++;
291 				if (c == '\n') {
292 					if (rp > record && *(rp-1) == '\\') {
293 						rp--;
294 						continue;
295 					} else
296 						break;
297 				}
298 				*rp++ = c;
299 
300 				/*
301 				 * Enforce loop invariant: if no room
302 				 * left in record buffer, try to get
303 				 * some more.
304 				 */
305 				if (rp >= r_end) {
306 					u_int pos;
307 					size_t newsize;
308 
309 					pos = rp - record;
310 					newsize = r_end - record + BFRAG;
311 					record = realloc(record, newsize);
312 					if (record == NULL) {
313 						errno = ENOMEM;
314 						if (myfd)
315 							(void)close(fd);
316 						return (-2);
317 					}
318 					r_end = record + newsize;
319 					rp = record + pos;
320 				}
321 			}
322 				/* loop invariant let's us do this */
323 			*rp++ = '\0';
324 
325 			/*
326 			 * If encountered eof check next file.
327 			 */
328 			if (eof)
329 				break;
330 
331 			/*
332 			 * Toss blank lines and comments.
333 			 */
334 			if (*record == '\0' || *record == '#')
335 				continue;
336 
337 			/*
338 			 * See if this is the record we want ...
339 			 */
340 			if (cgetmatch(record, name) == 0) {
341 				foundit = 1;
342 				break;	/* found it! */
343 			}
344 		}
345 		}
346 		if (foundit)
347 			break;
348 	}
349 
350 	if (!foundit)
351 		return (-1);
352 
353 	/*
354 	 * Got the capability record, but now we have to expand all tc=name
355 	 * references in it ...
356 	 */
357 tc_exp:	{
358 		register char *newicap, *s;
359 		register int newilen;
360 		u_int ilen;
361 		int diff, iret, tclen;
362 		char *icap, *scan, *tc, *tcstart, *tcend;
363 
364 		/*
365 		 * Loop invariants:
366 		 *	There is room for one more character in record.
367 		 *	R_end points just past end of record.
368 		 *	Rp points just past last character in record.
369 		 *	Scan points at remainder of record that needs to be
370 		 *	scanned for tc=name constructs.
371 		 */
372 		scan = record;
373 		for (;;) {
374 			if ((tc = cgetcap(scan, "tc", '=')) == NULL)
375 				break;
376 
377 			/*
378 			 * Find end of tc=name and stomp on the trailing `:'
379 			 * (if present) so we can use it to call ourselves.
380 			 */
381 			s = tc;
382 			for (;;)
383 				if (*s == '\0')
384 					break;
385 				else
386 					if (*s++ == ':') {
387 						*(s-1) = '\0';
388 						break;
389 					}
390 			tcstart = tc - 3;
391 			tclen = s - tcstart;
392 			tcend = s;
393 
394 			iret = getent(&icap, &ilen, db_p, fd, tc, depth+1);
395 			newicap = icap;		/* Put into a register. */
396 			newilen = ilen;
397 			if (iret != 0) {
398 				/* an error or couldn't resolve tc= */
399 				if (myfd)
400 					(void)close(fd);
401 				free(record);
402 				return (iret);
403 			}
404 
405 			/* not interested in name field of tc'ed record */
406 			s = newicap;
407 			for (;;)
408 				if (*s == '\0')
409 					break;
410 				else
411 					if (*s++ == ':')
412 						break;
413 			newilen -= s - newicap;
414 			newicap = s;
415 
416 			/* make sure interpolated record is `:'-terminated */
417 			s += newilen;
418 			if (*(s-1) != ':') {
419 				*s = ':';	/* overwrite NUL with : */
420 				newilen++;
421 			}
422 
423 			/*
424 			 * Make sure there's enough room to insert the
425 			 * new record.
426 			 */
427 			diff = newilen - tclen;
428 			if (diff >= r_end - rp) {
429 				u_int pos, tcpos, tcposend;
430 				size_t newsize;
431 
432 				pos = rp - record;
433 				newsize = r_end - record + diff + BFRAG;
434 				tcpos = tcstart - record;
435 				tcposend = tcend - record;
436 				record = realloc(record, newsize);
437 				if (record == NULL) {
438 					errno = ENOMEM;
439 					if (myfd)
440 						(void)close(fd);
441 					free(icap);
442 					return (-2);
443 				}
444 				r_end = record + newsize;
445 				rp = record + pos;
446 				tcstart = record + tcpos;
447 				tcend = record + tcposend;
448 			}
449 
450 			/*
451 			 * Insert tc'ed record into our record.
452 			 */
453 			s = tcstart + newilen;
454 			bcopy(tcend, s, rp - tcend);
455 			bcopy(newicap, tcstart, newilen);
456 			rp += diff;
457 			free(icap);
458 
459 			/*
460 			 * Start scan on `:' so next cgetcap works properly
461 			 * (cgetcap always skips first field).
462 			 */
463 			scan = s-1;
464 		}
465 	}
466 
467 	/*
468 	 * Close file (if we opened it), give back any extra memory, and
469 	 * return capability, length and success.
470 	 */
471 	if (myfd)
472 		(void)close(fd);
473 	*len = rp - record - 1;	/* don't count NUL */
474 	if (r_end > rp)
475 		record = realloc(record, (size_t)(rp - record));
476 	*cap = record;
477 	return (0);
478 }
479 
480 /*
481  * Cgetmatch will return 0 if name is one of the names of the capability
482  * record buf, -1 if not.
483  */
484 int
485 cgetmatch(buf, name)
486 	char *buf, *name;
487 {
488 	register char *np, *bp;
489 
490 	/*
491 	 * Start search at beginning of record.
492 	 */
493 	bp = buf;
494 	for (;;) {
495 		/*
496 		 * Try to match a record name.
497 		 */
498 		np = name;
499 		for (;;)
500 			if (*np == '\0')
501 				if (*bp == '|' || *bp == ':' || *bp == '\0')
502 					return (0);
503 				else
504 					break;
505 			else
506 				if (*bp++ != *np++)
507 					break;
508 
509 		/*
510 		 * Match failed, skip to next name in record.
511 		 */
512 		bp--;	/* a '|' or ':' may have stopped the match */
513 		for (;;)
514 			if (*bp == '\0' || *bp == ':')
515 				return (-1);	/* match failed totally */
516 			else
517 				if (*bp++ == '|')
518 					break;	/* found next name */
519 	}
520 }
521 
522 int
523 cgetfirst(buf, db_array)
524 	char **buf, **db_array;
525 {
526 	if (toprec) {
527 		if ((*buf = malloc(topreclen + 1)) == NULL) {
528 			errno = ENOMEM;
529 			return(-2);
530 		}
531 		strcpy(*buf, toprec);
532 		(void)cgetclose();
533 		gottoprec = 1;
534 		return(1);
535 	}
536 	(void)cgetclose();
537 	return (cgetnext(buf, db_array));
538 }
539 
540 static FILE *pfp;
541 static int slash;
542 static char **dbp;
543 
544 int
545 cgetclose()
546 {
547 	if (pfp != NULL) {
548 		(void)fclose(pfp);
549 		pfp = NULL;
550 	}
551 	dbp = NULL;
552 	gottoprec = 0;
553 	slash = 0;
554 	return(0);
555 }
556 
557 /*
558  * Cgetnext() gets either the first or next entry in the logical database
559  * specified by db_array.  It returns 0 upon completion of the database, 1
560  * upon returning an entry with more remaining, and -1 if an error occurs.
561  */
562 int
563 cgetnext(bp, db_array)
564         register char **bp;
565 	char **db_array;
566 {
567 	size_t len;
568 	int status;
569 	char *cp, *line, *rp, buf[BSIZE];
570 
571 	if (dbp == NULL) {
572 		if (toprec && !gottoprec) {
573 			if ((*bp = malloc(topreclen + 1)) == NULL) {
574 				errno = ENOMEM;
575 				return(-2);
576 			}
577 			strcpy(*bp, toprec);
578 			gottoprec = 1;
579 			return(1);
580 		}
581 		dbp = db_array;
582 	}
583 	if (pfp == NULL && (pfp = fopen(*dbp, "r")) == NULL) {
584 		(void)cgetclose();
585 		return (-1);
586 	}
587 	for(;;) {
588 		line = fgetline(pfp, &len);
589 		if (line == NULL) {
590 			(void)fclose(pfp);
591 			if (ferror(pfp)) {
592 				(void)cgetclose();
593 				return (-1);
594 			} else {
595 				dbp++;
596 				if (*dbp == NULL) {
597 					(void)cgetclose();
598 					return (0);
599 				} else if ((pfp = fopen(*dbp, "r")) == NULL) {
600 					(void)cgetclose();
601 					return (-1);
602 				} else
603 					continue;
604 			}
605 		}
606 		if (isspace(*line) || *line == ':' || *line == '#'
607 		    || len == 0 || slash) {
608 			if (len > 0 && line[len - 1] == '\\')
609 				slash = 1;
610 			else
611 				slash = 0;
612 			continue;
613 		}
614 		if (len > 0 && line[len - 1] == '\\')
615 			slash = 1;
616 		else
617 			slash = 0;
618 
619 		/* line points to a name line */
620 
621 		rp = buf;
622 		for(cp = line; *cp != NULL; cp++)
623 			if (*cp == '|' || *cp == ':')
624 				break;
625 			else
626 				*rp++ = *cp;
627 
628 		*rp = '\0';
629 		status = cgetent(bp, db_array, &buf[0]);
630 		if (status == 0)
631 			return (1);
632 		if (status == -2 || status == -3) {
633 			(void)cgetclose();
634 			return (status + 1);
635 		}
636 	}
637 	/* NOTREACHED */
638 }
639 
640 /*
641  * Cgetstr retrieves the value of the string capability cap from the
642  * capability record pointed to by buf.  A pointer to a decoded, NUL
643  * terminated, malloc'd copy of the string is returned in the char *
644  * pointed to by str.  The length of the string not including the trailing
645  * NUL is returned on success, -1 if the requested string capability
646  * couldn't be found, -2 if a system error was encountered (storage
647  * allocation failure).
648  */
649 int
650 cgetstr(buf, cap, str)
651 	char *buf, *cap;
652 	char **str;
653 {
654 	register u_int m_room;
655 	register char *bp, *mp;
656 	int len;
657 	char *mem;
658 
659 	/*
660 	 * Find string capability cap
661 	 */
662 	bp = cgetcap(buf, cap, '=');
663 	if (bp == NULL)
664 		return (-1);
665 
666 	/*
667 	 * Conversion / storage allocation loop ...  Allocate memory in
668 	 * chunks SFRAG in size.
669 	 */
670 	if ((mem = malloc(SFRAG)) == NULL) {
671 		errno = ENOMEM;
672 		return (-2);	/* couldn't even allocate the first fragment */
673 	}
674 	m_room = SFRAG;
675 	mp = mem;
676 
677 	while (*bp != ':' && *bp != '\0') {
678 		/*
679 		 * Loop invariants:
680 		 *	There is always room for one more character in mem.
681 		 *	Mp always points just past last character in mem.
682 		 *	Bp always points at next character in buf.
683 		 */
684 		if (*bp == '^') {
685 			bp++;
686 			if (*bp == ':' || *bp == '\0')
687 				break;	/* drop unfinished escape */
688 			*mp++ = *bp++ & 037;
689 		} else if (*bp == '\\') {
690 			bp++;
691 			if (*bp == ':' || *bp == '\0')
692 				break;	/* drop unfinished escape */
693 			if ('0' <= *bp && *bp <= '7') {
694 				register int n, i;
695 
696 				n = 0;
697 				i = 3;	/* maximum of three octal digits */
698 				do {
699 					n = n * 8 + (*bp++ - '0');
700 				} while (--i && '0' <= *bp && *bp <= '7');
701 				*mp++ = n;
702 			}
703 			else switch (*bp++) {
704 				case 'b': case 'B':
705 					*mp++ = '\b';
706 					break;
707 				case 't': case 'T':
708 					*mp++ = '\t';
709 					break;
710 				case 'n': case 'N':
711 					*mp++ = '\n';
712 					break;
713 				case 'f': case 'F':
714 					*mp++ = '\f';
715 					break;
716 				case 'r': case 'R':
717 					*mp++ = '\r';
718 					break;
719 				case 'e': case 'E':
720 					*mp++ = ESC;
721 					break;
722 				case 'c': case 'C':
723 					*mp++ = ':';
724 					break;
725 				default:
726 					/*
727 					 * Catches '\', '^', and
728 					 *  everything else.
729 					 */
730 					*mp++ = *(bp-1);
731 					break;
732 			}
733 		} else
734 			*mp++ = *bp++;
735 		m_room--;
736 
737 		/*
738 		 * Enforce loop invariant: if no room left in current
739 		 * buffer, try to get some more.
740 		 */
741 		if (m_room == 0) {
742 			size_t size = mp - mem;
743 
744 			if ((mem = realloc(mem, size + SFRAG)) == NULL)
745 				return (-2);
746 			m_room = SFRAG;
747 			mp = mem + size;
748 		}
749 	}
750 	*mp++ = '\0';	/* loop invariant let's us do this */
751 	m_room--;
752 	len = mp - mem - 1;
753 
754 	/*
755 	 * Give back any extra memory and return value and success.
756 	 */
757 	if (m_room != 0)
758 		mem = realloc(mem, (size_t)(mp - mem));
759 	*str = mem;
760 	return (len);
761 }
762 
763 /*
764  * Cgetustr retrieves the value of the string capability cap from the
765  * capability record pointed to by buf.  The difference between cgetustr()
766  * and cgetstr() is that cgetustr does not decode escapes but rather treats
767  * all characters literally.  A pointer to a  NUL terminated malloc'd
768  * copy of the string is returned in the char pointed to by str.  The
769  * length of the string not including the trailing NUL is returned on success,
770  * -1 if the requested string capability couldn't be found, -2 if a system
771  * error was encountered (storage allocation failure).
772  */
773 int
774 cgetustr(buf, cap, str)
775 	char *buf, *cap, **str;
776 {
777 	register u_int m_room;
778 	register char *bp, *mp;
779 	int len;
780 	char *mem;
781 
782 	/*
783 	 * Find string capability cap
784 	 */
785 	if ((bp = cgetcap(buf, cap, '=')) == NULL)
786 		return (-1);
787 
788 	/*
789 	 * Conversion / storage allocation loop ...  Allocate memory in
790 	 * chunks SFRAG in size.
791 	 */
792 	if ((mem = malloc(SFRAG)) == NULL) {
793 		errno = ENOMEM;
794 		return (-2);	/* couldn't even allocate the first fragment */
795 	}
796 	m_room = SFRAG;
797 	mp = mem;
798 
799 	while (*bp != ':' && *bp != '\0') {
800 		/*
801 		 * Loop invariants:
802 		 *	There is always room for one more character in mem.
803 		 *	Mp always points just past last character in mem.
804 		 *	Bp always points at next character in buf.
805 		 */
806 		*mp++ = *bp++;
807 		m_room--;
808 
809 		/*
810 		 * Enforce loop invariant: if no room left in current
811 		 * buffer, try to get some more.
812 		 */
813 		if (m_room == 0) {
814 			size_t size = mp - mem;
815 
816 			if ((mem = realloc(mem, size + SFRAG)) == NULL)
817 				return (-2);
818 			m_room = SFRAG;
819 			mp = mem + size;
820 		}
821 	}
822 	*mp++ = '\0';	/* loop invariant let's us do this */
823 	m_room--;
824 	len = mp - mem - 1;
825 
826 	/*
827 	 * Give back any extra memory and return value and success.
828 	 */
829 	if (m_room != 0)
830 		mem = realloc(mem, (size_t)(mp - mem));
831 	*str = mem;
832 	return (len);
833 }
834 
835 /*
836  * Cgetnum retrieves the value of the numeric capability cap from the
837  * capability record pointed to by buf.  The numeric value is returned in
838  * the long pointed to by num.  0 is returned on success, -1 if the requested
839  * numeric capability couldn't be found.
840  */
841 int
842 cgetnum(buf, cap, num)
843 	char *buf, *cap;
844 	long *num;
845 {
846 	register long n;
847 	register int base, digit;
848 	register char *bp;
849 
850 	/*
851 	 * Find numeric capability cap
852 	 */
853 	bp = cgetcap(buf, cap, '#');
854 	if (bp == NULL)
855 		return (-1);
856 
857 	/*
858 	 * Look at value and determine numeric base:
859 	 *	0x... or 0X...	hexadecimal,
860 	 * else	0...		octal,
861 	 * else			decimal.
862 	 */
863 	if (*bp == '0') {
864 		bp++;
865 		if (*bp == 'x' || *bp == 'X') {
866 			bp++;
867 			base = 16;
868 		} else
869 			base = 8;
870 	} else
871 		base = 10;
872 
873 	/*
874 	 * Conversion loop ...
875 	 */
876 	n = 0;
877 	for (;;) {
878 		if ('0' <= *bp && *bp <= '9')
879 			digit = *bp - '0';
880 		else
881 			if (   ('a' <= *bp && *bp <= 'f')
882 			    || ('A' <= *bp && *bp <= 'F'))
883 				digit = 10 + *bp - 'a';
884 			else
885 				break;
886 
887 		if (digit >= base)
888 			break;
889 
890 		n = n * base + digit;
891 		bp++;
892 	}
893 
894 	/*
895 	 * Return value and success.
896 	 */
897 	*num = n;
898 	return (0);
899 }
900