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 /*
23  * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <ctype.h>
27 #include <errno.h>
28 #include <locale.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <strings.h>
33 #include <string.h>
34 #include <syslog.h>
35 #include <nfs/nfs.h>
36 #include <assert.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include "nfslog_config.h"
42 
43 #define	ERROR_BUFSZ	100
44 
45 /*
46  * This flag controls where error messages go.
47  * Zero means that messages go to stderr.
48  * Non-zero means that messages go to syslog.
49  */
50 boolean_t nfsl_errs_to_syslog;
51 
52 /*
53  * Pointer to the global entry in the list
54  */
55 static nfsl_config_t *global = NULL;
56 
57 /*
58  * Pointer to the raw global entry in the list, this is the
59  * global entry without the expanded paths. This is used to
60  * complete configurations.
61  */
62 static nfsl_config_t *global_raw = NULL;
63 
64 /*
65  * Last modification time to config file.
66  */
67 static timestruc_t config_last_modification = { 0 };
68 
69 /*
70  * Whitespace characters to delimit fields in a line.
71  */
72 static const char *whitespace = " \t";
73 
74 static int getconfiglist(nfsl_config_t **, boolean_t);
75 static nfsl_config_t *create_config(char *, char *, char *, char *, char *,
76 			char *, int, boolean_t, int *);
77 static nfsl_config_t *create_global_raw(int *);
78 static int update_config(nfsl_config_t *, char *, char *, char *,
79 			char *, char *, char *, int, boolean_t, boolean_t);
80 static int update_field(char **, char *, char *, boolean_t *);
81 static nfsl_config_t *findconfig(nfsl_config_t **, char *, boolean_t,
82 			nfsl_config_t **);
83 static nfsl_config_t *getlastconfig(nfsl_config_t *);
84 static void complete_with_global(char **, char **, char **, char **,
85 			char **, int *);
86 #ifdef DEBUG
87 static void remove_config(nfsl_config_t **, nfsl_config_t *, nfsl_config_t **);
88 void nfsl_printconfig(nfsl_config_t *);
89 #endif /* DEBUG */
90 static char *gataline(FILE *, char *, char *, int);
91 static int get_info(char *, char **, char **, char **, char **, char **,
92 			char **, int *);
93 static void free_config(nfsl_config_t *);
94 static int is_legal_tag(char *);
95 static boolean_t is_complete_config(char *, char *, char *, char *);
96 
97 /*
98  * Read the configuration file and create a list of configuration
99  * parameters.  Returns zero for success or an errno value.
100  * The caller is responsible for freeing the returned configlist by calling
101  * nfsl_freeconfig_list().
102  *
103  * If the configuration file does not exist, *listpp points to a config entry
104  * containing the hardwired defaults.
105  */
106 int
107 nfsl_getconfig_list(nfsl_config_t **listpp)
108 {
109 	int error = 0;
110 	char *locale;
111 
112 	/*
113 	 * Set the locale correctly so that we can correctly identify
114 	 * alphabetic characters.
115 	 */
116 	if ((locale = getenv("LC_ALL")) != NULL)
117 		(void) setlocale(LC_ALL, locale);
118 	else if ((locale = getenv("LC_CTYPE")) != NULL)
119 		(void) setlocale(LC_CTYPE, locale);
120 	else if ((locale = getenv("LANG")) != NULL)
121 		(void) setlocale(LC_CTYPE, locale);
122 
123 	/*
124 	 * Allocate 'global_raw' structure, its contents are
125 	 * indirectly allocated by create_config().
126 	 */
127 	assert(global_raw == NULL);
128 	global_raw = create_global_raw(&error);
129 	if (global_raw == NULL)
130 		return (error);
131 
132 	/*
133 	 * Build global entry with hardwired defaults first.
134 	 */
135 	assert(global == NULL);
136 	global = create_config(DEFAULTTAG, DEFAULTDIR, BUFFERPATH, NULL,
137 			FHPATH, LOGPATH, TRANSLOG_BASIC, B_TRUE, &error);
138 	*listpp = global;
139 	if (global == NULL) {
140 		free_config(global_raw);
141 		return (error);
142 	}
143 
144 	if (error = getconfiglist(listpp, B_FALSE))
145 		nfsl_freeconfig_list(listpp);
146 	else {
147 		assert(global != NULL);
148 		/*
149 		 * The global entry was replaced with the one in the file,
150 		 * clear the UPDATED flag
151 		 */
152 		global->nc_flags &= ~NC_UPDATED;
153 	}
154 	return (error);
155 }
156 
157 /*
158  * Allocates memory for the 'global_raw' structure.
159  * The actual allocation of values for its components happens in
160  * update_config().
161  */
162 static nfsl_config_t *
163 create_global_raw(int *error)
164 {
165 	nfsl_config_t *p;
166 
167 	*error = 0;
168 	if (p = (nfsl_config_t *)malloc(sizeof (*p)))
169 		(void) memset((void *)p, 0, sizeof (*p));
170 	else
171 		*error = ENOMEM;
172 
173 	return (p);
174 }
175 
176 /*
177  * Checks if the the configuration file has been modified since we last
178  * read it, if not simply returns, otherwise it re-reads it adding new
179  * configuration entries. Note that existing entries that no longer
180  * exist in the configuration file are not removed. Existing entries
181  * that are modified in the configuration file are updated in the list
182  * as well.
183  * if 'updated' is defined then it is set to TRUE if the list was modified.
184  *
185  * Note that if an error occurs, the list may be corrupted.
186  * It is the responsibility of the caller to free the list.
187  * If the configuration file does not exist, we simply return the list
188  * that we previously had, log a message and return success.
189  */
190 int
191 nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *updated)
192 {
193 	struct stat st;
194 	int error = 0;
195 
196 	if (updated != NULL)
197 		*updated = B_FALSE;
198 
199 	if (stat(NFSL_CONFIG_FILE_PATH, &st) == -1) {
200 		error = errno;
201 		if (nfsl_errs_to_syslog) {
202 			syslog(LOG_ERR, gettext(
203 				"Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
204 				strerror(error));
205 		} else {
206 			(void) fprintf(stderr, gettext(
207 				"Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
208 				strerror(error));
209 		}
210 		return (0);
211 	}
212 
213 	if (config_last_modification.tv_sec == st.st_mtim.tv_sec &&
214 	    config_last_modification.tv_nsec == st.st_mtim.tv_nsec)
215 		return (0);
216 
217 	if (updated != NULL)
218 		*updated = B_TRUE;
219 
220 	return (getconfiglist(listpp, B_TRUE));
221 }
222 
223 /*
224  * Does the real work. Reads the configuration file and creates the
225  * list of entries. Assumes that *listpp contains at least one entry.
226  * The caller is responsible for freeing any config entries added to
227  * the list whether this routine returns an error or not.
228  *
229  * Returns 0 on success and updates the '*listpp' config list,
230  * Returns non-zero error value otherwise.
231  */
232 static int
233 getconfiglist(nfsl_config_t **listpp, boolean_t updating)
234 {
235 	FILE *fp;
236 	int error = 0;
237 	nfsl_config_t *listp = NULL, *tail = NULL;
238 	char linebuf[MAX_LINESZ];
239 	char errorbuf[ERROR_BUFSZ];
240 	char *tag, *defaultdir, *bufferpath, *rpclogpath, *fhpath, *logpath;
241 	int logformat;
242 	flock_t flock;
243 	struct stat st;
244 
245 	fp = fopen(NFSL_CONFIG_FILE_PATH, "r");
246 	if (fp == NULL) {
247 		if (updating) {
248 			(void) sprintf(errorbuf, "Can't open %s",
249 				NFSL_CONFIG_FILE_PATH);
250 		} else {
251 			(void) sprintf(errorbuf,
252 				"Can't open %s - using hardwired defaults",
253 				NFSL_CONFIG_FILE_PATH);
254 		}
255 
256 		/*
257 		 * Use hardwired config.
258 		 */
259 		if (nfsl_errs_to_syslog)
260 			syslog(LOG_ERR, gettext("%s"), errorbuf);
261 		else
262 			(void) fprintf(stderr, gettext("%s\n"), errorbuf);
263 
264 		return (0);
265 	}
266 
267 	(void) memset((void *) &flock, 0, sizeof (flock));
268 	flock.l_type = F_RDLCK;
269 	if (fcntl(fileno(fp), F_SETLKW, &flock) == -1) {
270 		error = errno;
271 		if (nfsl_errs_to_syslog) {
272 			syslog(LOG_ERR, gettext(
273 				"Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH,
274 				strerror(error));
275 		} else {
276 			(void) fprintf(stderr, gettext(
277 				"Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH,
278 				strerror(error));
279 		}
280 		goto done;
281 	}
282 
283 	assert (*listpp != NULL);
284 	tail = getlastconfig(*listpp);
285 
286 	while (gataline(fp, NFSL_CONFIG_FILE_PATH, linebuf, sizeof (linebuf))) {
287 		if (linebuf[0] == '\0') {
288 			/*
289 			 * ignore lines that exceed max size
290 			 */
291 			continue;
292 		}
293 
294 		if (error = get_info(linebuf, &tag, &defaultdir, &bufferpath,
295 		    &rpclogpath, &fhpath, &logpath, &logformat))
296 			break;
297 
298 		if (listp = findconfig(listpp, tag, B_FALSE, &tail)) {
299 			/*
300 			 * An entry with the same tag name exists,
301 			 * update the fields that changed.
302 			 */
303 			error = update_config(listp, tag, defaultdir,
304 					bufferpath, rpclogpath, fhpath, logpath,
305 					logformat, B_TRUE, B_TRUE);
306 			if (error)
307 				break;
308 		} else {
309 			/*
310 			 * New entry, create it.
311 			 */
312 			listp = create_config(tag, defaultdir,
313 					bufferpath, rpclogpath, fhpath,
314 					logpath, logformat, B_TRUE, &error);
315 			if (listp == NULL)
316 				break;
317 
318 			if (*listpp == NULL)
319 				*listpp = listp;
320 			else
321 				tail->nc_next = listp;
322 			tail = listp;
323 		}
324 
325 		assert(global != NULL);
326 	}
327 
328 	if (!error) {
329 		/*
330 		 * Get mtime while we have file locked
331 		 */
332 		if (error = fstat(fileno(fp), &st)) {
333 			error = errno;
334 			if (nfsl_errs_to_syslog) {
335 				syslog(LOG_ERR, gettext(
336 				"Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH,
337 				strerror(error));
338 			} else {
339 				(void) fprintf(stderr, gettext(
340 				"Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH,
341 				strerror(error));
342 			}
343 		}
344 		config_last_modification = st.st_mtim;
345 	}
346 
347 done:
348 	(void) fclose(fp);
349 	return (error);
350 }
351 
352 /*
353  * Creates the config structure with the values specified by the
354  * parameters. If defaultdir has been specified, all relative paths
355  * are prepended with this defaultdir.
356  * If 'complete' is set then this must represent a complete config entry
357  * as specified by is_complete_config(), otherwise no work is perfomed, and
358  * NULL is returned.
359  *
360  * Returns the newly created config structure on success.
361  * Returns NULL on failure and sets error to the appropriate error.
362  */
363 static nfsl_config_t *
364 create_config(
365 	char *tag,
366 	char *defaultdir,
367 	char *bufferpath,
368 	char *rpclogpath,
369 	char *fhpath,
370 	char *logpath,
371 	int   logformat,
372 	boolean_t complete,
373 	int  *error)
374 {
375 	nfsl_config_t *config;
376 
377 	if ((config = (nfsl_config_t *)malloc(sizeof (*config))) == NULL) {
378 		*error = ENOMEM;
379 		return (NULL);
380 	}
381 	(void) memset((void *)config, 0, sizeof (*config));
382 
383 	*error = update_config(config, tag, defaultdir, bufferpath, rpclogpath,
384 			fhpath, logpath, logformat, complete, B_TRUE);
385 	if (*error) {
386 		free(config);
387 		return (NULL);
388 	}
389 
390 	config->nc_flags &= ~NC_UPDATED;	/* This is a new entry */
391 
392 	return (config);
393 }
394 
395 
396 /*
397  * Updates the configuration entry with the new information provided,
398  * sets NC_UPDATED to indicate so. The entry is left untouched if all
399  * the fields are the same (except for 'nc_rpccookie', 'nc_transcookie'
400  * and 'nc_next').
401  * Prepends each path component with 'defauldir' if 'prepend' is set.
402  *
403  * Returns 0 on success, error otherwise.
404  * On error, the config entry is left in an inconsistent state.
405  * The only thing the caller can really do with it is free it.
406  */
407 static int
408 update_config(
409 	nfsl_config_t *config,
410 	char *tag,
411 	char *defaultdir,
412 	char *bufferpath,
413 	char *rpclogpath,
414 	char *fhpath,
415 	char *logpath,
416 	int   logformat,
417 	boolean_t complete,
418 	boolean_t prepend)
419 {
420 	boolean_t updated, config_updated = B_FALSE;
421 	int error = 0;
422 
423 	if (complete && !is_complete_config(tag, bufferpath, fhpath, logpath)) {
424 		/*
425 		 * Not a complete entry
426 		 */
427 		if (nfsl_errs_to_syslog) {
428 			syslog(LOG_ERR, gettext(
429 			"update_config: \"%s\" not a complete config entry."),
430 			tag);
431 		} else {
432 			(void) fprintf(stderr, gettext(
433 			"update_config: \"%s\" not a complete config entry.\n"),
434 			tag);
435 		}
436 		return (EINVAL);
437 	}
438 
439 	assert(tag != NULL);
440 	if (config->nc_name == NULL) {
441 		/*
442 		 * New entry
443 		 */
444 		if ((config->nc_name = strdup(tag)) == NULL) {
445 			error = ENOMEM;
446 			goto errout;
447 		}
448 	} else
449 		assert(strcmp(config->nc_name, tag) == 0);
450 
451 	if (error = update_field(
452 	    &config->nc_defaultdir, defaultdir, NULL, &updated))
453 		goto errout;
454 	if (!prepend) {
455 		/*
456 		 * Do not prepend default directory.
457 		 */
458 		defaultdir = NULL;
459 	}
460 	config_updated |= updated;
461 	if (error = update_field(
462 	    &config->nc_bufferpath, bufferpath, defaultdir, &updated))
463 		goto errout;
464 	config_updated |= updated;
465 	if (error = update_field(
466 	    &config->nc_rpclogpath, rpclogpath, defaultdir, &updated))
467 		goto errout;
468 	config_updated |= updated;
469 	if (error = update_field(
470 	    &config->nc_fhpath, fhpath, defaultdir, &updated))
471 		goto errout;
472 	config_updated |= updated;
473 	if (error = update_field(
474 	    &config->nc_logpath, logpath, defaultdir, &updated))
475 		goto errout;
476 	config_updated |= updated;
477 	updated = (config->nc_logformat != logformat);
478 	if (updated)
479 		config->nc_logformat = logformat;
480 	config_updated |= updated;
481 
482 	if (config_updated)
483 		config->nc_flags |= NC_UPDATED;
484 
485 	if (strcmp(tag, DEFAULTTAG) == 0) {
486 		/*
487 		 * Have the default global config point to this entry.
488 		 */
489 		global = config;
490 
491 		/*
492 		 * Update the global_raw configuration entry.
493 		 * Make sure no expanding of paths occurs.
494 		 */
495 		if (error = update_config(global_raw, DEFAULTRAWTAG, defaultdir,
496 			bufferpath, rpclogpath, fhpath, logpath, logformat,
497 			complete, B_FALSE))
498 				goto errout;
499 	}
500 
501 	return (error);
502 
503 errout:
504 	if (nfsl_errs_to_syslog) {
505 		syslog(LOG_ERR, gettext(
506 			"update_config: Can't process \"%s\" config entry: %s"),
507 			tag, strerror(error));
508 	} else {
509 		(void) fprintf(stderr, gettext(
510 		"update_config: Can't process \"%s\" config entry: %s\n"),
511 		tag, strerror(error));
512 	}
513 	return (error);
514 }
515 
516 /*
517  * Prepends 'prependir' to 'new' if 'prependir' is defined.
518  * Compares the value of '*old' with 'new', if it has changed,
519  * then sets whatever 'old' references equal to 'new'.
520  * Returns 0 on success, error otherwise.
521  * Sets '*updated' to B_TRUE if field was modified.
522  * The value of '*updated' is undefined on error.
523  */
524 static int
525 update_field(
526 	char **old,		/* pointer to config field */
527 	char *new,		/* updated value */
528 	char *prependdir,	/* prepend this directory to new */
529 	boolean_t *updated)	/* field was modified */
530 {
531 	char *tmp_new = NULL;
532 	int need_update = 0;
533 
534 	if (new != NULL) {
535 		if (prependdir != NULL && new[0] != '/') {
536 			tmp_new = malloc(strlen(prependdir) + strlen(new) + 2);
537 			if (tmp_new == NULL)
538 				return (ENOMEM);
539 			(void) sprintf(tmp_new, "%s/%s", prependdir, new);
540 		} else {
541 			if ((tmp_new = strdup(new)) == NULL)
542 				return (ENOMEM);
543 		}
544 	}
545 
546 	if (tmp_new != NULL) {
547 		if (*old == NULL)
548 			need_update++;
549 		else if (strcmp(tmp_new, *old) != 0) {
550 			free(*old);
551 			need_update++;
552 		}
553 		if (need_update)
554 			*old = tmp_new;
555 	} else if (*old != NULL) {
556 		need_update++;
557 		free(*old);
558 		*old = NULL;
559 	}
560 
561 	*updated = need_update != 0;
562 	return (0);
563 }
564 
565 #ifdef DEBUG
566 /*
567  * Removes and frees the 'config' entry from the list
568  * pointed to by '*listpp'.
569  * No error is reported if the entry does not exist.
570  * Updates '*tail' to point to the last item in the list.
571  */
572 static void
573 remove_config(
574 	nfsl_config_t **listpp,
575 	nfsl_config_t *config,
576 	nfsl_config_t **tail)
577 {
578 	nfsl_config_t *p, *prev;
579 
580 	prev = *listpp;
581 	for (p = *listpp; p != NULL; p = p->nc_next) {
582 		if (p == config) {
583 			if (p == prev) {
584 				/*
585 				 * first element of the list
586 				 */
587 				*listpp = prev->nc_next;
588 			} else
589 				prev->nc_next = p->nc_next;
590 			free_config(p);
591 			break;
592 		}
593 		prev = p;
594 	}
595 
596 	/*
597 	 * Find tail of the list.
598 	 */
599 	for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next)
600 		;
601 }
602 #endif /* DEBUG */
603 
604 static void
605 free_config(nfsl_config_t *config)
606 {
607 	if (config == NULL)
608 		return;
609 	if (config->nc_name)
610 		free(config->nc_name);
611 	if (config->nc_defaultdir)
612 		free(config->nc_defaultdir);
613 	if (config->nc_bufferpath)
614 		free(config->nc_bufferpath);
615 	if (config->nc_rpclogpath)
616 		free(config->nc_rpclogpath);
617 	if (config->nc_fhpath)
618 		free(config->nc_fhpath);
619 	if (config->nc_logpath)
620 		free(config->nc_logpath);
621 	if (config == global)
622 		global = NULL;
623 	if (config == global_raw)
624 		global_raw = NULL;
625 	free(config);
626 }
627 
628 void
629 nfsl_freeconfig_list(nfsl_config_t **listpp)
630 {
631 	nfsl_config_t *next;
632 
633 	if (*listpp == NULL)
634 		return;
635 
636 	do {
637 		next = (*listpp)->nc_next;
638 		free_config(*listpp);
639 		*listpp = next;
640 	} while (*listpp);
641 
642 	free_config(global_raw);
643 }
644 
645 /*
646  * Returns a pointer to the first instance of 'tag' in the list.
647  * If 'remove' is true, then the entry is removed from the list and
648  * a pointer to it is returned.
649  * If '*tail' is not NULL, then it will point to the last element of
650  * the list. Note that this function assumes that *tail already
651  * points at the last element of the list.
652  * Returns NULL if the entry does not exist.
653  */
654 static nfsl_config_t *
655 findconfig(
656 	nfsl_config_t **listpp,
657 	char *tag, boolean_t remove,
658 	nfsl_config_t **tail)
659 {
660 	nfsl_config_t *p, *prev;
661 
662 	prev = *listpp;
663 	for (p = *listpp; p != NULL; p = p->nc_next) {
664 		if (strcmp(p->nc_name, tag) == 0) {
665 			if (remove) {
666 				if (p == prev) {
667 					/*
668 					 * first element of the list
669 					 */
670 					*listpp = prev->nc_next;
671 				} else
672 					prev->nc_next = p->nc_next;
673 
674 				if (tail != NULL && p == *tail) {
675 					/*
676 					 * Only update *tail if we removed
677 					 * the last element of the list, and we
678 					 * requested *tail to be updated.
679 					 */
680 					*tail = prev;
681 				}
682 			}
683 			return (p);
684 		}
685 		prev = p;
686 	}
687 
688 	return (NULL);
689 }
690 
691 static nfsl_config_t *
692 getlastconfig(nfsl_config_t *listp)
693 {
694 	nfsl_config_t *lastp = NULL;
695 
696 	for (; listp != NULL; listp = listp->nc_next)
697 		lastp = listp;
698 
699 	return (lastp);
700 }
701 
702 /*
703  * Returns a pointer to the first instance of 'tag' in the list.
704  * Returns NULL if the entry does not exist.
705  * Sets 'error' if the update of the list failed if necessary, and
706  * returns NULL.
707  */
708 nfsl_config_t *
709 nfsl_findconfig(nfsl_config_t *listp, char *tag, int *error)
710 {
711 	nfsl_config_t *config;
712 	boolean_t updated;
713 
714 	*error = 0;
715 	config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL);
716 	if (config == NULL) {
717 		/*
718 		 * Rebuild our list if the file has changed.
719 		 */
720 		if (*error = nfsl_checkconfig_list(&listp, &updated)) {
721 			/*
722 			 * List may be corrupted, notify caller.
723 			 */
724 			return (NULL);
725 		}
726 		if (updated) {
727 			/*
728 			 * Search for tag again.
729 			 */
730 			config = findconfig(&listp, tag, B_FALSE,
731 				(nfsl_config_t **)NULL);
732 		}
733 	}
734 
735 	return (config);
736 }
737 
738 /*
739  * Use the raw global values if any of the parameters is not defined.
740  */
741 static void
742 complete_with_global(
743 	char **defaultdir,
744 	char **bufferpath,
745 	char **rpclogpath,
746 	char **fhpath,
747 	char **logpath,
748 	int  *logformat)
749 {
750 	if (*defaultdir == NULL)
751 		*defaultdir = global_raw->nc_defaultdir;
752 	if (*bufferpath == NULL)
753 		*bufferpath = global_raw->nc_bufferpath;
754 	if (*rpclogpath == NULL)
755 		*rpclogpath = global_raw->nc_rpclogpath;
756 	if (*fhpath == NULL)
757 		*fhpath = global_raw->nc_fhpath;
758 	if (*logpath == NULL)
759 		*logpath = global_raw->nc_logpath;
760 	if (*logformat == 0)
761 		*logformat = global_raw->nc_logformat;
762 }
763 
764 /*
765  * Parses 'linebuf'. Returns 0 if a valid tag is found, otherwise non-zero.
766  * Unknown tokens are silently ignored.
767  * It is the responsibility of the caller to make a copy of the non-NULL
768  * parameters if they need to be used before linebuf is freed.
769  */
770 static int
771 get_info(
772 	char *linebuf,
773 	char **tag,
774 	char **defaultdir,
775 	char **bufferpath,
776 	char **rpclogpath,
777 	char **fhpath,
778 	char **logpath,
779 	int  *logformat)
780 {
781 	char *tok;
782 	char *tmp;
783 
784 	/* tag */
785 	*tag = NULL;
786 	tok = strtok(linebuf, whitespace);
787 	if (tok == NULL)
788 		goto badtag;
789 	if (!is_legal_tag(tok))
790 		goto badtag;
791 	*tag = tok;
792 
793 	*defaultdir = *bufferpath = *rpclogpath = NULL;
794 	*fhpath = *logpath = NULL;
795 	*logformat = 0;
796 
797 	while (tok = strtok(NULL, whitespace)) {
798 		if (strncmp(tok, "defaultdir=", strlen("defaultdir=")) == 0) {
799 			*defaultdir = tok + strlen("defaultdir=");
800 		} else if (strncmp(tok, "buffer=", strlen("buffer=")) == 0) {
801 			*bufferpath = tok + strlen("buffer=");
802 		} else if (strncmp(tok, "rpclog=", strlen("rpclog=")) == 0) {
803 			*rpclogpath = tok + strlen("rpclog=");
804 		} else if (strncmp(tok, "fhtable=", strlen("fhtable=")) == 0) {
805 			*fhpath = tok + strlen("fhtable=");
806 		} else if (strncmp(tok, "log=", strlen("log=")) == 0) {
807 			*logpath = tok + strlen("log=");
808 		} else if (strncmp(tok, "logformat=",
809 				strlen("logformat=")) == 0) {
810 			tmp = tok + strlen("logformat=");
811 			if (strncmp(tmp, "extended", strlen("extended")) == 0) {
812 				*logformat = TRANSLOG_EXTENDED;
813 			} else {
814 				/*
815 				 * Use transaction log basic format if
816 				 * 'extended' was not specified.
817 				 */
818 				*logformat = TRANSLOG_BASIC;
819 			}
820 		}
821 	}
822 
823 	if (strcmp(*tag, DEFAULTTAG) != 0) {
824 		/*
825 		 * Use global values for fields not specified if
826 		 * this tag is not the global tag.
827 		 */
828 		complete_with_global(defaultdir, bufferpath,
829 			rpclogpath, fhpath, logpath, logformat);
830 	}
831 
832 	return (0);
833 
834 badtag:
835 	if (nfsl_errs_to_syslog) {
836 		syslog(LOG_ERR, gettext(
837 			"Bad tag found in config file."));
838 	} else {
839 		(void) fprintf(stderr, gettext(
840 			"Bad tag found in config file.\n"));
841 	}
842 	return (-1);
843 }
844 
845 /*
846  * Returns True if we have all the elements of a complete configuration
847  * entry. A complete configuration has tag, bufferpath, fhpath and logpath
848  * defined to non-zero strings.
849  */
850 static boolean_t
851 is_complete_config(
852 	char *tag,
853 	char *bufferpath,
854 	char *fhpath,
855 	char *logpath)
856 {
857 	assert(tag != NULL);
858 	assert(strlen(tag) > 0);
859 
860 	if ((bufferpath != NULL && strlen(bufferpath) > 0) &&
861 	    (fhpath != NULL && strlen(fhpath) > 0) &&
862 	    (logpath != NULL && strlen(logpath) > 0))
863 		return (B_TRUE);
864 	return (B_FALSE);
865 }
866 
867 #ifdef DEBUG
868 /*
869  * Prints the configuration entry to stdout.
870  */
871 void
872 nfsl_printconfig(nfsl_config_t *config)
873 {
874 	if (config->nc_name)
875 		(void) printf("tag=%s\t", config->nc_name);
876 	if (config->nc_defaultdir)
877 		(void) printf("defaultdir=%s\t", config->nc_defaultdir);
878 	if (config->nc_logpath)
879 		(void) printf("logpath=%s\t", config->nc_logpath);
880 	if (config->nc_fhpath)
881 		(void) printf("fhpath=%s\t", config->nc_fhpath);
882 	if (config->nc_bufferpath)
883 		(void) printf("bufpath=%s\t", config->nc_bufferpath);
884 	if (config->nc_rpclogpath)
885 		(void) printf("rpclogpath=%s\t", config->nc_rpclogpath);
886 	if (config->nc_logformat == TRANSLOG_BASIC)
887 		(void) printf("logformat=basic");
888 	else if (config->nc_logformat == TRANSLOG_EXTENDED)
889 		(void) printf("logformat=extended");
890 	else
891 		(void) printf("config->nc_logformat=UNKNOWN");
892 
893 	if (config->nc_flags & NC_UPDATED)
894 		(void) printf("\tflags=NC_UPDATED");
895 	(void) printf("\n");
896 }
897 
898 /*
899  * Prints the configuration list to stdout.
900  */
901 void
902 nfsl_printconfig_list(nfsl_config_t *listp)
903 {
904 	for (; listp != NULL; listp = listp->nc_next) {
905 		nfsl_printconfig(listp);
906 		(void) printf("\n");
907 	}
908 }
909 #endif /* DEBUG */
910 
911 /*
912  * Returns non-zero if the given string is allowable for a tag, zero if
913  * not.
914  */
915 static int
916 is_legal_tag(char *tag)
917 {
918 	int i;
919 	int len;
920 
921 	if (tag == NULL)
922 		return (0);
923 	len = strlen(tag);
924 	if (len == 0)
925 		return (0);
926 
927 	for (i = 0; i < len; i++) {
928 		char c;
929 
930 		c = tag[i];
931 		if (!(isalnum((unsigned char)c) || c == '_'))
932 			return (0);
933 	}
934 
935 	return (1);
936 }
937 
938 /*
939  * gataline attempts to get a line from the configuration file,
940  * upto LINESZ. A line in the file is a concatenation of lines if the
941  * continuation symbol '\' is used at the end of the line. Returns
942  * line on success, a NULL on EOF, and an empty string on lines > linesz.
943  */
944 static char *
945 gataline(FILE *fp, char *path, char *line, int linesz) {
946 	register char *p = line;
947 	register int len;
948 	int excess = 0;
949 
950 	*p = '\0';
951 
952 	for (;;) {
953 		if (fgets(p, linesz - (p-line), fp) == NULL) {
954 			return (*line ? line : NULL);   /* EOF */
955 		}
956 
957 		len = strlen(line);
958 		if (len <= 0) {
959 			p = line;
960 			continue;
961 		}
962 		p = &line[len - 1];
963 
964 		/*
965 		 * Is input line too long?
966 		 */
967 		if (*p != '\n') {
968 			excess = 1;
969 			/*
970 			 * Perhaps last char read was '\'. Reinsert it
971 			 * into the stream to ease the parsing when we
972 			 * read the rest of the line to discard.
973 			 */
974 			(void) ungetc(*p, fp);
975 			break;
976 		}
977 trim:
978 
979 		/* trim trailing white space */
980 		while (p >= line && isspace(*(uchar_t *)p))
981 		*p-- = '\0';
982 		if (p < line) {			/* empty line */
983 			p = line;
984 			continue;
985 		}
986 
987 		if (*p == '\\') {		/* continuation */
988 			*p = '\0';
989 			continue;
990 		}
991 
992 		/*
993 		 * Ignore comments. Comments start with '#'
994 		 * which must be preceded by a whitespace, unless
995 		 * '#' is the first character in the line.
996 		 */
997 		p = line;
998 
999 		while (p = strchr(p, '#')) {
1000 			if (p == line || isspace(*(p-1))) {
1001 				*p-- = '\0';
1002 				goto trim;
1003 			}
1004 			p++;
1005 		}
1006 
1007 		break;
1008 	}
1009 	if (excess) {
1010 		int c;
1011 
1012 		/*
1013 		 * discard rest of line and return an empty string.
1014 		 * done to set the stream to the correct place when
1015 		 * we are done with this line.
1016 		 */
1017 		while ((c = getc(fp)) != EOF) {
1018 			*p = c;
1019 			if (*p == '\n')		/* end of the long line */
1020 				break;
1021 			else if (*p == '\\') {		/* continuation */
1022 				if (getc(fp) == EOF)	/* ignore next char */
1023 					break;
1024 			}
1025 		}
1026 		if (nfsl_errs_to_syslog) {
1027 			syslog(LOG_ERR, gettext(
1028 				"%s: line too long - ignored (max %d chars)"),
1029 				path, linesz-1);
1030 		} else {
1031 			(void) fprintf(stderr, gettext(
1032 				"%s: line too long - ignored (max %d chars)\n"),
1033 				path, linesz-1);
1034 		}
1035 		*line = '\0';
1036 	}
1037 
1038 	return (line);
1039 }
1040