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