xref: /illumos-gate/usr/src/lib/libsmbfs/smb/rcfile.c (revision d3b5f56344d8bfcdd6cfb82446af0e5e55ad9ebe)
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  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
36  */
37 
38 #include <fcntl.h>
39 #include <sys/types.h>
40 #include <sys/queue.h>
41 #include <sys/stat.h>
42 
43 #include <ctype.h>
44 #include <errno.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <strings.h>
48 #include <stdlib.h>
49 #include <synch.h>
50 #include <unistd.h>
51 #include <pwd.h>
52 #include <libintl.h>
53 
54 #include <cflib.h>
55 #include "rcfile_priv.h"
56 
57 #include <assert.h>
58 
59 #if 0 /* before SMF */
60 #define	SMB_CFG_FILE	"/etc/nsmb.conf"
61 #define	OLD_SMB_CFG_FILE	"/usr/local/etc/nsmb.conf"
62 #endif
63 
64 extern int smb_debug;
65 
66 static struct rcfile *rc_cachelookup(const char *filename);
67 static struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname);
68 static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname);
69 static int		rc_freesect(struct rcfile *rcp, struct rcsection *rsp);
70 static struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *key);
71 static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name,
72     const char *value);
73 static void rc_key_free(struct rckey *p);
74 static void rc_parse(struct rcfile *rcp);
75 
76 /* lock for the variables below */
77 mutex_t rcfile_mutex = DEFAULTMUTEX;
78 
79 SLIST_HEAD(rcfile_head, rcfile);
80 static struct rcfile_head pf_head = {NULL};
81 struct rcfile *smb_rc;
82 int home_nsmbrc;
83 int insecure_nsmbrc;
84 
85 /*
86  * open rcfile and load its content, if already open - return previous handle
87  */
88 static int
89 rc_open(const char *filename, const char *mode, struct rcfile **rcfile)
90 {
91 	struct stat statbuf;
92 	struct rcfile *rcp;
93 	FILE *f;
94 
95 	assert(MUTEX_HELD(&rcfile_mutex));
96 
97 	rcp = rc_cachelookup(filename);
98 	if (rcp) {
99 		*rcfile = rcp;
100 		return (0);
101 	}
102 	f = fopen(filename, mode);
103 	if (f == NULL)
104 		return (errno);
105 	insecure_nsmbrc = 0;
106 	if (fstat(fileno(f), &statbuf) >= 0 &&
107 	    (statbuf.st_mode & 077) != 0)
108 		insecure_nsmbrc = 1;
109 	rcp = malloc(sizeof (struct rcfile));
110 	if (rcp == NULL) {
111 		fclose(f);
112 		return (ENOMEM);
113 	}
114 	bzero(rcp, sizeof (struct rcfile));
115 	rcp->rf_name = strdup(filename);
116 	rcp->rf_f = f;
117 	SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
118 	rc_parse(rcp);
119 	*rcfile = rcp;
120 	return (0);
121 }
122 
123 static int
124 rc_merge(const char *filename, struct rcfile **rcfile)
125 {
126 	struct stat statbuf;
127 	struct rcfile *rcp = *rcfile;
128 	FILE *f, *t;
129 
130 	assert(MUTEX_HELD(&rcfile_mutex));
131 
132 	insecure_nsmbrc = 0;
133 	if (rcp == NULL) {
134 		return (rc_open(filename, "r", rcfile));
135 	}
136 	f = fopen(filename, "r");
137 	if (f == NULL)
138 		return (errno);
139 	insecure_nsmbrc = 0;
140 	if (fstat(fileno(f), &statbuf) >= 0 &&
141 	    (statbuf.st_mode & 077) != 0)
142 		insecure_nsmbrc = 1;
143 	t = rcp->rf_f;
144 	rcp->rf_f = f;
145 	rc_parse(rcp);
146 	rcp->rf_f = t;
147 	fclose(f);
148 	return (0);
149 }
150 
151 /*
152  * Like rc_open, but creates a temporary file and
153  * reads the sharectl settings into it.
154  * The file is deleted when we close it.
155  */
156 static int
157 rc_open_sharectl(struct rcfile **rcfile)
158 {
159 	static char template[24] = "/tmp/smbfsXXXXXX";
160 	struct rcfile *rcp = NULL;
161 	FILE *fp = NULL;
162 	int err;
163 	int fd = -1;
164 
165 	assert(MUTEX_HELD(&rcfile_mutex));
166 
167 	fd = mkstemp(template);
168 	if (fd < 0) {
169 		err = errno;
170 		goto errout;
171 	}
172 
173 	fp = fdopen(fd, "w+");
174 	if (fp == NULL) {
175 		err = errno;
176 		close(fd);
177 		goto errout;
178 	}
179 	fd = -1; /* The fp owns this fd now. */
180 
181 	/*
182 	 * Get smbfs sharectl settings into the file.
183 	 */
184 	if ((err = rc_scf_get_sharectl(fp)) != 0)
185 		goto errout;
186 
187 	rcp = malloc(sizeof (struct rcfile));
188 	if (rcp == NULL) {
189 		err = ENOMEM;
190 		goto errout;
191 	}
192 	bzero(rcp, sizeof (struct rcfile));
193 
194 	rcp->rf_name = strdup(template);
195 	if (rcp->rf_name == NULL) {
196 		err = ENOMEM;
197 		goto errout;
198 	}
199 	rcp->rf_f = fp;
200 	rcp->rf_flags = RCFILE_DELETE_ON_CLOSE;
201 
202 	SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
203 	insecure_nsmbrc = 0;
204 	rc_parse(rcp);
205 	*rcfile = rcp;
206 	/* fclose(f) in rc_close */
207 	return (0);
208 
209 errout:
210 	if (rcp != NULL)
211 		free(rcp);
212 	if (fp != NULL) {
213 		fclose(fp);
214 		fd = -1;
215 	}
216 	if (fd != -1)
217 		close(fd);
218 
219 	return (err);
220 }
221 
222 
223 static int
224 rc_close(struct rcfile *rcp)
225 {
226 	struct rcsection *p, *n;
227 
228 	mutex_lock(&rcfile_mutex);
229 
230 	fclose(rcp->rf_f);
231 	if (rcp->rf_flags & RCFILE_DELETE_ON_CLOSE)
232 		(void) unlink(rcp->rf_name);
233 
234 	for (p = SLIST_FIRST(&rcp->rf_sect); p; ) {
235 		n = p;
236 		p = SLIST_NEXT(p, rs_next);
237 		rc_freesect(rcp, n);
238 	}
239 	free(rcp->rf_name);
240 	SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next);
241 	free(rcp);
242 
243 	mutex_unlock(&rcfile_mutex);
244 	return (0);
245 }
246 
247 static struct rcfile *
248 rc_cachelookup(const char *filename)
249 {
250 	struct rcfile *p;
251 
252 	assert(MUTEX_HELD(&rcfile_mutex));
253 
254 	SLIST_FOREACH(p, &pf_head, rf_next)
255 		if (strcmp(filename, p->rf_name) == 0)
256 			return (p);
257 	return (0);
258 }
259 
260 static struct rcsection *
261 rc_findsect(struct rcfile *rcp, const char *sectname)
262 {
263 	struct rcsection *p;
264 
265 	assert(MUTEX_HELD(&rcfile_mutex));
266 
267 	SLIST_FOREACH(p, &rcp->rf_sect, rs_next)
268 		if (strcasecmp(p->rs_name, sectname) == 0)
269 			return (p);
270 	return (NULL);
271 }
272 
273 static struct rcsection *
274 rc_addsect(struct rcfile *rcp, const char *sectname)
275 {
276 	struct rcsection *p;
277 
278 	assert(MUTEX_HELD(&rcfile_mutex));
279 
280 	p = rc_findsect(rcp, sectname);
281 	if (p)
282 		return (p);
283 	p = malloc(sizeof (*p));
284 	if (!p)
285 		return (NULL);
286 	p->rs_name = strdup(sectname);
287 	SLIST_INIT(&p->rs_keys);
288 	SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next);
289 	return (p);
290 }
291 
292 static int
293 rc_freesect(struct rcfile *rcp, struct rcsection *rsp)
294 {
295 	struct rckey *p, *n;
296 
297 	assert(MUTEX_HELD(&rcfile_mutex));
298 
299 	SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next);
300 	for (p = SLIST_FIRST(&rsp->rs_keys); p; ) {
301 		n = p;
302 		p = SLIST_NEXT(p, rk_next);
303 		rc_key_free(n);
304 	}
305 	free(rsp->rs_name);
306 	free(rsp);
307 	return (0);
308 }
309 
310 static struct rckey *
311 rc_sect_findkey(struct rcsection *rsp, const char *keyname)
312 {
313 	struct rckey *p;
314 
315 	assert(MUTEX_HELD(&rcfile_mutex));
316 
317 	SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
318 		if (strcmp(p->rk_name, keyname) == 0)
319 			return (p);
320 	return (NULL);
321 }
322 
323 static struct rckey *
324 rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value)
325 {
326 	struct rckey *p;
327 
328 	assert(MUTEX_HELD(&rcfile_mutex));
329 
330 	p = rc_sect_findkey(rsp, name);
331 	if (!p) {
332 		p = malloc(sizeof (*p));
333 		if (!p)
334 			return (NULL);
335 		SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next);
336 		p->rk_name = strdup(name);
337 		p->rk_value = value ? strdup(value) : strdup("");
338 	}
339 	return (p);
340 }
341 
342 #if 0
343 void
344 rc_sect_delkey(struct rcsection *rsp, struct rckey *p)
345 {
346 
347 	SLIST_REMOVE(&rsp->rs_keys, p, rckey, rk_next);
348 	rc_key_free(p);
349 }
350 #endif
351 
352 static void
353 rc_key_free(struct rckey *p)
354 {
355 	free(p->rk_value);
356 	free(p->rk_name);
357 	free(p);
358 }
359 
360 
361 static char *minauth_values[] = {
362 	"none",
363 	"lm",
364 	"ntlm",
365 	"ntlmv2",
366 	"kerberos",
367 	NULL
368 };
369 
370 static int
371 eval_minauth(char *auth)
372 {
373 	int i;
374 
375 	for (i = 0; minauth_values[i]; i++)
376 		if (strcmp(auth, minauth_values[i]) == 0)
377 			return (i);
378 	return (-1);
379 }
380 
381 /*
382  * Ensure that "minauth" is set to the highest level
383  */
384 /*ARGSUSED*/
385 static void
386 set_value(struct rcfile *rcp, struct rcsection *rsp, struct rckey *rkp,
387     char *ptr)
388 {
389 	int now, new;
390 #ifdef DEBUG
391 	char *from = "SMF";
392 
393 	if (home_nsmbrc != 0)
394 		from = "user file";
395 #endif
396 
397 	if (strcmp(rkp->rk_name, "minauth") == 0) {
398 		now = eval_minauth(rkp->rk_value);
399 		new = eval_minauth(ptr);
400 		if (new <= now) {
401 #ifdef DEBUG
402 			if (smb_debug)
403 				fprintf(stderr,
404 				    "set_value: rejecting %s=%s"
405 				    " in %s from %s\n",
406 				    rkp->rk_name, ptr,
407 				    rsp->rs_name, from);
408 #endif
409 			return;
410 		}
411 	}
412 #ifdef DEBUG
413 	if (smb_debug)
414 		fprintf(stderr,
415 		    "set_value: applying %s=%s in %s from %s\n",
416 		    rkp->rk_name, ptr, rsp->rs_name, from);
417 #endif
418 	rkp->rk_value = strdup(ptr);
419 }
420 
421 
422 /* states in rc_parse */
423 enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue};
424 
425 static void
426 rc_parse(struct rcfile *rcp)
427 {
428 	FILE *f = rcp->rf_f;
429 	int state = stNewLine, c;
430 	struct rcsection *rsp = NULL;
431 	struct rckey *rkp = NULL;
432 	char buf[2048];
433 	char *next = buf, *last = &buf[sizeof (buf)-1];
434 
435 	assert(MUTEX_HELD(&rcfile_mutex));
436 
437 	while ((c = getc(f)) != EOF) {
438 		if (c == '\r')
439 			continue;
440 		if (state == stNewLine) {
441 			next = buf;
442 			if (isspace(c))
443 				continue;	/* skip leading junk */
444 			if (c == '[') {
445 				state = stHeader;
446 				rsp = NULL;
447 				continue;
448 			}
449 			if (c == '#' || c == ';') {
450 				state = stSkipToEOL;
451 			} else {		/* something meaningfull */
452 				state = stGetKey;
453 			}
454 		}
455 		/* ignore long lines */
456 		if (state == stSkipToEOL || next == last) {
457 			if (c == '\n') {
458 				state = stNewLine;
459 				next = buf;
460 			}
461 			continue;
462 		}
463 		if (state == stHeader) {
464 			if (c == ']') {
465 				*next = 0;
466 				next = buf;
467 				rsp = rc_addsect(rcp, buf);
468 				state = stSkipToEOL;
469 			} else
470 				*next++ = c;
471 			continue;
472 		}
473 		if (state == stGetKey) {
474 			/* side effect: 'key name=' */
475 			if (c == ' ' || c == '\t')
476 				continue;	/* become 'keyname=' */
477 			if (c == '\n') {	/* silently ignore ... */
478 				state = stNewLine;
479 				continue;
480 			}
481 			if (c != '=') {
482 				*next++ = c;
483 				continue;
484 			}
485 			*next = 0;
486 			if (rsp == NULL) {
487 				fprintf(stderr, dgettext(TEXT_DOMAIN,
488 				    "Key '%s' defined before section\n"), buf);
489 				state = stSkipToEOL;
490 				continue;
491 			}
492 			if (home_nsmbrc != 0 && (
493 			    strcmp(buf, "nbns") == 0 ||
494 			    strcmp(buf, "nbns_enable") == 0 ||
495 			    strcmp(buf, "nbns_broadcast") == 0 ||
496 			    strcmp(buf, "signing") == 0)) {
497 				fprintf(stderr, dgettext(TEXT_DOMAIN,
498 				    "option %s may not be set "
499 				    "in user .nsmbrc file\n"), buf);
500 				next = buf;
501 				state = stNewLine;
502 				continue;
503 			}
504 			if (insecure_nsmbrc != 0 &&
505 			    strcmp(buf, "password") == 0) {
506 				fprintf(stderr, dgettext(TEXT_DOMAIN,
507 				    "Warning: .nsmbrc file not secure, "
508 				    "ignoring passwords\n"));
509 				next = buf;
510 				state = stNewLine;
511 				continue;
512 			}
513 			rkp = rc_sect_addkey(rsp, buf, NULL);
514 			next = buf;
515 			state = stGetValue;
516 			continue;
517 		}
518 		/* only stGetValue left */
519 		if (state != stGetValue) {
520 			fprintf(stderr, dgettext(TEXT_DOMAIN,
521 			    "Well, I can't parse file '%s'\n"), rcp->rf_name);
522 			state = stSkipToEOL;
523 		}
524 		if (c != '\n') {
525 			*next++ = c;
526 			continue;
527 		}
528 		*next = 0;
529 		set_value(rcp, rsp, rkp, buf);
530 		state = stNewLine;
531 		rkp = NULL;
532 	}	/* while */
533 	if (c == EOF && state == stGetValue) {
534 		*next = 0;
535 		set_value(rcp, rsp, rkp, buf);
536 	}
537 }
538 
539 int
540 rc_getstringptr(struct rcfile *rcp, const char *section, const char *key,
541 	char **dest)
542 {
543 	struct rcsection *rsp;
544 	struct rckey *rkp;
545 	int err;
546 
547 	mutex_lock(&rcfile_mutex);
548 
549 	*dest = NULL;
550 	rsp = rc_findsect(rcp, section);
551 	if (!rsp) {
552 		err = ENOENT;
553 		goto out;
554 	}
555 	rkp = rc_sect_findkey(rsp, key);
556 	if (!rkp) {
557 		err = ENOENT;
558 		goto out;
559 	}
560 	*dest = rkp->rk_value;
561 	err = 0;
562 
563 out:
564 	mutex_unlock(&rcfile_mutex);
565 	return (err);
566 }
567 
568 int
569 rc_getstring(struct rcfile *rcp, const char *section, const char *key,
570 	size_t maxlen, char *dest)
571 {
572 	char *value;
573 	int error;
574 
575 	error = rc_getstringptr(rcp, section, key, &value);
576 	if (error)
577 		return (error);
578 	if (strlen(value) >= maxlen) {
579 		fprintf(stderr, dgettext(TEXT_DOMAIN,
580 		    "line too long for key '%s' in section '%s', max = %d\n"),
581 		    key, section, maxlen);
582 		return (EINVAL);
583 	}
584 	strcpy(dest, value);
585 	return (0);
586 }
587 
588 int
589 rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value)
590 {
591 	struct rcsection *rsp;
592 	struct rckey *rkp;
593 	int err;
594 
595 	mutex_lock(&rcfile_mutex);
596 
597 	rsp = rc_findsect(rcp, section);
598 	if (!rsp) {
599 		err = ENOENT;
600 		goto out;
601 	}
602 	rkp = rc_sect_findkey(rsp, key);
603 	if (!rkp) {
604 		err = ENOENT;
605 		goto out;
606 	}
607 	errno = 0;
608 	*value = strtol(rkp->rk_value, NULL, 0);
609 	if ((err = errno) != 0) {
610 		fprintf(stderr, dgettext(TEXT_DOMAIN,
611 		    "invalid int value '%s' for key '%s' in section '%s'\n"),
612 		    rkp->rk_value, key, section);
613 	}
614 
615 out:
616 	mutex_unlock(&rcfile_mutex);
617 	return (err);
618 }
619 
620 /*
621  * 1,yes,true
622  * 0,no,false
623  */
624 int
625 rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value)
626 {
627 	struct rcsection *rsp;
628 	struct rckey *rkp;
629 	char *p;
630 	int err;
631 
632 	mutex_lock(&rcfile_mutex);
633 
634 	rsp = rc_findsect(rcp, section);
635 	if (!rsp) {
636 		err = ENOENT;
637 		goto out;
638 	}
639 	rkp = rc_sect_findkey(rsp, key);
640 	if (!rkp) {
641 		err = ENOENT;
642 		goto out;
643 	}
644 	p = rkp->rk_value;
645 	while (*p && isspace(*p)) p++;
646 	if (*p == '0' ||
647 	    strcasecmp(p, "no") == 0 ||
648 	    strcasecmp(p, "false") == 0) {
649 		*value = 0;
650 		err = 0;
651 		goto out;
652 	}
653 	if (*p == '1' ||
654 	    strcasecmp(p, "yes") == 0 ||
655 	    strcasecmp(p, "true") == 0) {
656 		*value = 1;
657 		err = 0;
658 		goto out;
659 	}
660 	fprintf(stderr, dgettext(TEXT_DOMAIN,
661 	    "invalid boolean value '%s' for key '%s' in section '%s' \n"),
662 	    p, key, section);
663 	err = EINVAL;
664 
665 out:
666 	mutex_unlock(&rcfile_mutex);
667 	return (err);
668 }
669 
670 #ifdef DEBUG
671 void
672 dump_props(char *where)
673 {
674 	struct rcsection *rsp = NULL;
675 	struct rckey *rkp = NULL;
676 
677 	fprintf(stderr, "Settings %s\n", where);
678 	SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) {
679 		fprintf(stderr, "section=%s\n", rsp->rs_name);
680 		fflush(stderr);
681 
682 		SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) {
683 			fprintf(stderr, "  key=%s, value=%s\n",
684 			    rkp->rk_name, rkp->rk_value);
685 			fflush(stderr);
686 		}
687 	}
688 }
689 #endif
690 
691 /*
692  * first parse "sharectl get smbfs, then $HOME/.nsmbrc
693  * This is called by library consumers (commands)
694  */
695 int
696 smb_open_rcfile(char *home)
697 {
698 	char *fn;
699 	int len, error = 0;
700 
701 	mutex_lock(&rcfile_mutex);
702 
703 	smb_rc = NULL;
704 #if 0	/* before SMF */
705 	fn = SMB_CFG_FILE;
706 	error = rc_open(fn, &smb_rc);
707 #else
708 	fn = "(sharectl get smbfs)";
709 	error = rc_open_sharectl(&smb_rc);
710 #endif
711 	if (error != 0 && error != ENOENT) {
712 		/* Error from fopen. strerror is OK. */
713 		fprintf(stderr, dgettext(TEXT_DOMAIN,
714 		    "Can't open %s: %s\n"), fn, strerror(errno));
715 	}
716 #ifdef DEBUG
717 	if (smb_debug)
718 		dump_props(fn);
719 #endif
720 
721 	if (home) {
722 		len = strlen(home) + 20;
723 		fn = malloc(len);
724 		snprintf(fn, len, "%s/.nsmbrc", home);
725 		home_nsmbrc = 1;
726 		error = rc_merge(fn, &smb_rc);
727 		if (error != 0 && error != ENOENT) {
728 			fprintf(stderr, dgettext(TEXT_DOMAIN,
729 			    "Can't open %s: %s\n"), fn, strerror(errno));
730 		}
731 		home_nsmbrc = 0;
732 #ifdef DEBUG
733 		if (smb_debug)
734 			dump_props(fn);
735 #endif
736 		free(fn);
737 	}
738 
739 	/* Mostly ignore error returns above. */
740 	if (smb_rc == NULL)
741 		error = ENOENT;
742 	else
743 		error = 0;
744 
745 	mutex_unlock(&rcfile_mutex);
746 
747 	return (error);
748 }
749 
750 /*
751  * This is called by library consumers (commands)
752  */
753 void
754 smb_close_rcfile(void)
755 {
756 	struct rcfile *rcp;
757 
758 	if ((rcp = smb_rc) != NULL) {
759 		smb_rc = NULL;
760 		rc_close(rcp);
761 	}
762 }
763