xref: /illumos-gate/usr/src/lib/libsmbfs/smb/rcfile.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * Copyright (c) 2000, Boris Popov
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: rcfile.c,v 1.1.1.2 2001/07/06 22:38:43 conrad Exp $
33  */
34 
35 #include <fcntl.h>
36 #include <sys/types.h>
37 #include <sys/queue.h>
38 #include <sys/stat.h>
39 
40 #include <ctype.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <string.h>
44 #include <strings.h>
45 #include <stdlib.h>
46 #include <synch.h>
47 #include <unistd.h>
48 #include <pwd.h>
49 #include <libintl.h>
50 
51 #include <cflib.h>
52 #include "rcfile_priv.h"
53 
54 #include <assert.h>
55 
56 #if 0 /* before SMF */
57 #define	SMB_CFG_FILE	"/etc/nsmb.conf"
58 #define	OLD_SMB_CFG_FILE	"/usr/local/etc/nsmb.conf"
59 #endif
60 #define	SMBFS_SHARECTL_CMD	"/usr/sbin/sharectl get smbfs"
61 
62 extern int smb_debug;
63 
64 static struct rcfile *rc_cachelookup(const char *filename);
65 static struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname);
66 static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname);
67 static int		rc_freesect(struct rcfile *rcp, struct rcsection *rsp);
68 static struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *key);
69 static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name,
70     const char *value);
71 static void rc_key_free(struct rckey *p);
72 static void rc_parse(struct rcfile *rcp);
73 
74 /* lock for the variables below */
75 mutex_t rcfile_mutex = DEFAULTMUTEX;
76 
77 SLIST_HEAD(rcfile_head, rcfile);
78 static struct rcfile_head pf_head = {NULL};
79 struct rcfile *smb_rc;
80 int home_nsmbrc;
81 int insecure_nsmbrc;
82 
83 /*
84  * open rcfile and load its content, if already open - return previous handle
85  */
86 static int
87 rc_open(const char *filename, const char *mode, struct rcfile **rcfile)
88 {
89 	struct stat statbuf;
90 	struct rcfile *rcp;
91 	FILE *f;
92 
93 	assert(MUTEX_HELD(&rcfile_mutex));
94 
95 	rcp = rc_cachelookup(filename);
96 	if (rcp) {
97 		*rcfile = rcp;
98 		return (0);
99 	}
100 	f = fopen(filename, mode);
101 	if (f == NULL)
102 		return (errno);
103 	insecure_nsmbrc = 0;
104 	if (fstat(fileno(f), &statbuf) >= 0 &&
105 	    (statbuf.st_mode & 077) != 0)
106 		insecure_nsmbrc = 1;
107 	rcp = malloc(sizeof (struct rcfile));
108 	if (rcp == NULL) {
109 		fclose(f);
110 		return (ENOMEM);
111 	}
112 	bzero(rcp, sizeof (struct rcfile));
113 	rcp->rf_name = strdup(filename);
114 	rcp->rf_f = f;
115 	SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
116 	rc_parse(rcp);
117 	*rcfile = rcp;
118 	return (0);
119 }
120 
121 static int
122 rc_merge(const char *filename, struct rcfile **rcfile)
123 {
124 	struct stat statbuf;
125 	struct rcfile *rcp = *rcfile;
126 	FILE *f, *t;
127 
128 	assert(MUTEX_HELD(&rcfile_mutex));
129 
130 	insecure_nsmbrc = 0;
131 	if (rcp == NULL) {
132 		return (rc_open(filename, "r", rcfile));
133 	}
134 	f = fopen(filename, "r");
135 	if (f == NULL)
136 		return (errno);
137 	insecure_nsmbrc = 0;
138 	if (fstat(fileno(f), &statbuf) >= 0 &&
139 	    (statbuf.st_mode & 077) != 0)
140 		insecure_nsmbrc = 1;
141 	t = rcp->rf_f;
142 	rcp->rf_f = f;
143 	rc_parse(rcp);
144 	rcp->rf_f = t;
145 	fclose(f);
146 	return (0);
147 }
148 
149 /*
150  * Like rc_open, but does popen of command:
151  * sharectl get smbfs
152  */
153 static int
154 rc_popen_cmd(const char *command, struct rcfile **rcfile)
155 {
156 	struct rcfile *rcp;
157 	FILE *f;
158 
159 	assert(MUTEX_HELD(&rcfile_mutex));
160 
161 	f = popen(command, "r");
162 	if (f == NULL)
163 		return (errno);
164 	insecure_nsmbrc = 0;
165 
166 	rcp = malloc(sizeof (struct rcfile));
167 	if (rcp == NULL) {
168 		fclose(f);
169 		return (ENOMEM);
170 	}
171 	bzero(rcp, sizeof (struct rcfile));
172 	rcp->rf_name = strdup(command);
173 	rcp->rf_f = f;
174 	SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
175 	rc_parse(rcp);
176 	*rcfile = rcp;
177 	/* fclose(f) in rc_close */
178 	return (0);
179 }
180 
181 static int
182 rc_close(struct rcfile *rcp)
183 {
184 	struct rcsection *p, *n;
185 
186 	mutex_lock(&rcfile_mutex);
187 
188 	fclose(rcp->rf_f);
189 	for (p = SLIST_FIRST(&rcp->rf_sect); p; ) {
190 		n = p;
191 		p = SLIST_NEXT(p, rs_next);
192 		rc_freesect(rcp, n);
193 	}
194 	free(rcp->rf_name);
195 	SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next);
196 	free(rcp);
197 
198 	mutex_unlock(&rcfile_mutex);
199 	return (0);
200 }
201 
202 static struct rcfile *
203 rc_cachelookup(const char *filename)
204 {
205 	struct rcfile *p;
206 
207 	assert(MUTEX_HELD(&rcfile_mutex));
208 
209 	SLIST_FOREACH(p, &pf_head, rf_next)
210 		if (strcmp(filename, p->rf_name) == 0)
211 			return (p);
212 	return (0);
213 }
214 
215 static struct rcsection *
216 rc_findsect(struct rcfile *rcp, const char *sectname)
217 {
218 	struct rcsection *p;
219 
220 	assert(MUTEX_HELD(&rcfile_mutex));
221 
222 	SLIST_FOREACH(p, &rcp->rf_sect, rs_next)
223 		if (strcasecmp(p->rs_name, sectname) == 0)
224 			return (p);
225 	return (NULL);
226 }
227 
228 static struct rcsection *
229 rc_addsect(struct rcfile *rcp, const char *sectname)
230 {
231 	struct rcsection *p;
232 
233 	assert(MUTEX_HELD(&rcfile_mutex));
234 
235 	p = rc_findsect(rcp, sectname);
236 	if (p)
237 		return (p);
238 	p = malloc(sizeof (*p));
239 	if (!p)
240 		return (NULL);
241 	p->rs_name = strdup(sectname);
242 	SLIST_INIT(&p->rs_keys);
243 	SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next);
244 	return (p);
245 }
246 
247 static int
248 rc_freesect(struct rcfile *rcp, struct rcsection *rsp)
249 {
250 	struct rckey *p, *n;
251 
252 	assert(MUTEX_HELD(&rcfile_mutex));
253 
254 	SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next);
255 	for (p = SLIST_FIRST(&rsp->rs_keys); p; ) {
256 		n = p;
257 		p = SLIST_NEXT(p, rk_next);
258 		rc_key_free(n);
259 	}
260 	free(rsp->rs_name);
261 	free(rsp);
262 	return (0);
263 }
264 
265 static struct rckey *
266 rc_sect_findkey(struct rcsection *rsp, const char *keyname)
267 {
268 	struct rckey *p;
269 
270 	assert(MUTEX_HELD(&rcfile_mutex));
271 
272 	SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
273 		if (strcmp(p->rk_name, keyname) == 0)
274 			return (p);
275 	return (NULL);
276 }
277 
278 static struct rckey *
279 rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value)
280 {
281 	struct rckey *p;
282 
283 	assert(MUTEX_HELD(&rcfile_mutex));
284 
285 	p = rc_sect_findkey(rsp, name);
286 	if (!p) {
287 		p = malloc(sizeof (*p));
288 		if (!p)
289 			return (NULL);
290 		SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next);
291 		p->rk_name = strdup(name);
292 		p->rk_value = value ? strdup(value) : strdup("");
293 	}
294 	return (p);
295 }
296 
297 #if 0
298 void
299 rc_sect_delkey(struct rcsection *rsp, struct rckey *p)
300 {
301 
302 	SLIST_REMOVE(&rsp->rs_keys, p, rckey, rk_next);
303 	rc_key_free(p);
304 }
305 #endif
306 
307 static void
308 rc_key_free(struct rckey *p)
309 {
310 	free(p->rk_value);
311 	free(p->rk_name);
312 	free(p);
313 }
314 
315 
316 static char *minauth_values[] = {
317 	"none",
318 	"lm",
319 	"ntlm",
320 	"ntlmv2",
321 	"kerberos",
322 	NULL
323 };
324 
325 static int
326 eval_minauth(char *auth)
327 {
328 	int i;
329 
330 	for (i = 0; minauth_values[i]; i++)
331 		if (strcmp(auth, minauth_values[i]) == 0)
332 			return (i);
333 	return (-1);
334 }
335 
336 /*
337  * Ensure that "minauth" is set to the highest level
338  */
339 /*ARGSUSED*/
340 static void
341 set_value(struct rcfile *rcp, struct rcsection *rsp, struct rckey *rkp,
342     char *ptr)
343 {
344 	int now, new;
345 #ifdef DEBUG
346 	char *from;
347 
348 	if (smb_debug)
349 		from = (home_nsmbrc) ?
350 		    "user file" : "SMF";
351 #endif
352 
353 	if (strcmp(rkp->rk_name, "minauth") == 0) {
354 		now = eval_minauth(rkp->rk_value);
355 		new = eval_minauth(ptr);
356 		if (new <= now) {
357 #ifdef DEBUG
358 			if (smb_debug)
359 				fprintf(stderr,
360 				    "set_value: rejecting %s=%s"
361 				    " in %s from %s\n",
362 				    rkp->rk_name, ptr,
363 				    rsp->rs_name, from);
364 #endif
365 			return;
366 		}
367 	}
368 #ifdef DEBUG
369 	if (smb_debug)
370 		fprintf(stderr,
371 		    "set_value: applying %s=%s in %s from %s\n",
372 		    rkp->rk_name, ptr, rsp->rs_name, from);
373 #endif
374 	rkp->rk_value = strdup(ptr);
375 }
376 
377 
378 /* states in rc_parse */
379 enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue};
380 
381 static void
382 rc_parse(struct rcfile *rcp)
383 {
384 	FILE *f = rcp->rf_f;
385 	int state = stNewLine, c;
386 	struct rcsection *rsp = NULL;
387 	struct rckey *rkp = NULL;
388 	char buf[2048];
389 	char *next = buf, *last = &buf[sizeof (buf)-1];
390 
391 	assert(MUTEX_HELD(&rcfile_mutex));
392 
393 	while ((c = getc(f)) != EOF) {
394 		if (c == '\r')
395 			continue;
396 		if (state == stNewLine) {
397 			next = buf;
398 			if (isspace(c))
399 				continue;	/* skip leading junk */
400 			if (c == '[') {
401 				state = stHeader;
402 				rsp = NULL;
403 				continue;
404 			}
405 			if (c == '#' || c == ';') {
406 				state = stSkipToEOL;
407 			} else {		/* something meaningfull */
408 				state = stGetKey;
409 			}
410 		}
411 		/* ignore long lines */
412 		if (state == stSkipToEOL || next == last) {
413 			if (c == '\n') {
414 				state = stNewLine;
415 				next = buf;
416 			}
417 			continue;
418 		}
419 		if (state == stHeader) {
420 			if (c == ']') {
421 				*next = 0;
422 				next = buf;
423 				rsp = rc_addsect(rcp, buf);
424 				state = stSkipToEOL;
425 			} else
426 				*next++ = c;
427 			continue;
428 		}
429 		if (state == stGetKey) {
430 			/* side effect: 'key name=' */
431 			if (c == ' ' || c == '\t')
432 				continue;	/* become 'keyname=' */
433 			if (c == '\n') {	/* silently ignore ... */
434 				state = stNewLine;
435 				continue;
436 			}
437 			if (c != '=') {
438 				*next++ = c;
439 				continue;
440 			}
441 			*next = 0;
442 			if (rsp == NULL) {
443 				fprintf(stderr, dgettext(TEXT_DOMAIN,
444 				    "Key '%s' defined before section\n"), buf);
445 				state = stSkipToEOL;
446 				continue;
447 			}
448 			if (home_nsmbrc != 0 && (
449 			    strcmp(buf, "nbns") == 0 ||
450 			    strcmp(buf, "nbns_enable") == 0 ||
451 			    strcmp(buf, "nbns_broadcast") == 0 ||
452 			    strcmp(buf, "signing") == 0)) {
453 				fprintf(stderr, dgettext(TEXT_DOMAIN,
454 				    "option %s may not be set "
455 				    "in user .nsmbrc file\n"), buf);
456 				next = buf;
457 				state = stNewLine;
458 				continue;
459 			}
460 			if (insecure_nsmbrc != 0 &&
461 			    strcmp(buf, "password") == 0) {
462 				fprintf(stderr, dgettext(TEXT_DOMAIN,
463 				    "Warning: .nsmbrc file not secure, "
464 				    "ignoring passwords\n"));
465 				next = buf;
466 				state = stNewLine;
467 				continue;
468 			}
469 			rkp = rc_sect_addkey(rsp, buf, NULL);
470 			next = buf;
471 			state = stGetValue;
472 			continue;
473 		}
474 		/* only stGetValue left */
475 		if (state != stGetValue) {
476 			fprintf(stderr, dgettext(TEXT_DOMAIN,
477 			    "Well, I can't parse file '%s'\n"), rcp->rf_name);
478 			state = stSkipToEOL;
479 		}
480 		if (c != '\n') {
481 			*next++ = c;
482 			continue;
483 		}
484 		*next = 0;
485 		set_value(rcp, rsp, rkp, buf);
486 		state = stNewLine;
487 		rkp = NULL;
488 	} 	/* while */
489 	if (c == EOF && state == stGetValue) {
490 		*next = 0;
491 		set_value(rcp, rsp, rkp, buf);
492 	}
493 }
494 
495 int
496 rc_getstringptr(struct rcfile *rcp, const char *section, const char *key,
497 	char **dest)
498 {
499 	struct rcsection *rsp;
500 	struct rckey *rkp;
501 	int err;
502 
503 	mutex_lock(&rcfile_mutex);
504 
505 	*dest = NULL;
506 	rsp = rc_findsect(rcp, section);
507 	if (!rsp) {
508 		err = ENOENT;
509 		goto out;
510 	}
511 	rkp = rc_sect_findkey(rsp, key);
512 	if (!rkp) {
513 		err = ENOENT;
514 		goto out;
515 	}
516 	*dest = rkp->rk_value;
517 	err = 0;
518 
519 out:
520 	mutex_unlock(&rcfile_mutex);
521 	return (err);
522 }
523 
524 int
525 rc_getstring(struct rcfile *rcp, const char *section, const char *key,
526 	size_t maxlen, char *dest)
527 {
528 	char *value;
529 	int error;
530 
531 	error = rc_getstringptr(rcp, section, key, &value);
532 	if (error)
533 		return (error);
534 	if (strlen(value) >= maxlen) {
535 		fprintf(stderr, dgettext(TEXT_DOMAIN,
536 		    "line too long for key '%s' in section '%s', max = %d\n"),
537 		    key, section, maxlen);
538 		return (EINVAL);
539 	}
540 	strcpy(dest, value);
541 	return (0);
542 }
543 
544 int
545 rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value)
546 {
547 	struct rcsection *rsp;
548 	struct rckey *rkp;
549 	int err;
550 
551 	mutex_lock(&rcfile_mutex);
552 
553 	rsp = rc_findsect(rcp, section);
554 	if (!rsp) {
555 		err = ENOENT;
556 		goto out;
557 	}
558 	rkp = rc_sect_findkey(rsp, key);
559 	if (!rkp) {
560 		err = ENOENT;
561 		goto out;
562 	}
563 	errno = 0;
564 	*value = strtol(rkp->rk_value, NULL, 0);
565 	if ((err = errno) != 0) {
566 		fprintf(stderr, dgettext(TEXT_DOMAIN,
567 		    "invalid int value '%s' for key '%s' in section '%s'\n"),
568 		    rkp->rk_value, key, section);
569 	}
570 
571 out:
572 	mutex_unlock(&rcfile_mutex);
573 	return (err);
574 }
575 
576 /*
577  * 1,yes,true
578  * 0,no,false
579  */
580 int
581 rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value)
582 {
583 	struct rcsection *rsp;
584 	struct rckey *rkp;
585 	char *p;
586 	int err;
587 
588 	mutex_lock(&rcfile_mutex);
589 
590 	rsp = rc_findsect(rcp, section);
591 	if (!rsp) {
592 		err = ENOENT;
593 		goto out;
594 	}
595 	rkp = rc_sect_findkey(rsp, key);
596 	if (!rkp) {
597 		err = ENOENT;
598 		goto out;
599 	}
600 	p = rkp->rk_value;
601 	while (*p && isspace(*p)) p++;
602 	if (*p == '0' ||
603 	    strcasecmp(p, "no") == 0 ||
604 	    strcasecmp(p, "false") == 0) {
605 		*value = 0;
606 		err = 0;
607 		goto out;
608 	}
609 	if (*p == '1' ||
610 	    strcasecmp(p, "yes") == 0 ||
611 	    strcasecmp(p, "true") == 0) {
612 		*value = 1;
613 		err = 0;
614 		goto out;
615 	}
616 	fprintf(stderr, dgettext(TEXT_DOMAIN,
617 	    "invalid boolean value '%s' for key '%s' in section '%s' \n"),
618 	    p, key, section);
619 	err = EINVAL;
620 
621 out:
622 	mutex_unlock(&rcfile_mutex);
623 	return (err);
624 }
625 
626 #ifdef DEBUG
627 void
628 dump_props(char *where)
629 {
630 	struct rcsection *rsp = NULL;
631 	struct rckey *rkp = NULL;
632 
633 	fprintf(stderr, "Settings %s\n", where);
634 	SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) {
635 		fprintf(stderr, "section=%s\n", rsp->rs_name);
636 		fflush(stderr);
637 
638 		SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) {
639 			fprintf(stderr, "  key=%s, value=%s\n",
640 			    rkp->rk_name, rkp->rk_value);
641 			fflush(stderr);
642 		}
643 	}
644 }
645 #endif
646 
647 /*
648  * first parse "sharectl get smbfs, then $HOME/.nsmbrc
649  * This is called by library consumers (commands)
650  */
651 int
652 smb_open_rcfile(char *home)
653 {
654 	char *fn;
655 	int len, error = 0;
656 
657 	mutex_lock(&rcfile_mutex);
658 
659 	smb_rc = NULL;
660 #if 0	/* before SMF */
661 	fn = SMB_CFG_FILE;
662 	error = rc_open(fn, &smb_rc);
663 #else
664 	fn = SMBFS_SHARECTL_CMD;
665 	error = rc_popen_cmd(fn, &smb_rc);
666 #endif
667 	if (error != 0 && error != ENOENT) {
668 		/* Error from fopen. strerror is OK. */
669 		fprintf(stderr, dgettext(TEXT_DOMAIN,
670 		    "Can't open %s: %s\n"), fn, strerror(errno));
671 	}
672 #ifdef DEBUG
673 	if (smb_debug)
674 		dump_props(fn);
675 #endif
676 
677 	if (home) {
678 		len = strlen(home) + 20;
679 		fn = malloc(len);
680 		snprintf(fn, len, "%s/.nsmbrc", home);
681 		home_nsmbrc = 1;
682 		error = rc_merge(fn, &smb_rc);
683 		if (error != 0 && error != ENOENT) {
684 			fprintf(stderr, dgettext(TEXT_DOMAIN,
685 			    "Can't open %s: %s\n"), fn, strerror(errno));
686 		}
687 		home_nsmbrc = 0;
688 #ifdef DEBUG
689 		if (smb_debug)
690 			dump_props(fn);
691 #endif
692 		free(fn);
693 	}
694 
695 	/* Mostly ignore error returns above. */
696 	if (smb_rc == NULL)
697 		error = ENOENT;
698 	else
699 		error = 0;
700 
701 	mutex_unlock(&rcfile_mutex);
702 
703 	return (error);
704 }
705 
706 /*
707  * This is called by library consumers (commands)
708  */
709 void
710 smb_close_rcfile(void)
711 {
712 	struct rcfile *rcp;
713 
714 	if ((rcp = smb_rc) != NULL) {
715 		smb_rc = NULL;
716 		rc_close(rcp);
717 	}
718 }
719