xref: /illumos-gate/usr/src/lib/libsmbfs/smb/rcfile.c (revision 698f4ab6008be205f4362675967638572eef4f21)
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 				fprintf(stderr, dgettext(TEXT_DOMAIN,
497 				    "option %s may not be set "
498 				    "in user .nsmbrc file\n"), buf);
499 				next = buf;
500 				state = stNewLine;
501 				continue;
502 			}
503 			if (insecure_nsmbrc != 0 &&
504 			    strcmp(buf, "password") == 0) {
505 				fprintf(stderr, dgettext(TEXT_DOMAIN,
506 				    "Warning: .nsmbrc file not secure, "
507 				    "ignoring passwords\n"));
508 				next = buf;
509 				state = stNewLine;
510 				continue;
511 			}
512 			rkp = rc_sect_addkey(rsp, buf, NULL);
513 			next = buf;
514 			state = stGetValue;
515 			continue;
516 		}
517 		/* only stGetValue left */
518 		if (state != stGetValue) {
519 			fprintf(stderr, dgettext(TEXT_DOMAIN,
520 			    "Well, I can't parse file '%s'\n"), rcp->rf_name);
521 			state = stSkipToEOL;
522 		}
523 		if (c != '\n') {
524 			*next++ = c;
525 			continue;
526 		}
527 		*next = 0;
528 		set_value(rcp, rsp, rkp, buf);
529 		state = stNewLine;
530 		rkp = NULL;
531 	}	/* while */
532 	if (c == EOF && state == stGetValue) {
533 		*next = 0;
534 		set_value(rcp, rsp, rkp, buf);
535 	}
536 }
537 
538 int
539 rc_getstringptr(struct rcfile *rcp, const char *section, const char *key,
540 	char **dest)
541 {
542 	struct rcsection *rsp;
543 	struct rckey *rkp;
544 	int err;
545 
546 	mutex_lock(&rcfile_mutex);
547 
548 	*dest = NULL;
549 	rsp = rc_findsect(rcp, section);
550 	if (!rsp) {
551 		err = ENOENT;
552 		goto out;
553 	}
554 	rkp = rc_sect_findkey(rsp, key);
555 	if (!rkp) {
556 		err = ENOENT;
557 		goto out;
558 	}
559 	*dest = rkp->rk_value;
560 	err = 0;
561 
562 out:
563 	mutex_unlock(&rcfile_mutex);
564 	return (err);
565 }
566 
567 int
568 rc_getstring(struct rcfile *rcp, const char *section, const char *key,
569 	size_t maxlen, char *dest)
570 {
571 	char *value;
572 	int error;
573 
574 	error = rc_getstringptr(rcp, section, key, &value);
575 	if (error)
576 		return (error);
577 	if (strlen(value) >= maxlen) {
578 		fprintf(stderr, dgettext(TEXT_DOMAIN,
579 		    "line too long for key '%s' in section '%s', max = %d\n"),
580 		    key, section, maxlen);
581 		return (EINVAL);
582 	}
583 	strcpy(dest, value);
584 	return (0);
585 }
586 
587 int
588 rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value)
589 {
590 	struct rcsection *rsp;
591 	struct rckey *rkp;
592 	int err;
593 
594 	mutex_lock(&rcfile_mutex);
595 
596 	rsp = rc_findsect(rcp, section);
597 	if (!rsp) {
598 		err = ENOENT;
599 		goto out;
600 	}
601 	rkp = rc_sect_findkey(rsp, key);
602 	if (!rkp) {
603 		err = ENOENT;
604 		goto out;
605 	}
606 	errno = 0;
607 	*value = strtol(rkp->rk_value, NULL, 0);
608 	if ((err = errno) != 0) {
609 		fprintf(stderr, dgettext(TEXT_DOMAIN,
610 		    "invalid int value '%s' for key '%s' in section '%s'\n"),
611 		    rkp->rk_value, key, section);
612 	}
613 
614 out:
615 	mutex_unlock(&rcfile_mutex);
616 	return (err);
617 }
618 
619 /*
620  * 1,yes,true
621  * 0,no,false
622  */
623 int
624 rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value)
625 {
626 	struct rcsection *rsp;
627 	struct rckey *rkp;
628 	char *p;
629 	int err;
630 
631 	mutex_lock(&rcfile_mutex);
632 
633 	rsp = rc_findsect(rcp, section);
634 	if (!rsp) {
635 		err = ENOENT;
636 		goto out;
637 	}
638 	rkp = rc_sect_findkey(rsp, key);
639 	if (!rkp) {
640 		err = ENOENT;
641 		goto out;
642 	}
643 	p = rkp->rk_value;
644 	while (*p && isspace(*p)) p++;
645 	if (*p == '0' ||
646 	    strcasecmp(p, "no") == 0 ||
647 	    strcasecmp(p, "false") == 0) {
648 		*value = 0;
649 		err = 0;
650 		goto out;
651 	}
652 	if (*p == '1' ||
653 	    strcasecmp(p, "yes") == 0 ||
654 	    strcasecmp(p, "true") == 0) {
655 		*value = 1;
656 		err = 0;
657 		goto out;
658 	}
659 	fprintf(stderr, dgettext(TEXT_DOMAIN,
660 	    "invalid boolean value '%s' for key '%s' in section '%s' \n"),
661 	    p, key, section);
662 	err = EINVAL;
663 
664 out:
665 	mutex_unlock(&rcfile_mutex);
666 	return (err);
667 }
668 
669 #ifdef DEBUG
670 void
671 dump_props(char *where)
672 {
673 	struct rcsection *rsp = NULL;
674 	struct rckey *rkp = NULL;
675 
676 	fprintf(stderr, "Settings %s\n", where);
677 	SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) {
678 		fprintf(stderr, "section=%s\n", rsp->rs_name);
679 		fflush(stderr);
680 
681 		SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) {
682 			fprintf(stderr, "  key=%s, value=%s\n",
683 			    rkp->rk_name, rkp->rk_value);
684 			fflush(stderr);
685 		}
686 	}
687 }
688 #endif
689 
690 /*
691  * first parse "sharectl get smbfs, then $HOME/.nsmbrc
692  * This is called by library consumers (commands)
693  */
694 int
695 smb_open_rcfile(char *home)
696 {
697 	char *fn;
698 	int len, error = 0;
699 
700 	mutex_lock(&rcfile_mutex);
701 
702 	smb_rc = NULL;
703 #if 0	/* before SMF */
704 	fn = SMB_CFG_FILE;
705 	error = rc_open(fn, &smb_rc);
706 #else
707 	fn = "(sharectl get smbfs)";
708 	error = rc_open_sharectl(&smb_rc);
709 #endif
710 	if (error != 0 && error != ENOENT) {
711 		/* Error from fopen. strerror is OK. */
712 		fprintf(stderr, dgettext(TEXT_DOMAIN,
713 		    "Can't open %s: %s\n"), fn, strerror(errno));
714 	}
715 #ifdef DEBUG
716 	if (smb_debug)
717 		dump_props(fn);
718 #endif
719 
720 	if (home) {
721 		len = strlen(home) + 20;
722 		fn = malloc(len);
723 		snprintf(fn, len, "%s/.nsmbrc", home);
724 		home_nsmbrc = 1;
725 		error = rc_merge(fn, &smb_rc);
726 		if (error != 0 && error != ENOENT) {
727 			fprintf(stderr, dgettext(TEXT_DOMAIN,
728 			    "Can't open %s: %s\n"), fn, strerror(errno));
729 		}
730 		home_nsmbrc = 0;
731 #ifdef DEBUG
732 		if (smb_debug)
733 			dump_props(fn);
734 #endif
735 		free(fn);
736 	}
737 
738 	/* Mostly ignore error returns above. */
739 	if (smb_rc == NULL)
740 		error = ENOENT;
741 	else
742 		error = 0;
743 
744 	mutex_unlock(&rcfile_mutex);
745 
746 	return (error);
747 }
748 
749 /*
750  * This is called by library consumers (commands)
751  */
752 void
753 smb_close_rcfile(void)
754 {
755 	struct rcfile *rcp;
756 
757 	if ((rcp = smb_rc) != NULL) {
758 		smb_rc = NULL;
759 		rc_close(rcp);
760 	}
761 }
762