1/*	$NetBSD: getcap.c,v 1.58 2023/09/21 13:46:12 shm Exp $	*/
2
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#if HAVE_NBTOOL_CONFIG_H
36#include "nbtool_config.h"
37#endif
38
39#include <sys/cdefs.h>
40#if defined(LIBC_SCCS) && !defined(lint)
41#if 0
42static char sccsid[] = "@(#)getcap.c	8.3 (Berkeley) 3/25/94";
43#else
44__RCSID("$NetBSD: getcap.c,v 1.58 2023/09/21 13:46:12 shm Exp $");
45#endif
46#endif /* LIBC_SCCS and not lint */
47
48#ifndef LIBHACK
49#include "namespace.h"
50#endif
51#include <sys/types.h>
52#include <sys/param.h>
53
54#include <assert.h>
55#include <stddef.h>
56#include <ctype.h>
57#ifndef SMALL
58#include <db.h>
59#endif
60#include <errno.h>
61#include <fcntl.h>
62#include <limits.h>
63#include <stdio.h>
64#include <stdlib.h>
65#include <string.h>
66#include <unistd.h>
67
68#if defined(__weak_alias) && !defined(LIBHACK)
69__weak_alias(cgetcap,_cgetcap)
70__weak_alias(cgetclose,_cgetclose)
71__weak_alias(cgetent,_cgetent)
72__weak_alias(cgetfirst,_cgetfirst)
73__weak_alias(cgetmatch,_cgetmatch)
74__weak_alias(cgetnext,_cgetnext)
75__weak_alias(cgetnum,_cgetnum)
76__weak_alias(cgetset,_cgetset)
77__weak_alias(cgetstr,_cgetstr)
78__weak_alias(cgetustr,_cgetustr)
79__weak_alias(csetexpandtc,_csetexpandtc)
80#endif
81
82#define	BFRAG		1024
83#define	BSIZE		1024
84#define	ESC		('[' & 037)	/* ASCII ESC */
85#define	MAX_RECURSION	32		/* maximum getent recursion */
86#define	SFRAG		100		/* cgetstr mallocs in SFRAG chunks */
87
88#define RECOK	(char)0
89#define TCERR	(char)1
90#define	SHADOW	(char)2
91
92static size_t	 topreclen;	/* toprec length */
93static char	*toprec;	/* Additional record specified by cgetset() */
94static int	 gottoprec;	/* Flag indicating retrieval of toprecord */
95static int	 expandtc = 1;	/* flag to expand tc= or not */
96
97#ifndef SMALL
98static int	cdbget(DB *, char **, const char *);
99#endif
100static int 	getent(char **, size_t *, const char * const *, int,
101    const char *, int, char *);
102static int	nfcmp(char *, char *);
103
104/*
105 * Cgetset() allows the addition of a user specified buffer to be added
106 * to the database array, in effect "pushing" the buffer on top of the
107 * virtual database. 0 is returned on success, -1 on failure.
108 */
109int
110cgetset(const char *ent)
111{
112	const char *source, *check;
113	char *dest;
114
115	if (ent == NULL) {
116		if (toprec != NULL)
117			free(toprec);
118                toprec = NULL;
119                topreclen = 0;
120                return 0;
121        }
122        topreclen = strlen(ent);
123        if ((toprec = malloc(topreclen + 1)) == NULL) {
124		errno = ENOMEM;
125                return -1;
126	}
127	gottoprec = 0;
128
129	source = ent;
130	dest = toprec;
131	while (*source != '\0') { /* Strip whitespace */
132		*dest++ = *source++; /* Do not check first field */
133		while (*source == ':') {
134			check = source + 1;
135			while (*check && (isspace((unsigned char)*check) ||
136			    (*check=='\\' && isspace((unsigned char)check[1]))))
137				++check;
138			if (*check == ':')
139				source = check;
140			else
141				break;
142
143		}
144	}
145	*dest = 0;
146
147        return 0;
148}
149
150/*
151 * Cgetcap searches the capability record buf for the capability cap with
152 * type `type'.  A pointer to the value of cap is returned on success, NULL
153 * if the requested capability couldn't be found.
154 *
155 * Specifying a type of ':' means that nothing should follow cap (:cap:).
156 * In this case a pointer to the terminating ':' or NUL will be returned if
157 * cap is found.
158 *
159 * If (cap, '@') or (cap, terminator, '@') is found before (cap, terminator)
160 * return NULL.
161 */
162char *
163cgetcap(char *buf, const char *cap, int type)
164{
165	char *bp;
166	const char *cp;
167
168	_DIAGASSERT(buf != NULL);
169	_DIAGASSERT(cap != NULL);
170
171	bp = buf;
172	for (;;) {
173		/*
174		 * Skip past the current capability field - it's either the
175		 * name field if this is the first time through the loop, or
176		 * the remainder of a field whose name failed to match cap.
177		 */
178		for (;;)
179			if (*bp == '\0')
180				return NULL;
181			else if (*bp++ == ':')
182				break;
183
184		/*
185		 * Try to match (cap, type) in buf.
186		 */
187		for (cp = cap; *cp == *bp && *bp != '\0'; cp++, bp++)
188			continue;
189		if (*cp != '\0')
190			continue;
191		if (*bp == '@')
192			return NULL;
193		if (type == ':') {
194			if (*bp != '\0' && *bp != ':')
195				continue;
196			return bp;
197		}
198		if (*bp != type)
199			continue;
200		bp++;
201		return *bp == '@' ? NULL : bp;
202	}
203	/* NOTREACHED */
204}
205
206/*
207 * Cgetent extracts the capability record name from the NULL terminated file
208 * array db_array and returns a pointer to a malloc'd copy of it in buf.
209 * Buf must be retained through all subsequent calls to cgetcap, cgetnum,
210 * cgetflag, and cgetstr, but may then be free'd.  0 is returned on success,
211 * -1 if the requested record couldn't be found, -2 if a system error was
212 * encountered (couldn't open/read a file, etc.), and -3 if a potential
213 * reference loop is detected.
214 */
215/* coverity[+alloc : arg-*0] */
216int
217cgetent(char **buf, const char * const *db_array, const char *name)
218{
219	size_t dummy;
220
221	_DIAGASSERT(buf != NULL);
222	_DIAGASSERT(db_array != NULL);
223	_DIAGASSERT(name != NULL);
224
225	return getent(buf, &dummy, db_array, -1, name, 0, NULL);
226}
227
228void
229csetexpandtc(int etc)
230{
231	expandtc = etc;
232}
233
234/*
235 * Getent implements the functions of cgetent.  If fd is non-negative,
236 * *db_array has already been opened and fd is the open file descriptor.  We
237 * do this to save time and avoid using up file descriptors for tc=
238 * recursions.
239 *
240 * Getent returns the same success/failure codes as cgetent.  On success, a
241 * pointer to a malloc'ed capability record with all tc= capabilities fully
242 * expanded and its length (not including trailing ASCII NUL) are left in
243 * *cap and *len.
244 *
245 * Basic algorithm:
246 *	+ Allocate memory incrementally as needed in chunks of size BFRAG
247 *	  for capability buffer.
248 *	+ Recurse for each tc=name and interpolate result.  Stop when all
249 *	  names interpolated, a name can't be found, or depth exceeds
250 *	  MAX_RECURSION.
251 */
252/* coverity[+alloc : arg-*0] */
253static int
254getent(char **cap, size_t *len, const char * const *db_array, int fd,
255    const char *name, int depth, char *nfield)
256{
257	char *record, *newrecord;
258	char *r_end, *rp;	/* pacify gcc */
259	const char * const *db_p;
260	int myfd, eof, foundit;
261	int tc_not_resolved;
262
263	_DIAGASSERT(cap != NULL);
264	_DIAGASSERT(len != NULL);
265	_DIAGASSERT(db_array != NULL);
266	/* fd may be -1 */
267	_DIAGASSERT(name != NULL);
268	/* nfield may be NULL */
269
270	myfd = 0;
271	rp = NULL;
272
273	/*
274	 * Return with ``loop detected'' error if we've recursed more than
275	 * MAX_RECURSION times.
276	 */
277	if (depth > MAX_RECURSION)
278		return -3;
279
280	/*
281	 * Check if we have a top record from cgetset().
282         */
283	if (depth == 0 && toprec != NULL && cgetmatch(toprec, name) == 0) {
284		if ((record = malloc(topreclen + BFRAG)) == NULL) {
285			errno = ENOMEM;
286			return -2;
287		}
288		(void)strcpy(record, toprec);	/* XXX: strcpy is safe */
289		db_p = db_array;
290		rp = record + topreclen + 1;
291		r_end = rp + BFRAG;
292		goto tc_exp;
293	}
294	/*
295	 * Allocate first chunk of memory.
296	 */
297	if ((record = malloc(BFRAG)) == NULL) {
298		errno = ENOMEM;
299		return -2;
300	}
301	r_end = record + BFRAG;
302	foundit = 0;
303	/*
304	 * Loop through database array until finding the record.
305	 */
306
307	for (db_p = db_array; *db_p != NULL; db_p++) {
308		eof = 0;
309
310		/*
311		 * Open database if not already open.
312		 */
313
314		if (fd >= 0) {
315			(void)lseek(fd, (off_t)0, SEEK_SET);
316		} else {
317#ifndef SMALL
318			DB *capdbp;
319			char pbuf[MAXPATHLEN];
320			char *cbuf;
321			int retval;
322			size_t clen;
323
324			(void)snprintf(pbuf, sizeof(pbuf), "%s.db", *db_p);
325			if ((capdbp = dbopen(pbuf, O_RDONLY | O_CLOEXEC, 0,
326			    DB_HASH, 0)) != NULL) {
327				free(record);
328				retval = cdbget(capdbp, &record, name);
329				if (retval < 0) {
330					/* no record available */
331					(void)capdbp->close(capdbp);
332					return retval;
333				}
334				/* save the data; close frees it */
335				clen = strlen(record);
336				if ((cbuf = malloc(clen + 1)) == NULL) {
337					(void)capdbp->close(capdbp);
338					errno = ENOMEM;
339					return -2;
340				}
341				memmove(cbuf, record, clen + 1);
342				if (capdbp->close(capdbp) < 0) {
343					int serrno = errno;
344
345					free(cbuf);
346					errno = serrno;
347					return -2;
348				}
349				*len = clen;
350				*cap = cbuf;
351				return retval;
352			} else
353#endif
354			{
355				fd = open(*db_p, O_RDONLY | O_CLOEXEC, 0);
356				if (fd < 0) {
357					/* No error on unfound file. */
358					continue;
359				}
360				myfd = 1;
361			}
362		}
363		/*
364		 * Find the requested capability record ...
365		 */
366		{
367		char buf[BUFSIZ];
368		char *b_end, *bp, *cp;
369		int c, slash;
370
371		/*
372		 * Loop invariants:
373		 *	There is always room for one more character in record.
374		 *	R_end always points just past end of record.
375		 *	Rp always points just past last character in record.
376		 *	B_end always points just past last character in buf.
377		 *	Bp always points at next character in buf.
378		 *	Cp remembers where the last colon was.
379		 */
380		b_end = buf;
381		bp = buf;
382		cp = NULL;
383		slash = 0;
384		for (;;) {
385			/*
386			 * Read in a line implementing (\, newline)
387			 * line continuation.
388			 */
389			rp = record;
390			for (;;) {
391				if (bp >= b_end) {
392					ssize_t n;
393
394					n = read(fd, buf, sizeof(buf));
395					if (n <= 0) {
396						if (myfd)
397							(void)close(fd);
398						if (n < 0) {
399							int serrno = errno;
400
401							free(record);
402							errno = serrno;
403							return -2;
404						} else {
405							fd = -1;
406							eof = 1;
407							break;
408						}
409					}
410					b_end = buf+n;
411					bp = buf;
412				}
413
414				c = *bp++;
415				if (c == '\n') {
416					if (slash) {
417						slash = 0;
418						rp--;
419						continue;
420					} else
421						break;
422				}
423				if (slash) {
424					slash = 0;
425					cp = 0;
426				}
427				if (c == ':') {
428					/*
429					 * If the field was `empty' (i.e.
430					 * contained only white space), back up
431					 * to the colon (eliminating the
432					 * field).
433					 */
434					if (cp != NULL)
435						rp = cp;
436					else
437						cp = rp;
438				} else if (c == '\\') {
439					slash = 1;
440				} else if (c != ' ' && c != '\t') {
441					/*
442					 * Forget where the colon was, as this
443					 * is not an empty field.
444					 */
445					cp = 0;
446				}
447				*rp++ = c;
448
449				/*
450				 * Enforce loop invariant: if no room
451				 * left in record buffer, try to get
452				 * some more.
453				 */
454				if (rp >= r_end) {
455					ptrdiff_t pos;
456					size_t newsize;
457
458					pos = rp - record;
459					newsize = r_end - record + BFRAG;
460					newrecord = realloc(record, newsize);
461					if (newrecord == NULL) {
462						free(record);
463						if (myfd)
464							(void)close(fd);
465						errno = ENOMEM;
466						return -2;
467					}
468					record = newrecord;
469					r_end = record + newsize;
470					rp = record + pos;
471				}
472			}
473			/* Eliminate any white space after the last colon. */
474			if (cp)
475				rp = cp + 1;
476			/* Loop invariant lets us do this. */
477			*rp++ = '\0';
478
479			/*
480			 * If encountered eof check next file.
481			 */
482			if (eof)
483				break;
484
485			/*
486			 * Toss blank lines and comments.
487			 */
488			if (*record == '\0' || *record == '#')
489				continue;
490
491			/*
492			 * See if this is the record we want ...
493			 */
494			if (cgetmatch(record, name) == 0)
495				if (nfield == NULL || !nfcmp(nfield, record)) {
496					foundit = 1;
497					break;	/* found it! */
498				}
499		}
500		}
501		if (foundit)
502			break;
503	}
504
505	if (!foundit) {
506		free(record);
507		return -1;
508	}
509
510	/*
511	 * Got the capability record, but now we have to expand all tc=name
512	 * references in it ...
513	 */
514tc_exp:
515	tc_not_resolved = 0;
516	if (expandtc) {
517		char *newicap, *s;
518		size_t ilen, newilen;
519		int iret;
520		ptrdiff_t diff, tclen;
521		char *icap, *scan, *tc, *tcstart, *tcend;
522
523		/*
524		 * Loop invariants:
525		 *	There is room for one more character in record.
526		 *	R_end points just past end of record.
527		 *	Rp points just past last character in record.
528		 *	Scan points at remainder of record that needs to be
529		 *	scanned for tc=name constructs.
530		 */
531		scan = record;
532		for (;;) {
533			if ((tc = cgetcap(scan, "tc", '=')) == NULL)
534				break;
535
536			/*
537			 * Find end of tc=name and stomp on the trailing `:'
538			 * (if present) so we can use it to call ourselves.
539			 */
540			s = tc;
541			for (;;)
542				if (*s == '\0')
543					break;
544				else
545					if (*s++ == ':') {
546						*(s - 1) = '\0';
547						break;
548					}
549			tcstart = tc - 3;
550			tclen = s - tcstart;
551			tcend = s;
552
553			iret = getent(&icap, &ilen, db_p, fd, tc, depth+1,
554				      NULL);
555			newicap = icap;		/* Put into a register. */
556			newilen = ilen;
557			if (iret != 0) {
558				/* an error */
559				if (iret < -1) {
560					if (myfd)
561						(void)close(fd);
562					free(record);
563					return iret;
564				}
565				if (iret == 1)
566					tc_not_resolved = 1;
567				/* couldn't resolve tc */
568				if (iret == -1) {
569					*(s - 1) = ':';
570					scan = s - 1;
571					tc_not_resolved = 1;
572					continue;
573
574				}
575			}
576			/* not interested in name field of tc'ed record */
577			s = newicap;
578			for (;;)
579				if (*s == '\0')
580					break;
581				else if (*s++ == ':')
582					break;
583			newilen -= s - newicap;
584			newicap = s;
585
586			/* make sure interpolated record is `:'-terminated */
587			s += newilen;
588			if (*(s - 1) != ':') {
589				*s = ':';	/* overwrite NUL with : */
590				newilen++;
591			}
592
593			/*
594			 * Make sure there's enough room to insert the
595			 * new record.
596			 */
597			diff = newilen - tclen;
598			if (diff >= r_end - rp) {
599				ptrdiff_t pos, tcpos, tcposend;
600				size_t newsize;
601
602				pos = rp - record;
603				newsize = r_end - record + diff + BFRAG;
604				tcpos = tcstart - record;
605				tcposend = tcend - record;
606				newrecord = realloc(record, newsize);
607				if (newrecord == NULL) {
608					free(record);
609					if (myfd)
610						(void)close(fd);
611					free(icap);
612					errno = ENOMEM;
613					return -2;
614				}
615				record = newrecord;
616				r_end = record + newsize;
617				rp = record + pos;
618				tcstart = record + tcpos;
619				tcend = record + tcposend;
620			}
621
622			/*
623			 * Insert tc'ed record into our record.
624			 */
625			s = tcstart + newilen;
626			memmove(s, tcend,  (size_t)(rp - tcend));
627			memmove(tcstart, newicap, newilen);
628			rp += diff;
629			free(icap);
630
631			/*
632			 * Start scan on `:' so next cgetcap works properly
633			 * (cgetcap always skips first field).
634			 */
635			scan = s - 1;
636		}
637
638	}
639	/*
640	 * Close file (if we opened it), give back any extra memory, and
641	 * return capability, length and success.
642	 */
643	if (myfd)
644		(void)close(fd);
645	*len = rp - record - 1;	/* don't count NUL */
646	if (r_end > rp) {
647		if ((newrecord =
648		     realloc(record, (size_t)(rp - record))) == NULL) {
649			free(record);
650			errno = ENOMEM;
651			return -2;
652		}
653		record = newrecord;
654	}
655
656	*cap = record;
657	if (tc_not_resolved)
658		return 1;
659	return 0;
660}
661
662#ifndef SMALL
663static int
664cdbget(DB *capdbp, char **bp, const char *name)
665{
666	DBT key;
667	DBT data;
668
669	_DIAGASSERT(capdbp != NULL);
670	_DIAGASSERT(bp != NULL);
671	_DIAGASSERT(name != NULL);
672
673	key.data = __UNCONST(name);
674	key.size = strlen(name);
675
676	for (;;) {
677		/* Get the reference. */
678		switch(capdbp->get(capdbp, &key, &data, 0)) {
679		case -1:
680			return -2;
681		case 1:
682			return -1;
683		}
684
685		/* If not an index to another record, leave. */
686		if (((char *)data.data)[0] != SHADOW)
687			break;
688
689		key.data = (char *)data.data + 1;
690		key.size = data.size - 1;
691	}
692
693	*bp = (char *)data.data + 1;
694	return ((char *)(data.data))[0] == TCERR ? 1 : 0;
695}
696#endif
697
698/*
699 * Cgetmatch will return 0 if name is one of the names of the capability
700 * record buf, -1 if not.
701 */
702int
703cgetmatch(const char *buf, const char *name)
704{
705	const char *np, *bp;
706
707	_DIAGASSERT(buf != NULL);
708	_DIAGASSERT(name != NULL);
709
710	/*
711	 * Start search at beginning of record.
712	 */
713	bp = buf;
714	for (;;) {
715		/*
716		 * Try to match a record name.
717		 */
718		np = name;
719		for (;;)
720			if (*np == '\0') {
721				if (*bp == '|' || *bp == ':' || *bp == '\0')
722					return 0;
723				else
724					break;
725			} else if (*bp++ != *np++)
726				break;
727
728		/*
729		 * Match failed, skip to next name in record.
730		 */
731		if (bp > buf)
732			bp--;	/* a '|' or ':' may have stopped the match */
733		else
734			return -1;
735		for (;;)
736			if (*bp == '\0' || *bp == ':')
737				return -1;	/* match failed totally */
738			else if (*bp++ == '|')
739				break;		/* found next name */
740	}
741}
742
743int
744cgetfirst(char **buf, const char * const *db_array)
745{
746
747	_DIAGASSERT(buf != NULL);
748	_DIAGASSERT(db_array != NULL);
749
750	(void)cgetclose();
751	return cgetnext(buf, db_array);
752}
753
754static FILE *pfp;
755static int slash;
756static const char * const *dbp;
757
758int
759cgetclose(void)
760{
761	if (pfp != NULL) {
762		(void)fclose(pfp);
763		pfp = NULL;
764	}
765	dbp = NULL;
766	gottoprec = 0;
767	slash = 0;
768	return 0;
769}
770
771/*
772 * Cgetnext() gets either the first or next entry in the logical database
773 * specified by db_array.  It returns 0 upon completion of the database, 1
774 * upon returning an entry with more remaining, and -1 if an error occurs.
775 */
776/* coverity[+alloc : arg-*0] */
777int
778cgetnext(char **bp, const char * const *db_array)
779{
780	size_t len = 0;
781	int status, done;
782	char *cp, *line, *rp, *np, buf[BSIZE], nbuf[BSIZE];
783	size_t dummy;
784
785	_DIAGASSERT(bp != NULL);
786	_DIAGASSERT(db_array != NULL);
787
788	if (dbp == NULL)
789		dbp = db_array;
790
791	if (pfp == NULL && (pfp = fopen(*dbp, "re")) == NULL) {
792		(void)cgetclose();
793		return -1;
794	}
795	for (;;) {
796		if (toprec != NULL && !gottoprec) {
797			gottoprec = 1;
798			line = toprec;
799		} else {
800			line = fgetln(pfp, &len);
801			if (line == NULL) {
802				if (pfp == NULL)
803					return -1;
804				if (ferror(pfp)) {
805					(void)cgetclose();
806					return -1;
807				} else {
808					(void)fclose(pfp);
809					pfp = NULL;
810					if (*++dbp == NULL) {
811						(void)cgetclose();
812						return 0;
813					} else if ((pfp =
814					    fopen(*dbp, "re")) == NULL) {
815						(void)cgetclose();
816						return -1;
817					} else
818						continue;
819				}
820			} else
821				line[len - 1] = '\0';
822			if (len == 1) {
823				slash = 0;
824				continue;
825			}
826			if (isspace((unsigned char)*line) ||
827			    *line == ':' || *line == '#' || slash) {
828				if (line[len - 2] == '\\')
829					slash = 1;
830				else
831					slash = 0;
832				continue;
833			}
834			if (line[len - 2] == '\\')
835				slash = 1;
836			else
837				slash = 0;
838		}
839
840
841		/*
842		 * Line points to a name line.
843		 */
844		if (len > sizeof(nbuf))
845			return -1;
846		done = 0;
847		np = nbuf;
848		for (;;) {
849			for (cp = line; *cp != '\0'; cp++) {
850				if (*cp == ':') {
851					*np++ = ':';
852					done = 1;
853					break;
854				}
855				if (*cp == '\\')
856					break;
857				*np++ = *cp;
858			}
859			if (done) {
860				*np = '\0';
861				break;
862			} else { /* name field extends beyond the line */
863				line = fgetln(pfp, &len);
864				if (line == NULL && pfp) {
865					if (ferror(pfp)) {
866						(void)cgetclose();
867						return -1;
868					}
869					(void)fclose(pfp);
870					pfp = NULL;
871					*np = '\0';
872					break;
873				} else
874					line[len - 1] = '\0';
875			}
876		}
877		if (len > sizeof(buf))
878			return -1;
879		rp = buf;
880		for (cp = nbuf; *cp != '\0'; cp++)
881			if (*cp == '|' || *cp == ':')
882				break;
883			else
884				*rp++ = *cp;
885
886		*rp = '\0';
887		/*
888		 * XXX
889		 * Last argument of getent here should be nbuf if we want true
890		 * sequential access in the case of duplicates.
891		 * With NULL, getent will return the first entry found
892		 * rather than the duplicate entry record.  This is a
893		 * matter of semantics that should be resolved.
894		 */
895		status = getent(bp, &dummy, db_array, -1, buf, 0, NULL);
896		if (status == -2 || status == -3)
897			(void)cgetclose();
898
899		return status + 1;
900	}
901	/* NOTREACHED */
902}
903
904/*
905 * Cgetstr retrieves the value of the string capability cap from the
906 * capability record pointed to by buf.  A pointer to a decoded, NUL
907 * terminated, malloc'd copy of the string is returned in the char *
908 * pointed to by str.  The length of the string not including the trailing
909 * NUL is returned on success, -1 if the requested string capability
910 * couldn't be found, -2 if a system error was encountered (storage
911 * allocation failure).
912 */
913int
914cgetstr(char *buf, const char *cap, char **str)
915{
916	u_int m_room;
917	const char *bp;
918	char *mp;
919	ptrdiff_t len;
920	char *mem, *newmem;
921
922	_DIAGASSERT(buf != NULL);
923	_DIAGASSERT(cap != NULL);
924	_DIAGASSERT(str != NULL);
925
926	/*
927	 * Find string capability cap
928	 */
929	bp = cgetcap(buf, cap, '=');
930	if (bp == NULL)
931		return -1;
932
933	/*
934	 * Conversion / storage allocation loop ...  Allocate memory in
935	 * chunks SFRAG in size.
936	 */
937	if ((mem = malloc(SFRAG)) == NULL) {
938		errno = ENOMEM;
939		return -2;	/* couldn't even allocate the first fragment */
940	}
941	m_room = SFRAG;
942	mp = mem;
943
944	while (*bp != ':' && *bp != '\0') {
945		/*
946		 * Loop invariants:
947		 *	There is always room for one more character in mem.
948		 *	Mp always points just past last character in mem.
949		 *	Bp always points at next character in buf.
950		 */
951		if (*bp == '^') {
952			bp++;
953			if (*bp == ':' || *bp == '\0')
954				break;	/* drop unfinished escape */
955			*mp++ = *bp++ & 037;
956		} else if (*bp == '\\') {
957			bp++;
958			if (*bp == ':' || *bp == '\0')
959				break;	/* drop unfinished escape */
960			if ('0' <= *bp && *bp <= '7') {
961				int n, i;
962
963				n = 0;
964				i = 3;	/* maximum of three octal digits */
965				do {
966					n = n * 8 + (*bp++ - '0');
967				} while (--i && '0' <= *bp && *bp <= '7');
968				*mp++ = n;
969			}
970			else switch (*bp++) {
971				case 'b': case 'B':
972					*mp++ = '\b';
973					break;
974				case 't': case 'T':
975					*mp++ = '\t';
976					break;
977				case 'n': case 'N':
978					*mp++ = '\n';
979					break;
980				case 'f': case 'F':
981					*mp++ = '\f';
982					break;
983				case 'r': case 'R':
984					*mp++ = '\r';
985					break;
986				case 'e': case 'E':
987					*mp++ = ESC;
988					break;
989				case 'c': case 'C':
990					*mp++ = ':';
991					break;
992				default:
993					/*
994					 * Catches '\', '^', and
995					 *  everything else.
996					 */
997					*mp++ = *(bp-1);
998					break;
999			}
1000		} else
1001			*mp++ = *bp++;
1002		m_room--;
1003
1004		/*
1005		 * Enforce loop invariant: if no room left in current
1006		 * buffer, try to get some more.
1007		 */
1008		if (m_room == 0) {
1009			size_t size = mp - mem;
1010
1011			if ((newmem = realloc(mem, size + SFRAG)) == NULL) {
1012				free(mem);
1013				return -2;
1014			}
1015			mem = newmem;
1016			m_room = SFRAG;
1017			mp = mem + size;
1018		}
1019	}
1020	*mp++ = '\0';	/* loop invariant let's us do this */
1021	m_room--;
1022	len = mp - mem - 1;
1023
1024	/*
1025	 * Give back any extra memory and return value and success.
1026	 */
1027	if (m_room != 0) {
1028		if ((newmem = realloc(mem, (size_t)(mp - mem))) == NULL) {
1029			free(mem);
1030			return -2;
1031		}
1032		mem = newmem;
1033	}
1034	*str = mem;
1035	_DIAGASSERT(__type_fit(int, len));
1036	return (int)len;
1037}
1038
1039/*
1040 * Cgetustr retrieves the value of the string capability cap from the
1041 * capability record pointed to by buf.  The difference between cgetustr()
1042 * and cgetstr() is that cgetustr does not decode escapes but rather treats
1043 * all characters literally.  A pointer to a  NUL terminated malloc'd
1044 * copy of the string is returned in the char pointed to by str.  The
1045 * length of the string not including the trailing NUL is returned on success,
1046 * -1 if the requested string capability couldn't be found, -2 if a system
1047 * error was encountered (storage allocation failure).
1048 */
1049int
1050cgetustr(char *buf, const char *cap, char **str)
1051{
1052	u_int m_room;
1053	const char *bp;
1054	char *mp;
1055	size_t len;
1056	char *mem, *newmem;
1057
1058	_DIAGASSERT(buf != NULL);
1059	_DIAGASSERT(cap != NULL);
1060	_DIAGASSERT(str != NULL);
1061
1062	/*
1063	 * Find string capability cap
1064	 */
1065	if ((bp = cgetcap(buf, cap, '=')) == NULL)
1066		return -1;
1067
1068	/*
1069	 * Conversion / storage allocation loop ...  Allocate memory in
1070	 * chunks SFRAG in size.
1071	 */
1072	if ((mem = malloc(SFRAG)) == NULL) {
1073		errno = ENOMEM;
1074		return -2;	/* couldn't even allocate the first fragment */
1075	}
1076	m_room = SFRAG;
1077	mp = mem;
1078
1079	while (*bp != ':' && *bp != '\0') {
1080		/*
1081		 * Loop invariants:
1082		 *	There is always room for one more character in mem.
1083		 *	Mp always points just past last character in mem.
1084		 *	Bp always points at next character in buf.
1085		 */
1086		*mp++ = *bp++;
1087		m_room--;
1088
1089		/*
1090		 * Enforce loop invariant: if no room left in current
1091		 * buffer, try to get some more.
1092		 */
1093		if (m_room == 0) {
1094			size_t size = mp - mem;
1095
1096			if ((newmem = realloc(mem, size + SFRAG)) == NULL) {
1097				free(mem);
1098				return -2;
1099			}
1100			mem = newmem;
1101			m_room = SFRAG;
1102			mp = mem + size;
1103		}
1104	}
1105	*mp++ = '\0';	/* loop invariant let's us do this */
1106	m_room--;
1107	len = mp - mem - 1;
1108
1109	/*
1110	 * Give back any extra memory and return value and success.
1111	 */
1112	if (m_room != 0) {
1113		if ((newmem = realloc(mem, (size_t)(mp - mem))) == NULL) {
1114			free(mem);
1115			return -2;
1116		}
1117		mem = newmem;
1118	}
1119	*str = mem;
1120	_DIAGASSERT(__type_fit(int, len));
1121	return (int)len;
1122}
1123
1124/*
1125 * Cgetnum retrieves the value of the numeric capability cap from the
1126 * capability record pointed to by buf.  The numeric value is returned in
1127 * the long pointed to by num.  0 is returned on success, -1 if the requested
1128 * numeric capability couldn't be found.
1129 */
1130int
1131cgetnum(char *buf, const char *cap, long *num)
1132{
1133	long n;
1134	int base, digit;
1135	const char *bp;
1136
1137	_DIAGASSERT(buf != NULL);
1138	_DIAGASSERT(cap != NULL);
1139	_DIAGASSERT(num != NULL);
1140
1141	/*
1142	 * Find numeric capability cap
1143	 */
1144	bp = cgetcap(buf, cap, '#');
1145	if (bp == NULL)
1146		return -1;
1147
1148	/*
1149	 * Look at value and determine numeric base:
1150	 *	0x... or 0X...	hexadecimal,
1151	 * else	0...		octal,
1152	 * else			decimal.
1153	 */
1154	if (*bp == '0') {
1155		bp++;
1156		if (*bp == 'x' || *bp == 'X') {
1157			bp++;
1158			base = 16;
1159		} else
1160			base = 8;
1161	} else
1162		base = 10;
1163
1164	/*
1165	 * Conversion loop ...
1166	 */
1167	n = 0;
1168	for (;;) {
1169		if ('0' <= *bp && *bp <= '9')
1170			digit = *bp - '0';
1171		else if ('a' <= *bp && *bp <= 'f')
1172			digit = 10 + *bp - 'a';
1173		else if ('A' <= *bp && *bp <= 'F')
1174			digit = 10 + *bp - 'A';
1175		else
1176			break;
1177
1178		if (digit >= base)
1179			break;
1180
1181		n = n * base + digit;
1182		bp++;
1183	}
1184
1185	/*
1186	 * Return value and success.
1187	 */
1188	*num = n;
1189	return 0;
1190}
1191
1192
1193/*
1194 * Compare name field of record.
1195 */
1196static int
1197nfcmp(char *nf, char *rec)
1198{
1199	char *cp, tmp;
1200	int ret;
1201
1202	_DIAGASSERT(nf != NULL);
1203	_DIAGASSERT(rec != NULL);
1204
1205	for (cp = rec; *cp != ':'; cp++)
1206		continue;
1207
1208	tmp = *(cp + 1);
1209	*(cp + 1) = '\0';
1210	ret = strcmp(nf, rec);
1211	*(cp + 1) = tmp;
1212
1213	return ret;
1214}
1215