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 }