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