xref: /illumos-gate/usr/src/cmd/fs.d/autofs/ns_files.c (revision 55381082)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  *	ns_files.c
24  *
25  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <syslog.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <nsswitch.h>
37 #include <sys/stat.h>
38 #include <sys/param.h>
39 #include <rpc/rpc.h>
40 #include <rpcsvc/nfs_prot.h>
41 #include <thread.h>
42 #include <assert.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <unistd.h>
46 #include <synch.h>
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 #include "automount.h"
50 
51 static int read_execout(char *key, char **lp, char *fname, char *line,
52 			int linesz);
53 static FILE *file_open(char *, char *, char **, char ***);
54 
55 /*
56  * Initialize the stack
57  */
58 void
59 init_files(char **stack, char ***stkptr)
60 {
61 	/*
62 	 * The call is bogus for automountd since the stack is
63 	 * is more appropriately initialized in the thread-private
64 	 * routines
65 	 */
66 	if (stack == NULL && stkptr == NULL)
67 		return;
68 	(void) stack_op(INIT, NULL, stack, stkptr);
69 }
70 
71 int
72 getmapent_files(key, mapname, ml, stack, stkptr, iswildcard, isrestricted)
73 	char *key;
74 	char *mapname;
75 	struct mapline *ml;
76 	char **stack, ***stkptr;
77 	bool_t *iswildcard;
78 	bool_t isrestricted;
79 {
80 	int nserr;
81 	FILE *fp;
82 	char word[MAXPATHLEN+1], wordq[MAXPATHLEN+1];
83 	char linebuf[LINESZ], lineqbuf[LINESZ];
84 	char *lp, *lq;
85 	struct stat stbuf;
86 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
87 	int syntaxok = 1;
88 
89 	if (iswildcard)
90 		*iswildcard = FALSE;
91 	if ((fp = file_open(mapname, fname, stack, stkptr)) == NULL) {
92 		nserr = __NSW_UNAVAIL;
93 		goto done;
94 	}
95 
96 	if (stat(fname, &stbuf) < 0) {
97 		nserr = __NSW_UNAVAIL;
98 		goto done;
99 	}
100 
101 	/*
102 	 * If the file has its execute bit on then
103 	 * assume it's an executable map.
104 	 * Execute it and pass the key as an argument.
105 	 * Expect to get a map entry on the stdout.
106 	 * Ignore the "x" bit on restricted maps.
107 	 */
108 	if (!isrestricted && (stbuf.st_mode & S_IXUSR)) {
109 		int rc;
110 
111 		if (trace > 1) {
112 			trace_prt(1,
113 				"\tExecutable map: map=%s key=%s\n",
114 				fname, key);
115 		}
116 
117 		rc = read_execout(key, &lp, fname, ml->linebuf, LINESZ);
118 
119 		if (rc != 0) {
120 			nserr = __NSW_UNAVAIL;
121 			goto done;
122 		}
123 
124 		if (lp == NULL || strlen(ml->linebuf) == 0) {
125 			nserr = __NSW_NOTFOUND;
126 			goto done;
127 		}
128 
129 		unquote(ml->linebuf, ml->lineqbuf);
130 		nserr = __NSW_SUCCESS;
131 		goto done;
132 	}
133 
134 
135 	/*
136 	 * It's just a normal map file.
137 	 * Search for the entry with the required key.
138 	 */
139 	for (;;) {
140 		lp = get_line(fp, fname, linebuf, sizeof (linebuf));
141 		if (lp == NULL) {
142 			nserr = __NSW_NOTFOUND;
143 			goto done;
144 		}
145 		if (verbose && syntaxok && isspace(*(uchar_t *)lp)) {
146 			syntaxok = 0;
147 			syslog(LOG_ERR,
148 				"leading space in map entry \"%s\" in %s",
149 				lp, mapname);
150 		}
151 		lq = lineqbuf;
152 		unquote(lp, lq);
153 		if ((getword(word, wordq, &lp, &lq, ' ', sizeof (word))
154 			== -1) || (word[0] == '\0'))
155 			continue;
156 		if (strcmp(word, key) == 0)
157 			break;
158 		if (word[0] == '*' && word[1] == '\0') {
159 			if (iswildcard)
160 				*iswildcard = TRUE;
161 			break;
162 		}
163 		if (word[0] == '+') {
164 			nserr = getmapent(key, word+1, ml, stack, stkptr,
165 						iswildcard, isrestricted);
166 			if (nserr == __NSW_SUCCESS)
167 				goto done;
168 			continue;
169 		}
170 
171 		/*
172 		 * sanity check each map entry key against
173 		 * the lookup key as the map is searched.
174 		 */
175 		if (verbose && syntaxok) { /* sanity check entry */
176 			if (*key == '/') {
177 				if (*word != '/') {
178 					syntaxok = 0;
179 					syslog(LOG_ERR,
180 					"bad key \"%s\" in direct map %s\n",
181 					word, mapname);
182 				}
183 			} else {
184 				if (strchr(word, '/')) {
185 					syntaxok = 0;
186 					syslog(LOG_ERR,
187 					"bad key \"%s\" in indirect map %s\n",
188 					word, mapname);
189 				}
190 			}
191 		}
192 	}
193 
194 	(void) strcpy(ml->linebuf, lp);
195 	(void) strcpy(ml->lineqbuf, lq);
196 	nserr = __NSW_SUCCESS;
197 done:
198 	if (fp) {
199 		(void) stack_op(POP, (char *)NULL, stack, stkptr);
200 		(void) fclose(fp);
201 	}
202 
203 
204 	return (nserr);
205 }
206 
207 int
208 getmapkeys_files(mapname, list, error, cache_time, stack, stkptr)
209 	char *mapname;
210 	struct dir_entry **list;
211 	int *error;
212 	int *cache_time;
213 	char **stack, ***stkptr;
214 {
215 	FILE *fp = NULL;
216 	char word[MAXPATHLEN+1], wordq[MAXPATHLEN+1];
217 	char linebuf[LINESZ], lineqbuf[LINESZ];
218 	char *lp, *lq;
219 	struct stat stbuf;
220 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
221 	int syntaxok = 1;
222 	int nserr;
223 	struct dir_entry *last = NULL;
224 
225 	if (trace > 1)
226 		trace_prt(1, "getmapkeys_files %s\n", mapname);
227 
228 	*cache_time = RDDIR_CACHE_TIME;
229 	if ((fp = file_open(mapname, fname, stack, stkptr)) == NULL) {
230 		*error = ENOENT;
231 		nserr = __NSW_UNAVAIL;
232 		goto done;
233 	}
234 	if (fseek(fp, 0L, SEEK_SET) == -1) {
235 		*error = ENOENT;
236 		nserr = __NSW_UNAVAIL;
237 		goto done;
238 	}
239 
240 	if (stat(fname, &stbuf) < 0) {
241 		*error = ENOENT;
242 		nserr = __NSW_UNAVAIL;
243 		goto done;
244 	}
245 
246 	/*
247 	 * If the file has its execute bit on then
248 	 * assume it's an executable map.
249 	 * I don't know how to list executable maps, return
250 	 * an empty map.
251 	 */
252 	if (stbuf.st_mode & S_IXUSR) {
253 		*error = 0;
254 		nserr = __NSW_SUCCESS;
255 		goto done;
256 	}
257 	/*
258 	 * It's just a normal map file.
259 	 * List entries one line at a time.
260 	 */
261 	for (;;) {
262 		lp = get_line(fp, fname, linebuf, sizeof (linebuf));
263 		if (lp == NULL) {
264 			nserr = __NSW_SUCCESS;
265 			goto done;
266 		}
267 		if (syntaxok && isspace(*(uchar_t *)lp)) {
268 			syntaxok = 0;
269 			syslog(LOG_ERR,
270 				"leading space in map entry \"%s\" in %s",
271 				lp, mapname);
272 		}
273 		lq = lineqbuf;
274 		unquote(lp, lq);
275 		if ((getword(word, wordq, &lp, &lq, ' ', MAXFILENAMELEN)
276 				== -1) || (word[0] == '\0'))
277 			continue;
278 		/*
279 		 * Wildcard entries should be ignored and this should be
280 		 * the last entry read to corroborate the search through
281 		 * files, i.e., search for key until a wildcard is reached.
282 		 */
283 		if (word[0] == '*' && word[1] == '\0')
284 			break;
285 		if (word[0] == '+') {
286 			/*
287 			 * Name switch here
288 			 */
289 			getmapkeys(word+1, list, error, cache_time,
290 				stack, stkptr, 0);
291 			/*
292 			 * the list may have been updated, therefore
293 			 * our 'last' may no longer be valid
294 			 */
295 			last = NULL;
296 			continue;
297 		}
298 
299 		if (add_dir_entry(word, list, &last) != 0) {
300 			*error = ENOMEM;
301 			goto done;
302 		}
303 		assert(last != NULL);
304 	}
305 
306 	nserr = __NSW_SUCCESS;
307 done:
308 	if (fp) {
309 		(void) stack_op(POP, (char *)NULL, stack, stkptr);
310 		(void) fclose(fp);
311 	}
312 
313 	if (*list != NULL) {
314 		/*
315 		 * list of entries found
316 		 */
317 		*error = 0;
318 	}
319 	return (nserr);
320 }
321 
322 int
323 loadmaster_files(mastermap, defopts, stack, stkptr)
324 	char *mastermap;
325 	char *defopts;
326 	char **stack, ***stkptr;
327 {
328 	FILE *fp;
329 	int done = 0;
330 	char *line, *dir, *map, *opts;
331 	char linebuf[LINESZ];
332 	char lineq[LINESZ];
333 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
334 
335 
336 	if ((fp = file_open(mastermap, fname, stack, stkptr)) == NULL)
337 		return (__NSW_UNAVAIL);
338 
339 	while ((line = get_line(fp, fname, linebuf,
340 				sizeof (linebuf))) != NULL) {
341 		unquote(line, lineq);
342 		if (macro_expand("", line, lineq, LINESZ)) {
343 			syslog(LOG_ERR,
344 				"map %s: line too long (max %d chars)",
345 				mastermap, LINESZ - 1);
346 			continue;
347 		}
348 		dir = line;
349 		while (*dir && isspace(*dir))
350 			dir++;
351 		if (*dir == '\0')
352 			continue;
353 		map = dir;
354 
355 		while (*map && !isspace(*map)) map++;
356 		if (*map)
357 			*map++ = '\0';
358 
359 		if (*dir == '+') {
360 			opts = map;
361 			while (*opts && isspace(*opts))
362 				opts++;
363 			if (*opts != '-')
364 				opts = defopts;
365 			else
366 				opts++;
367 			/*
368 			 * Check for no embedded blanks.
369 			 */
370 			if (strcspn(opts, " 	") == strlen(opts)) {
371 				dir++;
372 				(void) loadmaster_map(dir, opts, stack, stkptr);
373 			} else {
374 pr_msg("Warning: invalid entry for %s in %s ignored.\n", dir, fname);
375 				continue;
376 			}
377 
378 		} else {
379 			while (*map && isspace(*map))
380 				map++;
381 			if (*map == '\0')
382 				continue;
383 			opts = map;
384 			while (*opts && !isspace(*opts))
385 				opts++;
386 			if (*opts) {
387 				*opts++ = '\0';
388 				while (*opts && isspace(*opts))
389 					opts++;
390 			}
391 			if (*opts != '-')
392 				opts = defopts;
393 			else
394 				opts++;
395 			/*
396 			 * Check for no embedded blanks.
397 			 */
398 			if (strcspn(opts, " 	") == strlen(opts)) {
399 				dirinit(dir, map, opts, 0, stack, stkptr);
400 			} else {
401 pr_msg("Warning: invalid entry for %s in %s ignored.\n", dir, fname);
402 				continue;
403 			}
404 		}
405 		done++;
406 	}
407 
408 	(void) stack_op(POP, (char *)NULL, stack, stkptr);
409 	(void) fclose(fp);
410 
411 	return (done ? __NSW_SUCCESS : __NSW_NOTFOUND);
412 }
413 
414 int
415 loaddirect_files(map, local_map, opts, stack, stkptr)
416 	char *map, *local_map, *opts;
417 	char **stack, ***stkptr;
418 {
419 	FILE *fp;
420 	int done = 0;
421 	char *line, *p1, *p2;
422 	char linebuf[LINESZ];
423 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
424 
425 	if ((fp = file_open(map, fname, stack, stkptr)) == NULL)
426 		return (__NSW_UNAVAIL);
427 
428 	while ((line = get_line(fp, fname, linebuf,
429 				sizeof (linebuf))) != NULL) {
430 		p1 = line;
431 		while (*p1 && isspace(*p1))
432 			p1++;
433 		if (*p1 == '\0')
434 			continue;
435 		p2 = p1;
436 		while (*p2 && !isspace(*p2))
437 			p2++;
438 		*p2 = '\0';
439 		if (*p1 == '+') {
440 			p1++;
441 			(void) loaddirect_map(p1, local_map, opts, stack,
442 					stkptr);
443 		} else {
444 			dirinit(p1, local_map, opts, 1, stack, stkptr);
445 		}
446 		done++;
447 	}
448 
449 	(void) stack_op(POP, (char *)NULL, stack, stkptr);
450 	(void) fclose(fp);
451 
452 	return (done ? __NSW_SUCCESS : __NSW_NOTFOUND);
453 }
454 
455 /*
456  * This procedure opens the file and pushes it onto the
457  * the stack. Only if a file is opened successfully, is
458  * it pushed onto the stack
459  */
460 static FILE *
461 file_open(map, fname, stack, stkptr)
462 	char *map, *fname;
463 	char **stack, ***stkptr;
464 {
465 	FILE *fp;
466 
467 	if (*map != '/') {
468 		/* prepend an "/etc" */
469 		(void) strcpy(fname, "/etc/");
470 		(void) strcat(fname, map);
471 	} else
472 		(void) strcpy(fname, map);
473 
474 	fp = fopen(fname, "r");
475 
476 	if (fp != NULL) {
477 		if (!stack_op(PUSH, fname, stack, stkptr)) {
478 			(void) fclose(fp);
479 			return (NULL);
480 		}
481 	}
482 	return (fp);
483 }
484 
485 /*
486  * reimplemnted to be MT-HOT.
487  */
488 int
489 stack_op(op, name, stack, stkptr)
490 	int op;
491 	char *name;
492 	char **stack, ***stkptr;
493 {
494 	char **ptr = NULL;
495 	char **stk_top = &stack[STACKSIZ - 1];
496 
497 	/*
498 	 * the stackptr points to the next empty slot
499 	 * for PUSH: put the element and increment stkptr
500 	 * for POP: decrement stkptr and free
501 	 */
502 
503 	switch (op) {
504 	case INIT:
505 		for (ptr = stack; ptr != stk_top; ptr++)
506 			*ptr = (char *)NULL;
507 		*stkptr = stack;
508 		return (1);
509 	case ERASE:
510 		for (ptr = stack; ptr != stk_top; ptr++)
511 			if (*ptr) {
512 				if (trace > 1)
513 					trace_prt(1, "  ERASE %s\n", *ptr);
514 				free (*ptr);
515 				*ptr = (char *)NULL;
516 			}
517 		*stkptr = stack;
518 		return (1);
519 	case PUSH:
520 		if (*stkptr == stk_top)
521 			return (0);
522 		for (ptr = stack; ptr != *stkptr; ptr++)
523 			if (*ptr && (strcmp(*ptr, name) == 0)) {
524 				return (0);
525 			}
526 		if (trace > 1)
527 			trace_prt(1, "  PUSH %s\n", name);
528 		if ((**stkptr = strdup(name)) == NULL) {
529 			syslog(LOG_ERR, "stack_op: Memory alloc failed : %m");
530 			return (0);
531 		}
532 		(*stkptr)++;
533 		return (1);
534 	case POP:
535 		if (*stkptr != stack)
536 			(*stkptr)--;
537 		else
538 			syslog(LOG_ERR, "Attempt to pop empty stack\n");
539 
540 		if (*stkptr && **stkptr) {
541 			if (trace > 1)
542 				trace_prt(1, "  POP %s\n", **stkptr);
543 			free (**stkptr);
544 			**stkptr = (char *)NULL;
545 		}
546 		return (1);
547 	default:
548 		return (0);
549 	}
550 }
551 
552 #define	READ_EXECOUT_ARGS 3
553 
554 /*
555  * read_execout(char *key, char **lp, char *fname, char *line, int linesz)
556  * A simpler, multithreaded implementation of popen(). Used due to
557  * non multithreaded implementation of popen() (it calls vfork()) and a
558  * significant bug in execl().
559  * Returns 0 on OK or -1 on error.
560  */
561 static int
562 read_execout(char *key, char **lp, char *fname, char *line, int linesz)
563 {
564 	int p[2];
565 	int status = 0;
566 	int child_pid;
567 	char *args[READ_EXECOUT_ARGS];
568 	FILE *fp0;
569 
570 	if (pipe(p) < 0) {
571 		syslog(LOG_ERR, "read_execout: Cannot create pipe");
572 		return (-1);
573 	}
574 
575 	/* setup args for execv */
576 	if (((args[0] = strdup(fname)) == NULL) ||
577 		((args[1] = strdup(key)) == NULL)) {
578 		if (args[0] != NULL)
579 			free(args[0]);
580 		syslog(LOG_ERR, "read_execout: Memory allocation failed");
581 		return (-1);
582 	}
583 	args[2] = NULL;
584 
585 	if (trace > 3)
586 		trace_prt(1, "\tread_execout: forking .....\n");
587 
588 	switch ((child_pid = fork1())) {
589 	case -1:
590 		syslog(LOG_ERR, "read_execout: Cannot fork");
591 		return (-1);
592 	case 0:
593 		/*
594 		 * Child
595 		 */
596 		close(p[0]);
597 		close(1);
598 		if (fcntl(p[1], F_DUPFD, 1) != 1) {
599 			syslog(LOG_ERR,
600 			"read_execout: dup of stdout failed");
601 			_exit(-1);
602 		}
603 		close(p[1]);
604 		execv(fname, &args[0]);
605 		_exit(-1);
606 	default:
607 		/*
608 		 * Parent
609 		 */
610 		close(p[1]);
611 
612 		/*
613 		 * wait for child to complete. Note we read after the
614 		 * child exits to guarantee a full pipe.
615 		 */
616 		while (waitpid(child_pid, &status, 0) < 0) {
617 			/* if waitpid fails with EINTR, restart */
618 			if (errno != EINTR) {
619 				status = -1;
620 				break;
621 			}
622 		}
623 		if (status != -1) {
624 			if ((fp0 = fdopen(p[0], "r")) != NULL) {
625 				*lp = get_line(fp0, fname, line, linesz);
626 				fclose(fp0);
627 			} else {
628 				close(p[0]);
629 				status = -1;
630 			}
631 		} else {
632 			close(p[0]);
633 		}
634 
635 		/* free args */
636 		free(args[0]);
637 		free(args[1]);
638 
639 		if (trace > 3)
640 			trace_prt(1, "\tread_execout: map=%s key=%s line=%s\n",
641 			fname, key, line);
642 
643 		return (status);
644 	}
645 }
646