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