xref: /illumos-gate/usr/src/cmd/fs.d/autofs/ns_files.c (revision 7c478bd9)
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 2004 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 loadmaster_files(mastermap, defopts, stack, stkptr)
323 	char *mastermap;
324 	char *defopts;
325 	char **stack, ***stkptr;
326 {
327 	FILE *fp;
328 	int done = 0;
329 	char *line, *dir, *map, *opts;
330 	char linebuf[LINESZ];
331 	char lineq[LINESZ];
332 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
333 
334 
335 	if ((fp = file_open(mastermap, fname, stack, stkptr)) == NULL)
336 		return (__NSW_UNAVAIL);
337 
338 	while ((line = get_line(fp, fname, linebuf,
339 				sizeof (linebuf))) != NULL) {
340 		unquote(line, lineq);
341 		if (macro_expand("", line, lineq, LINESZ)) {
342 			syslog(LOG_ERR,
343 				"map %s: line too long (max %d chars)",
344 				mastermap, LINESZ - 1);
345 			continue;
346 		}
347 		dir = line;
348 		while (*dir && isspace(*dir))
349 			dir++;
350 		if (*dir == '\0')
351 			continue;
352 		map = dir;
353 
354 		while (*map && !isspace(*map)) map++;
355 		if (*map)
356 			*map++ = '\0';
357 
358 		if (*dir == '+') {
359 			opts = map;
360 			while (*opts && isspace(*opts))
361 				opts++;
362 			if (*opts != '-')
363 				opts = defopts;
364 			else
365 				opts++;
366 			/*
367 			 * Check for no embedded blanks.
368 			 */
369 			if (strcspn(opts, " 	") == strlen(opts)) {
370 				dir++;
371 				(void) loadmaster_map(dir, opts, stack, stkptr);
372 			} else {
373 pr_msg("Warning: invalid entry for %s in %s ignored.\n", dir, fname);
374 				continue;
375 			}
376 
377 		} else {
378 			while (*map && isspace(*map))
379 				map++;
380 			if (*map == '\0')
381 				continue;
382 			opts = map;
383 			while (*opts && !isspace(*opts))
384 				opts++;
385 			if (*opts) {
386 				*opts++ = '\0';
387 				while (*opts && isspace(*opts))
388 					opts++;
389 			}
390 			if (*opts != '-')
391 				opts = defopts;
392 			else
393 				opts++;
394 			/*
395 			 * Check for no embedded blanks.
396 			 */
397 			if (strcspn(opts, " 	") == strlen(opts)) {
398 				dirinit(dir, map, opts, 0, stack, stkptr);
399 			} else {
400 pr_msg("Warning: invalid entry for %s in %s ignored.\n", dir, fname);
401 				continue;
402 			}
403 		}
404 		done++;
405 	}
406 
407 	(void) stack_op(POP, (char *)NULL, stack, stkptr);
408 	(void) fclose(fp);
409 
410 	return (done ? __NSW_SUCCESS : __NSW_NOTFOUND);
411 }
412 
413 loaddirect_files(map, local_map, opts, stack, stkptr)
414 	char *map, *local_map, *opts;
415 	char **stack, ***stkptr;
416 {
417 	FILE *fp;
418 	int done = 0;
419 	char *line, *p1, *p2;
420 	char linebuf[LINESZ];
421 	char fname[MAXFILENAMELEN]; /* /etc prepended to mapname if reqd */
422 
423 	if ((fp = file_open(map, fname, stack, stkptr)) == NULL)
424 		return (__NSW_UNAVAIL);
425 
426 	while ((line = get_line(fp, fname, linebuf,
427 				sizeof (linebuf))) != NULL) {
428 		p1 = line;
429 		while (*p1 && isspace(*p1))
430 			p1++;
431 		if (*p1 == '\0')
432 			continue;
433 		p2 = p1;
434 		while (*p2 && !isspace(*p2))
435 			p2++;
436 		*p2 = '\0';
437 		if (*p1 == '+') {
438 			p1++;
439 			(void) loaddirect_map(p1, local_map, opts, stack,
440 					stkptr);
441 		} else {
442 			dirinit(p1, local_map, opts, 1, stack, stkptr);
443 		}
444 		done++;
445 	}
446 
447 	(void) stack_op(POP, (char *)NULL, stack, stkptr);
448 	(void) fclose(fp);
449 
450 	return (done ? __NSW_SUCCESS : __NSW_NOTFOUND);
451 }
452 
453 /*
454  * This procedure opens the file and pushes it onto the
455  * the stack. Only if a file is opened successfully, is
456  * it pushed onto the stack
457  */
458 static FILE *
459 file_open(map, fname, stack, stkptr)
460 	char *map, *fname;
461 	char **stack, ***stkptr;
462 {
463 	FILE *fp;
464 
465 	if (*map != '/') {
466 		/* prepend an "/etc" */
467 		(void) strcpy(fname, "/etc/");
468 		(void) strcat(fname, map);
469 	} else
470 		(void) strcpy(fname, map);
471 
472 	fp = fopen(fname, "r");
473 
474 	if (fp != NULL) {
475 		if (!stack_op(PUSH, fname, stack, stkptr)) {
476 			(void) fclose(fp);
477 			return (NULL);
478 		}
479 	}
480 	return (fp);
481 }
482 
483 /*
484  * reimplemnted to be MT-HOT.
485  */
486 int
487 stack_op(op, name, stack, stkptr)
488 	int op;
489 	char *name;
490 	char **stack, ***stkptr;
491 {
492 	char **ptr = NULL;
493 	char **stk_top = &stack[STACKSIZ - 1];
494 
495 	/*
496 	 * the stackptr points to the next empty slot
497 	 * for PUSH: put the element and increment stkptr
498 	 * for POP: decrement stkptr and free
499 	 */
500 
501 	switch (op) {
502 	case INIT:
503 		for (ptr = stack; ptr != stk_top; ptr++)
504 			*ptr = (char *)NULL;
505 		*stkptr = stack;
506 		return (1);
507 	case ERASE:
508 		for (ptr = stack; ptr != stk_top; ptr++)
509 			if (*ptr) {
510 				if (trace > 1)
511 					trace_prt(1, "  ERASE %s\n", *ptr);
512 				free (*ptr);
513 				*ptr = (char *)NULL;
514 			}
515 		*stkptr = stack;
516 		return (1);
517 	case PUSH:
518 		if (*stkptr == stk_top)
519 			return (0);
520 		for (ptr = stack; ptr != *stkptr; ptr++)
521 			if (*ptr && (strcmp(*ptr, name) == 0)) {
522 				return (0);
523 			}
524 		if (trace > 1)
525 			trace_prt(1, "  PUSH %s\n", name);
526 		if ((**stkptr = strdup(name)) == NULL) {
527 			syslog(LOG_ERR, "stack_op: Memory alloc failed : %m");
528 			return (0);
529 		}
530 		(*stkptr)++;
531 		return (1);
532 	case POP:
533 		if (*stkptr != stack)
534 			(*stkptr)--;
535 		else
536 			syslog(LOG_ERR, "Attempt to pop empty stack\n");
537 
538 		if (*stkptr && **stkptr) {
539 			if (trace > 1)
540 				trace_prt(1, "  POP %s\n", **stkptr);
541 			free (**stkptr);
542 			**stkptr = (char *)NULL;
543 		}
544 		return (1);
545 	default:
546 		return (0);
547 	}
548 }
549 
550 #define	READ_EXECOUT_ARGS 3
551 
552 /*
553  * read_execout(char *key, char **lp, char *fname, char *line, int linesz)
554  * A simpler, multithreaded implementation of popen(). Used due to
555  * non multithreaded implementation of popen() (it calls vfork()) and a
556  * significant bug in execl().
557  * Returns 0 on OK or -1 on error.
558  */
559 static int
560 read_execout(char *key, char **lp, char *fname, char *line, int linesz)
561 {
562 	int p[2];
563 	int status = 0;
564 	int child_pid;
565 	char *args[READ_EXECOUT_ARGS];
566 	FILE *fp0;
567 
568 	if (pipe(p) < 0) {
569 		syslog(LOG_ERR, "read_execout: Cannot create pipe");
570 		return (-1);
571 	}
572 
573 	/* setup args for execv */
574 	if (((args[0] = strdup(fname)) == NULL) ||
575 		((args[1] = strdup(key)) == NULL)) {
576 		if (args[0] != NULL)
577 			free(args[0]);
578 		syslog(LOG_ERR, "read_execout: Memory allocation failed");
579 		return (-1);
580 	}
581 	args[2] = NULL;
582 
583 	if (trace > 3)
584 		trace_prt(1, "\tread_execout: forking .....\n");
585 
586 	switch ((child_pid = fork1())) {
587 	case -1:
588 		syslog(LOG_ERR, "read_execout: Cannot fork");
589 		return (-1);
590 	case 0:
591 		/*
592 		 * Child
593 		 */
594 		close(p[0]);
595 		close(1);
596 		if (fcntl(p[1], F_DUPFD, 1) != 1) {
597 			syslog(LOG_ERR,
598 			"read_execout: dup of stdout failed");
599 			_exit(-1);
600 		}
601 		close(p[1]);
602 		execv(fname, &args[0]);
603 		_exit(-1);
604 	default:
605 		/*
606 		 * Parent
607 		 */
608 		close(p[1]);
609 
610 		/*
611 		 * wait for child to complete. Note we read after the
612 		 * child exits to guarantee a full pipe.
613 		 */
614 		while (waitpid(child_pid, &status, 0) < 0) {
615 			/* if waitpid fails with EINTR, restart */
616 			if (errno != EINTR) {
617 				status = -1;
618 				break;
619 			}
620 		}
621 		if (status != -1) {
622 			if ((fp0 = fdopen(p[0], "r")) != NULL) {
623 				*lp = get_line(fp0, fname, line, linesz);
624 				fclose(fp0);
625 			} else {
626 				close(p[0]);
627 				status = -1;
628 			}
629 		} else {
630 			close(p[0]);
631 		}
632 
633 		/* free args */
634 		free(args[0]);
635 		free(args[1]);
636 
637 		if (trace > 3)
638 			trace_prt(1, "\tread_execout: map=%s key=%s line=%s\n",
639 			fname, key, line);
640 
641 		return (status);
642 	}
643 }
644