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