1 /** $MirOS: src/usr.bin/cap_mkdb/getinfo.c,v 1.5 2006/11/01 00:49:45 tg Exp $ */
2 /* $OpenBSD: getinfo.c,v 1.10 2006/03/18 03:55:09 ray Exp $ */
3
4 /*-
5 * Copyright (c) 1992, 1993
6 * The Regents of the University of California.
7 * Copyright (c) 1996 SigmaSoft, Th. Lockert <tholo@sigmasoft.com>
8 * Copyright (c) 2006 Thorsten Glaser <tg@mirbsd.de>
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 4. The name of the author may not be used to endorse or promote products
20 * derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
25 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
28 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34 #include <sys/types.h>
35
36 #include <ctype.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <limits.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44
45 __RCSID("$MirOS: src/usr.bin/cap_mkdb/getinfo.c,v 1.5 2006/11/01 00:49:45 tg Exp $");
46
47 #define BFRAG 1024
48 #define BSIZE 1024
49 #define ESC ('[' & 037) /* ASCII ESC */
50 #define MAX_RECURSION 32 /* maximum getent recursion */
51 #define SFRAG 100 /* cgetstr mallocs in SFRAG chunks */
52
53 #define RECOK (char)0
54 #define TCERR (char)1
55 #define SHADOW (char)2
56
57 static int getent(char **, u_int *, char **, int, char *, int);
58 static char *igetcap(char *, const char *, int);
59 static int igetmatch(char *, char *);
60 static int igetclose(void);
61
62 int igetnext(char **, char **);
63
64 /*
65 * Igetcap searches the capability record buf for the capability cap with
66 * type `type'. A pointer to the value of cap is returned on success, NULL
67 * if the requested capability couldn't be found.
68 *
69 * Specifying a type of ',' means that nothing should follow cap (,cap,).
70 * In this case a pointer to the terminating ',' or NUL will be returned if
71 * cap is found.
72 *
73 * If (cap, '@') or (cap, terminator, '@') is found before (cap, terminator)
74 * return NULL.
75 */
76 static char *
igetcap(char * buf,const char * cap,int type)77 igetcap(char *buf, const char *cap, int type)
78 {
79 char *bp;
80 const char *cp;
81
82 bp = buf;
83 for (;;) {
84 /*
85 * Skip past the current capability field - it's either the
86 * name field if this is the first time through the loop, or
87 * the remainder of a field whose name failed to match cap.
88 */
89 for (;;)
90 if (*bp == '\0')
91 return (NULL);
92 else if ((*bp == '\\') || (*bp == '^')) {
93 if (*++bp == '\0')
94 return (NULL);
95 bp++;
96 continue;
97 } else
98 if (*bp++ == ',')
99 break;
100
101 /*
102 * Try to match (cap, type) in buf.
103 */
104 for (cp = cap; *cp == *bp && *bp != '\0'; cp++, bp++)
105 continue;
106 if (*cp != '\0')
107 continue;
108 if (*bp == '@')
109 return (NULL);
110 if (type == ',') {
111 if (*bp != '\0' && *bp != ',')
112 continue;
113 return(bp);
114 }
115 if (*bp != type)
116 continue;
117 bp++;
118 return (*bp == '@' ? NULL : bp);
119 }
120 /* NOTREACHED */
121 }
122
123 /*
124 * Getent implements the functions of igetent. If fd is non-negative,
125 * *db_array has already been opened and fd is the open file descriptor. We
126 * do this to save time and avoid using up file descriptors for use=
127 * recursions.
128 *
129 * Getent returns the same success/failure codes as igetent. On success, a
130 * pointer to a malloc'ed capability record with all use= capabilities fully
131 * expanded and its length (not including trailing ASCII NUL) are left in
132 * *cap and *len.
133 *
134 * Basic algorithm:
135 * + Allocate memory incrementally as needed in chunks of size BFRAG
136 * for capability buffer.
137 * + Recurse for each use=name and interpolate result. Stop when all
138 * names interpolated, a name can't be found, or depth exceeds
139 * MAX_RECURSION.
140 */
141 static int
getent(char ** cap,u_int * len,char ** db_array,int fd,char * name,int depth)142 getent(char **cap, u_int *len, char **db_array, int fd, char *name, int depth)
143 {
144 char *r_end, *rp, **db_p;
145 int myfd, eof, foundit;
146 char *record;
147 int tc_not_resolved;
148
149 /*
150 * Return with ``loop detected'' error if we've recursed more than
151 * MAX_RECURSION times.
152 */
153 if (depth > MAX_RECURSION)
154 return (-3);
155
156 /*
157 * Allocate first chunk of memory.
158 */
159 if ((record = malloc(BFRAG)) == NULL) {
160 errno = ENOMEM;
161 return (-2);
162 }
163 r_end = record + BFRAG;
164 foundit = 0;
165 rp = NULL;
166 myfd = -1;
167 /*
168 * Loop through database array until finding the record.
169 */
170
171 for (db_p = db_array; *db_p != NULL; db_p++) {
172 eof = 0;
173
174 /*
175 * Open database if not already open.
176 */
177
178 if (fd >= 0) {
179 (void)lseek(fd, (off_t)0, SEEK_SET);
180 myfd = 0;
181 } else {
182 fd = open(*db_p, O_RDONLY, 0);
183 if (fd < 0) {
184 /* No error on unfound file. */
185 continue;
186 }
187 myfd = 1;
188 }
189 /*
190 * Find the requested capability record ...
191 */
192 {
193 char buf[BUFSIZ];
194 char *b_end, *bp;
195 int c;
196
197 /*
198 * Loop invariants:
199 * There is always room for one more character in record.
200 * R_end always points just past end of record.
201 * Rp always points just past last character in record.
202 * B_end always points just past last character in buf.
203 * Bp always points at next character in buf.
204 */
205 b_end = buf;
206 bp = buf;
207 for (;;) {
208
209 /*
210 * Read in a line implementing (\, newline)
211 * line continuation.
212 */
213 rp = record;
214 for (;;) {
215 if (bp >= b_end) {
216 int n;
217
218 n = read(fd, buf, sizeof(buf));
219 if (n <= 0) {
220 if (myfd)
221 (void)close(fd);
222 if (n < 0) {
223 free(record);
224 return (-2);
225 } else {
226 fd = -1;
227 eof = 1;
228 break;
229 }
230 }
231 b_end = buf+n;
232 bp = buf;
233 }
234
235 c = *bp++;
236 if (c == '\n') {
237 if (bp >= b_end) {
238 int n;
239
240 n = read(fd, buf, sizeof(buf));
241 if (n <= 0) {
242 if (myfd)
243 (void)close(fd);
244 if (n < 0) {
245 free(record);
246 return (-2);
247 } else {
248 fd = -1;
249 eof = 1;
250 break;
251 }
252 }
253 b_end = buf+n;
254 bp = buf;
255 }
256 if (rp > record && isspace(*bp))
257 continue;
258 else
259 break;
260 }
261 if (rp <= record || *(rp - 1) != ',' || !isspace(c))
262 *rp++ = c;
263
264 /*
265 * Enforce loop invariant: if no room
266 * left in record buffer, try to get
267 * some more.
268 */
269 if (rp >= r_end) {
270 u_int pos;
271 size_t newsize;
272
273 pos = rp - record;
274 newsize = r_end - record + BFRAG;
275 record = realloc(record, newsize);
276 if (record == NULL) {
277 errno = ENOMEM;
278 if (myfd)
279 (void)close(fd);
280 return (-2);
281 }
282 r_end = record + newsize;
283 rp = record + pos;
284 }
285 }
286 /* loop invariant let's us do this */
287 *rp++ = '\0';
288
289 /*
290 * Toss blank lines and comments.
291 */
292 if (*record == '\0' || *record == '#')
293 continue;
294
295 /*
296 * See if this is the record we want ...
297 */
298 if (igetmatch(record, name) == 0) {
299 foundit = 1;
300 break; /* found it! */
301 }
302
303 /*
304 * If encountered eof check next file.
305 */
306 if (eof)
307 break;
308 }
309 }
310 if (foundit)
311 break;
312 }
313
314 if (!foundit)
315 return (-1);
316
317 /*
318 * Got the capability record, but now we have to expand all use=name
319 * references in it ...
320 */
321 {
322 char *newicap, *s;
323 int newilen;
324 u_int ilen;
325 int diff, iret, tclen;
326 char *icap, *scan, *tc, *tcstart, *tcend;
327
328 /*
329 * Loop invariants:
330 * There is room for one more character in record.
331 * R_end points just past end of record.
332 * Rp points just past last character in record.
333 * Scan points at remainder of record that needs to be
334 * scanned for use=name constructs.
335 */
336 scan = record;
337 tc_not_resolved = 0;
338 for (;;) {
339 if ((tc = igetcap(scan, "use", '=')) == NULL)
340 break;
341
342 /*
343 * Find end of use=name and stomp on the trailing `,'
344 * (if present) so we can use it to call ourselves.
345 */
346 s = tc;
347 for (;;)
348 if (*s == '\0')
349 break;
350 else
351 if (*s++ == ',') {
352 *(s - 1) = '\0';
353 break;
354 }
355 tcstart = tc - 4;
356 tclen = s - tcstart;
357 tcend = s;
358
359 iret = getent(&icap, &ilen, db_p, fd, tc, depth+1);
360 newicap = icap; /* Put into a register. */
361 newilen = ilen;
362 if (iret != 0) {
363 /* an error */
364 if (iret < -1) {
365 if (myfd)
366 (void)close(fd);
367 free(record);
368 return (iret);
369 }
370 if (iret == 1)
371 tc_not_resolved = 1;
372 /* couldn't resolve tc */
373 if (iret == -1) {
374 *(s - 1) = ',';
375 scan = s - 1;
376 tc_not_resolved = 1;
377 continue;
378
379 }
380 }
381 /* not interested in name field of tc'ed record */
382 s = newicap;
383 for (;;)
384 if (*s == '\0')
385 break;
386 else if ((*s == '\\') || (*s == '^')) {
387 if (*++s == '\0')
388 break;
389 s++;
390 continue;
391 } else
392 if (*s++ == ',')
393 break;
394 newilen -= s - newicap;
395 newicap = s;
396
397 /* make sure interpolated record is `,'-terminated */
398 s += newilen;
399 if (*(s-1) != ',') {
400 *s = ','; /* overwrite NUL with , */
401 newilen++;
402 }
403
404 /*
405 * Make sure there's enough room to insert the
406 * new record.
407 */
408 diff = newilen - tclen;
409 if (diff >= r_end - rp) {
410 u_int pos, tcpos, tcposend;
411 size_t newsize;
412
413 pos = rp - record;
414 newsize = r_end - record + diff + BFRAG;
415 tcpos = tcstart - record;
416 tcposend = tcend - record;
417 record = realloc(record, newsize);
418 if (record == NULL) {
419 errno = ENOMEM;
420 if (myfd)
421 (void)close(fd);
422 free(icap);
423 return (-2);
424 }
425 r_end = record + newsize;
426 rp = record + pos;
427 tcstart = record + tcpos;
428 tcend = record + tcposend;
429 }
430
431 /*
432 * Insert tc'ed record into our record.
433 */
434 s = tcstart + newilen;
435 memmove(s, tcend, (size_t)(rp - tcend));
436 memmove(tcstart, newicap, (size_t)newilen);
437 rp += diff;
438 free(icap);
439
440 /*
441 * Start scan on `,' so next igetcap works properly
442 * (igetcap always skips first field).
443 */
444 scan = s-1;
445 }
446
447 }
448 /*
449 * Close file (if we opened it), give back any extra memory, and
450 * return capability, length and success.
451 */
452 if (myfd)
453 (void)close(fd);
454 *len = rp - record - 1; /* don't count NUL */
455 if (r_end > rp)
456 if ((record =
457 realloc(record, (size_t)(rp - record))) == NULL) {
458 errno = ENOMEM;
459 return (-2);
460 }
461
462 *cap = record;
463 if (tc_not_resolved)
464 return (1);
465 return (0);
466 }
467
468 /*
469 * Igetmatch will return 0 if name is one of the names of the capability
470 * record buf, -1 if not.
471 */
472 static int
igetmatch(char * buf,char * name)473 igetmatch(char *buf, char *name)
474 {
475 char *np, *bp;
476
477 /*
478 * Start search at beginning of record.
479 */
480 bp = buf;
481 for (;;) {
482 /*
483 * Try to match a record name.
484 */
485 np = name;
486 for (;;)
487 if (*np == '\0') {
488 if (*bp == '|' || *bp == ',' || *bp == '\0')
489 return (0);
490 else
491 break;
492 } else {
493 if (*bp++ != *np++)
494 break;
495 }
496
497 /*
498 * Match failed, skip to next name in record.
499 */
500 bp--; /* a '|' or ',' may have stopped the match */
501 for (;;)
502 if (*bp == '\0' || *bp == ',')
503 return (-1); /* match failed totally */
504 else
505 if (*bp++ == '|')
506 break; /* found next name */
507 }
508 }
509
510 static FILE *pfp;
511 static int slash;
512 static char **dbp;
513
514 static int
igetclose(void)515 igetclose(void)
516 {
517 if (pfp != NULL) {
518 (void)fclose(pfp);
519 pfp = NULL;
520 }
521 dbp = NULL;
522 slash = 0;
523 return(0);
524 }
525
526 /*
527 * Igetnext() gets either the first or next entry in the logical database
528 * specified by db_array. It returns 0 upon completion of the database, 1
529 * upon returning an entry with more remaining, and -1 if an error occurs.
530 */
531 int
igetnext(char ** bp,char ** db_array)532 igetnext(char **bp, char **db_array)
533 {
534 size_t len;
535 int status, done;
536 char *cp, *line, *rp, *np, buf[BSIZE], nbuf[BSIZE];
537 u_int dummy;
538
539 if (dbp == NULL)
540 dbp = db_array;
541
542 if (pfp == NULL && (pfp = fopen(*dbp, "r")) == NULL) {
543 (void)igetclose();
544 return (-1);
545 }
546 for (;;) {
547 line = fgetln(pfp, &len);
548 if (line == NULL) {
549 if (ferror(pfp)) {
550 (void)igetclose();
551 return (-1);
552 } else {
553 (void)fclose(pfp);
554 pfp = NULL;
555 if (*++dbp == NULL) {
556 (void)igetclose();
557 return (0);
558 } else if ((pfp =
559 fopen(*dbp, "r")) == NULL) {
560 (void)igetclose();
561 return (-1);
562 } else
563 continue;
564 }
565 } else
566 line[len - 1] = '\0';/* XXX - assumes newline */
567 if (len == 1) {
568 slash = 0;
569 continue;
570 }
571 if (isspace(*line) ||
572 *line == ',' || *line == '#' || slash) {
573 if (line[len - 2] == '\\')
574 slash = 1;
575 else
576 slash = 0;
577 continue;
578 }
579 if (line[len - 2] == '\\')
580 slash = 1;
581 else
582 slash = 0;
583
584 /*
585 * Line points to a name line.
586 */
587 done = 0;
588 np = nbuf;
589 for (;;) {
590 for (cp = line; *cp != '\0'; cp++) {
591 if ((*cp == '\\') || (*cp == '^')) {
592 *np++ = *cp++;
593 if (*cp == '\0')
594 break;
595 } else if (*cp == ',') {
596 *np++ = ',';
597 done = 1;
598 break;
599 }
600 *np++ = *cp;
601 }
602 if (done) {
603 *np = '\0';
604 break;
605 } else { /* name field extends beyond the line */
606 line = fgetln(pfp, &len);
607 if (line == NULL) {
608 if (ferror(pfp)) {
609 (void)igetclose();
610 return (-1);
611 }
612 /* Move on to next file. */
613 (void)fclose(pfp);
614 pfp = NULL;
615 ++dbp;
616 /* NUL terminate nbuf. */
617 *np = '\0';
618 break;
619 } else
620 /* XXX - assumes newline */
621 line[len - 1] = '\0';
622 }
623 }
624 rp = buf;
625 for(cp = nbuf; *cp; cp++) {
626 if ((*cp == '\\') || (*cp == '^')) {
627 *rp++ = *cp++;
628 if (!*cp)
629 break;
630 } else if (*cp == '|' || *cp == ',')
631 break;
632 *rp++ = *cp;
633 }
634
635 *rp = '\0';
636 status = getent(bp, &dummy, db_array, -1, buf, 0);
637 if (status == -2 || status == -3)
638 (void)igetclose();
639
640 return (status + 1);
641 }
642 /* NOTREACHED */
643 }
644