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