root/usr/src/lib/libsmbfs/smb/ctx.c
/*
 * Copyright (c) 2000, Boris Popov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Boris Popov.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: ctx.c,v 1.32.70.2 2005/06/02 00:55:40 lindak Exp $
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/byteorder.h>

#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <libintl.h>
#include <assert.h>
#include <nss_dbdefs.h>

#include <cflib.h>
#include <netsmb/smb_lib.h>
#include <netsmb/netbios.h>
#include <netsmb/nb_lib.h>
#include <netsmb/smb_dev.h>

#include "charsets.h"
#include "private.h"
#include "ntlm.h"

#ifndef FALSE
#define FALSE   0
#endif
#ifndef TRUE
#define TRUE    1
#endif

#define SMB_AT_DEFAULT  (SMB_AT_KRB5 | SMB_AT_NTLM2)
#define SMB_AT_MINAUTH  (SMB_AT_KRB5 | SMB_AT_NTLM2 | SMB_AT_NTLM1)

struct nv {
        char *name;
        int value;
};

/* These two may be set by commands. */
int smb_debug, smb_verbose;

/*
 * Was: STDPARAM_OPT - see smb_ctx_scan_argv, smb_ctx_opt
 */
const char smbutil_std_opts[] = "ABCD:E:I:L:M:NO:P:U:R:S:T:W:";

/*
 * Defaults for new contexts (connections to servers).
 * These are set by smbfs_set_default_...
 */
static char default_domain[SMBIOC_MAX_NAME];
static char default_user[SMBIOC_MAX_NAME];


/*
 * Give the RPC library a callback hook that will be
 * called whenever we destroy or reinit an smb_ctx_t.
 * The name rpc_cleanup_smbctx() is legacy, and was
 * originally a direct call into the RPC code.
 */
static smb_ctx_close_hook_t close_hook;
static void
rpc_cleanup_smbctx(struct smb_ctx *ctx)
{
        if (close_hook)
                (*close_hook)(ctx);
}
void
smb_ctx_set_close_hook(smb_ctx_close_hook_t hook)
{
        close_hook = hook;
}

void
dump_ctx_flags(int flags)
{
        printf(" Flags: ");
        if (flags == 0)
                printf("0");
        if (flags & SMBCF_NOPWD)
                printf("NOPWD ");
        if (flags & SMBCF_SRIGHTS)
                printf("SRIGHTS ");
        if (flags & SMBCF_LOCALE)
                printf("LOCALE ");
        if (flags & SMBCF_CMD_DOM)
                printf("CMD_DOM ");
        if (flags & SMBCF_CMD_USR)
                printf("CMD_USR ");
        if (flags & SMBCF_CMD_PW)
                printf("CMD_PW ");
        if (flags & SMBCF_RESOLVED)
                printf("RESOLVED ");
        if (flags & SMBCF_KCBAD)
                printf("KCBAD ");
        if (flags & SMBCF_KCFOUND)
                printf("KCFOUND ");
        if (flags & SMBCF_BROWSEOK)
                printf("BROWSEOK ");
        if (flags & SMBCF_AUTHREQ)
                printf("AUTHREQ ");
        if (flags & SMBCF_KCSAVE)
                printf("KCSAVE  ");
        if (flags & SMBCF_KCDOMAIN)
                printf("KCDOMAIN ");
        printf("\n");
}

void
dump_iod_ssn(smb_iod_ssn_t *is)
{
        static const char zeros[NTLM_HASH_SZ] = {0};
        struct smbioc_ossn *ssn = &is->iod_ossn;

        printf(" ct_srvname=\"%s\", ", ssn->ssn_srvname);
        dump_sockaddr(&ssn->ssn_srvaddr.sa);
        printf(" dom=\"%s\", user=\"%s\"\n",
            ssn->ssn_domain, ssn->ssn_user);
        printf(" ct_vopt=0x%x, ct_owner=%d\n",
            ssn->ssn_vopt, ssn->ssn_owner);
        printf(" ct_minver=0x%x, ct_maxver=0x%x\n",
            ssn->ssn_minver, ssn->ssn_maxver);
        printf(" ct_authflags=0x%x\n", is->iod_authflags);

        printf(" ct_nthash:");
        if (bcmp(zeros, &is->iod_nthash, NTLM_HASH_SZ))
                smb_hexdump(&is->iod_nthash, NTLM_HASH_SZ);
        else
                printf(" {0}\n");

        printf(" ct_lmhash:");
        if (bcmp(zeros, &is->iod_lmhash, NTLM_HASH_SZ))
                smb_hexdump(&is->iod_lmhash, NTLM_HASH_SZ);
        else
                printf(" {0}\n");
}

void
dump_ctx(char *where, struct smb_ctx *ctx)
{
        printf("context %s:\n", where);
        dump_ctx_flags(ctx->ct_flags);

        if (ctx->ct_locname)
                printf(" localname=\"%s\"", ctx->ct_locname);
        else
                printf(" localname=NULL");

        if (ctx->ct_fullserver)
                printf(" fullserver=\"%s\"", ctx->ct_fullserver);
        else
                printf(" fullserver=NULL");

        if (ctx->ct_srvaddr_s)
                printf(" srvaddr_s=\"%s\"\n", ctx->ct_srvaddr_s);
        else
                printf(" srvaddr_s=NULL\n");

        if (ctx->ct_addrinfo)
                dump_addrinfo(ctx->ct_addrinfo);
        else
                printf(" ct_addrinfo = NULL\n");

        dump_iod_ssn(&ctx->ct_iod_ssn);

        printf(" share_name=\"%s\", share_type=%d\n",
            ctx->ct_origshare ? ctx->ct_origshare : "",
            ctx->ct_shtype_req);

        printf(" ct_home=\"%s\"\n", ctx->ct_home);
        printf(" ct_rpath=\"%s\"\n", ctx->ct_rpath);
}

int
smb_ctx_alloc(struct smb_ctx **ctx_pp)
{
        smb_ctx_t *ctx;
        int err;

        ctx = malloc(sizeof (*ctx));
        if (ctx == NULL)
                return (ENOMEM);
        err = smb_ctx_init(ctx);
        if (err != 0) {
                free(ctx);
                return (err);
        }
        *ctx_pp = ctx;
        return (0);
}

/*
 * Initialize an smb_ctx struct (defaults)
 */
int
smb_ctx_init(struct smb_ctx *ctx)
{
        int error;

        bzero(ctx, sizeof (*ctx));

        error = nb_ctx_create(&ctx->ct_nb);
        if (error)
                return (error);

        ctx->ct_dev_fd = -1;
        ctx->ct_door_fd = -1;
        ctx->ct_parsedlevel = SMBL_NONE;
        ctx->ct_minlevel = SMBL_NONE;
        ctx->ct_maxlevel = SMBL_PATH;

        /* Fill in defaults */
        ctx->ct_vopt = SMBVOPT_SIGNING_ENABLED;
        ctx->ct_owner = SMBM_ANY_OWNER;
        ctx->ct_authflags = SMB_AT_DEFAULT;
        ctx->ct_minauth = SMB_AT_MINAUTH;
        ctx->ct_maxver = SMB2_DIALECT_MAX;

        /*
         * Default domain, user, ...
         */
        strlcpy(ctx->ct_domain, default_domain,
            sizeof (ctx->ct_domain));
        strlcpy(ctx->ct_user, default_user,
            sizeof (ctx->ct_user));

        return (0);
}

/*
 * "Scan" the command line args to find the server name,
 * user name, and share name, as needed.  We need these
 * before reading the RC files and/or sharectl values.
 *
 * The sequence for getting all the members filled in
 * has some tricky aspects.  Here's how it works:
 *
 * The search order for options is as follows:
 *   command line options
 *   values parsed from UNC path (cmd)
 *   values from RC file (per-user)
 *   values from SMF (system-wide)
 *   built-in defaults
 *
 * Normally, one would simply get all the values starting with
 * the bottom of the above list and working to the top, and
 * overwriting values as you go.  But we need an exception.
 *
 * In this function, we parse the UNC path and command line options,
 * because we need (at least) the server name when we're getting the
 * SMF and RC file values.  However, values we get from the command
 * should not be overwritten by SMF or RC file parsing, so we mark
 * values from the command as "from CMD" and the RC file parser
 * leaves in place any values so marked.  See: SMBCF_CMD_*
 *
 * The semantics of these flags are: "This value came from the
 * current command instance, not from sources that may apply to
 * multiple commands."  (Different from the old "FROMUSR" flag.)
 *
 * Note that smb_ctx_opt() is called later to handle the
 * remaining options, which should be ignored here.
 * The (magic) leading ":" in cf_getopt() makes it
 * ignore options not in the options string.
 */
int
smb_ctx_scan_argv(struct smb_ctx *ctx, int argc, char **argv,
        int minlevel, int maxlevel, int sharetype)
{
        int  ind, opt, error = 0;
        int aflg = 0, uflg = 0;
        const char *arg;

        /*
         * Parse options, if any.  Values from here too
         * are marked as "from CMD".
         */
        if (argv == NULL)
                return (0);

        ctx->ct_minlevel = minlevel;
        ctx->ct_maxlevel = maxlevel;
        ctx->ct_shtype_req = sharetype;

        cf_opt_lock();
        /* Careful: no return/goto before cf_opt_unlock! */
        while (error == 0) {
                /*
                 * Leading ':' tells this to skip unknown opts.
                 * Just get -A and -U here so we know the user
                 * for config file parsing.
                 */
                opt = cf_getopt(argc, argv, ":AU:");
                if (opt == -1)
                        break;
                arg = cf_optarg;
                /* NB: handle most in smb_ctx_opt */
                switch (opt) {
                case 'A':
                        aflg = 1;
                        error = smb_ctx_setuser(ctx, "", TRUE);
                        ctx->ct_flags |= SMBCF_NOPWD;
                        break;
                case 'U':
                        uflg = 1;
                        error = smb_ctx_setuser(ctx, arg, TRUE);
                        break;
                default:
                        DPRINT("skip opt=%c", opt);
                        break;
                }
        }
        ind = cf_optind;
        arg = argv[ind];
        cf_optind = cf_optreset = 1;
        cf_opt_unlock();

        if (error)
                return (error);

        if (aflg && uflg)  {
                printf(gettext("-A and -U flags are exclusive.\n"));
                return (EINVAL);
        }

        /*
         * Parse the UNC path.  Values from here are
         * marked as "from CMD".
         */
        for (; ind < argc; ind++) {
                arg = argv[ind];
                if (strncmp(arg, "//", 2) != 0)
                        continue;
                error = smb_ctx_parseunc(ctx, arg,
                    minlevel, maxlevel, sharetype, &arg);
                if (error)
                        return (error);
                break;
        }

        return (error);
}

void
smb_ctx_free(smb_ctx_t *ctx)
{
        smb_ctx_done(ctx);
        free(ctx);
}

void
smb_ctx_done(struct smb_ctx *ctx)
{

        rpc_cleanup_smbctx(ctx);

        if (ctx->ct_dev_fd != -1) {
                nsmb_close(ctx->ct_dev_fd);
                ctx->ct_dev_fd = -1;
        }
        if (ctx->ct_door_fd != -1) {
                close(ctx->ct_door_fd);
                ctx->ct_door_fd = -1;
        }
        if (ctx->ct_srvaddr_s) {
                free(ctx->ct_srvaddr_s);
                ctx->ct_srvaddr_s = NULL;
        }
        if (ctx->ct_nb) {
                nb_ctx_done(ctx->ct_nb);
                ctx->ct_nb = NULL;
        }
        if (ctx->ct_locname) {
                free(ctx->ct_locname);
                ctx->ct_locname = NULL;
        }
        if (ctx->ct_origshare) {
                free(ctx->ct_origshare);
                ctx->ct_origshare = NULL;
        }
        if (ctx->ct_fullserver) {
                free(ctx->ct_fullserver);
                ctx->ct_fullserver = NULL;
        }
        if (ctx->ct_addrinfo) {
                freeaddrinfo(ctx->ct_addrinfo);
                ctx->ct_addrinfo = NULL;
        }
        if (ctx->ct_home) {
                free(ctx->ct_home);
                ctx->ct_home = NULL;
        }
        if (ctx->ct_rpath) {
                free(ctx->ct_rpath);
                ctx->ct_rpath = NULL;
        }
        if (ctx->ct_ssnkey_buf) {
                free(ctx->ct_ssnkey_buf);
                ctx->ct_ssnkey_buf = NULL;
        }
}

/*
 * Parse the UNC path.  Here we expect something like
 *   "//[[domain;]user[:password]@]host[/share[/path]]"
 * See http://ietf.org/internet-drafts/draft-crhertel-smb-url-07.txt
 * Values found here are marked as "from CMD".
 */
int
smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc,
        int minlevel, int maxlevel, int sharetype,
        const char **next)
{
        char tmp[1024];
        char *host, *share, *path;
        char *dom, *usr, *pw, *p;
        int error;

        /*
         * This may be called outside of _scan_argv,
         * so make sure these get initialized.
         */
        ctx->ct_minlevel = minlevel;
        ctx->ct_maxlevel = maxlevel;
        ctx->ct_shtype_req = sharetype;
        ctx->ct_parsedlevel = SMBL_NONE;

        dom = usr = pw = host = NULL;

        /* Work on a temporary copy, fix back slashes. */
        strlcpy(tmp, unc, sizeof (tmp));
        for (p = tmp; *p; p++)
                if (*p == '\\')
                        *p = '/';

        if (tmp[0] != '/' || tmp[1] != '/') {
                smb_error(dgettext(TEXT_DOMAIN,
                    "UNC should start with '//'"), 0);
                error = EINVAL;
                goto out;
        }
        p = tmp + 2;    /* user@host... */

        /* Find the share part, if any. */
        share = strchr(p, '/');
        if (share)
                *share = '\0';
        (void) unpercent(p);    /* host component */

        /*
         * Parse the "host" stuff right to left:
         * 1: trailing "@hostname" (or whole field)
         * 2: trailing ":password"
         * 3: trailing "domain;user" (or just user)
         */
        host = strrchr(p, '@');
        if (host == NULL) {
                host = p;       /* no user@ prefix */
        } else {
                *host++ = '\0';

                /* may have [[domain;]user[:passwd]] */
                pw = strchr(p, ':');
                if (pw)
                        *pw++ = '\0';
                usr = strchr(p, ';');
                if (usr) {
                        *usr++ = '\0';
                        dom = p;
                } else
                        usr = p;
        }

        if (*host == '\0') {
                smb_error(dgettext(TEXT_DOMAIN, "empty server name"), 0);
                error = EINVAL;
                goto out;
        }
        error = smb_ctx_setfullserver(ctx, host);
        if (error)
                goto out;
        ctx->ct_parsedlevel = SMBL_VC;

        if (dom != NULL) {
                error = smb_ctx_setdomain(ctx, dom, TRUE);
                if (error)
                        goto out;
        }
        if (usr != NULL) {
                if (*usr == '\0') {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "empty user name"), 0);
                        error = EINVAL;
                        goto out;
                }
                if (ctx->ct_maxlevel < SMBL_VC) {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "no user name required"), 0);
                        error = EINVAL;
                        goto out;
                }
                error = smb_ctx_setuser(ctx, usr, TRUE);
                if (error)
                        goto out;
        }
        if (pw != NULL) {
                error = smb_ctx_setpassword(ctx, pw, TRUE);
                if (error)
                        goto out;
        }

        if (share != NULL) {
                /* restore the slash */
                *share = '/';
                p = share + 1;

                /* Find the path part, if any. */
                path = strchr(p, '/');
                if (path)
                        *path = '\0';
                (void) unpercent(p);    /* share component */

                if (*p == '\0') {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "empty share name"), 0);
                        error = EINVAL;
                        goto out;
                }
                if (ctx->ct_maxlevel < SMBL_SHARE) {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "no share name required"), 0);
                        error = EINVAL;
                        goto out;
                }

                /*
                 * Special case UNC names like:
                 *      //host/PIPE/endpoint
                 * to have share: IPC$
                 */
                if (strcasecmp(p, "PIPE") == 0) {
                        sharetype = USE_IPC;
                        p = "IPC$";
                }
                error = smb_ctx_setshare(ctx, p, sharetype);
                if (error)
                        goto out;
                ctx->ct_parsedlevel = SMBL_SHARE;

                if (path) {
                        /* restore the slash */
                        *path = '/';
                        p = path + 1;
                        (void) unpercent(p);    /* remainder */
                        free(ctx->ct_rpath);
                        ctx->ct_rpath = strdup(path);
                }
        } else if (ctx->ct_minlevel >= SMBL_SHARE) {
                smb_error(dgettext(TEXT_DOMAIN, "empty share name"), 0);
                error = EINVAL;
                goto out;
        }

        if (next)
                *next = NULL;

out:
        if (error == 0 && smb_debug > 0)
                dump_ctx("after smb_ctx_parseunc", ctx);

        return (error);
}

#ifdef KICONV_SUPPORT
int
smb_ctx_setcharset(struct smb_ctx *ctx, const char *arg)
{
        char *cp, *servercs, *localcs;
        int cslen = sizeof (ctx->ct_ssn.ioc_localcs);
        int scslen, lcslen, error;

        cp = strchr(arg, ':');
        lcslen = cp ? (cp - arg) : 0;
        if (lcslen == 0 || lcslen >= cslen) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "invalid local charset specification (%s)"), 0, arg);
                return (EINVAL);
        }
        scslen = (size_t)strlen(++cp);
        if (scslen == 0 || scslen >= cslen) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "invalid server charset specification (%s)"), 0, arg);
                return (EINVAL);
        }
        localcs = memcpy(ctx->ct_ssn.ioc_localcs, arg, lcslen);
        localcs[lcslen] = 0;
        servercs = strcpy(ctx->ct_ssn.ioc_servercs, cp);
        error = nls_setrecode(localcs, servercs);
        if (error == 0)
                return (0);
        smb_error(dgettext(TEXT_DOMAIN,
            "can't initialize iconv support (%s:%s)"),
            error, localcs, servercs);
        localcs[0] = 0;
        servercs[0] = 0;
        return (error);
}
#endif /* KICONV_SUPPORT */

int
smb_ctx_setauthflags(struct smb_ctx *ctx, int flags)
{
        ctx->ct_authflags = flags;
        return (0);
}

int
smb_ctx_setfullserver(struct smb_ctx *ctx, const char *name)
{
        char *p = strdup(name);

        if (p == NULL)
                return (ENOMEM);
        if (ctx->ct_fullserver)
                free(ctx->ct_fullserver);
        ctx->ct_fullserver = p;
        return (0);
}

int
smb_ctx_setserver(struct smb_ctx *ctx, const char *name)
{
        strlcpy(ctx->ct_srvname, name,
            sizeof (ctx->ct_srvname));
        return (0);
}

int
smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd)
{

        if (strlen(name) >= sizeof (ctx->ct_user)) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "user name '%s' too long"), 0, name);
                return (ENAMETOOLONG);
        }

        /*
         * Don't overwrite a value from the command line
         * with one from anywhere else.
         */
        if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_USR))
                return (0);

        strlcpy(ctx->ct_user, name,
            sizeof (ctx->ct_user));

        /* Mark this as "from the command line". */
        if (from_cmd)
                ctx->ct_flags |= SMBCF_CMD_USR;

        return (0);
}

/*
 * Don't overwrite a domain name from the
 * command line with one from anywhere else.
 * See smb_ctx_init() for notes about this.
 */
int
smb_ctx_setdomain(struct smb_ctx *ctx, const char *name, int from_cmd)
{

        if (strlen(name) >= sizeof (ctx->ct_domain)) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "workgroup name '%s' too long"), 0, name);
                return (ENAMETOOLONG);
        }

        /*
         * Don't overwrite a value from the command line
         * with one from anywhere else.
         */
        if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_DOM))
                return (0);

        strlcpy(ctx->ct_domain, name,
            sizeof (ctx->ct_domain));

        /* Mark this as "from the command line". */
        if (from_cmd)
                ctx->ct_flags |= SMBCF_CMD_DOM;

        return (0);
}

int
smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd, int from_cmd)
{
        int err;

        if (passwd == NULL)
                return (EINVAL);
        if (strlen(passwd) >= sizeof (ctx->ct_password)) {
                smb_error(dgettext(TEXT_DOMAIN, "password too long"), 0);
                return (ENAMETOOLONG);
        }

        /*
         * If called again after comand line parsing,
         * don't overwrite a value from the command line
         * with one from any stored config.
         */
        if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_PW))
                return (0);

        memset(ctx->ct_password, 0, sizeof (ctx->ct_password));
        if (strncmp(passwd, "$$1", 3) == 0)
                (void) smb_simpledecrypt(ctx->ct_password, passwd);
        else
                strlcpy(ctx->ct_password, passwd,
                    sizeof (ctx->ct_password));

        /*
         * Compute LM hash, NT hash.
         */
        if (ctx->ct_password[0]) {
                err = ntlm_compute_nt_hash(ctx->ct_nthash, ctx->ct_password);
                if (err != 0)
                        return (err);
                err = ntlm_compute_lm_hash(ctx->ct_lmhash, ctx->ct_password);
                if (err != 0)
                        return (err);
        }

        /* Mark this as "from the command line". */
        if (from_cmd)
                ctx->ct_flags |= SMBCF_CMD_PW;

        return (0);
}

/*
 * Use this to set NTLM auth. info (hashes)
 * when we don't have the password.
 */
int
smb_ctx_setpwhash(smb_ctx_t *ctx,
    const uchar_t *nthash, const uchar_t *lmhash)
{

        /* Need ct_password to be non-null. */
        if (ctx->ct_password[0] == '\0')
                strlcpy(ctx->ct_password, "$HASH",
                    sizeof (ctx->ct_password));

        /*
         * Compute LM hash, NT hash.
         */
        memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ);

        /* The LM hash is optional */
        if (lmhash) {
                memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ);
        }

        return (0);
}

int
smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype)
{
        if (strlen(share) >= SMBIOC_MAX_NAME) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "share name '%s' too long"), 0, share);
                return (ENAMETOOLONG);
        }
        if (ctx->ct_origshare)
                free(ctx->ct_origshare);
        if ((ctx->ct_origshare = strdup(share)) == NULL)
                return (ENOMEM);

        ctx->ct_shtype_req = stype;

        return (0);
}

int
smb_ctx_setsrvaddr(struct smb_ctx *ctx, const char *addr)
{
        if (addr == NULL || addr[0] == 0)
                return (EINVAL);
        if (ctx->ct_srvaddr_s)
                free(ctx->ct_srvaddr_s);
        if ((ctx->ct_srvaddr_s = strdup(addr)) == NULL)
                return (ENOMEM);
        return (0);
}

/*
 * API for library caller to set signing enabled, required
 * Note: if not enable, ignore require
 */
int
smb_ctx_setsigning(struct smb_ctx *ctx, int enable, int require)
{
        ctx->ct_vopt &= ~SMBVOPT_SIGNING_MASK;
        if (enable) {
                ctx->ct_vopt |= SMBVOPT_SIGNING_ENABLED;
                if (require)
                        ctx->ct_vopt |= SMBVOPT_SIGNING_REQUIRED;
        }
        return (0);
}

/*
 * Handle .nsmbrc "minver" option.
 * Must be <= maxver
 */
int
smb_ctx_setminver(struct smb_ctx *ctx, int ver)
{
        if (ver < 0 || ver > ctx->ct_maxver)
                return (EINVAL);
        ctx->ct_minver = (uint16_t)ver;
        return (0);
}

/*
 * Handle .nsmbrc "maxver" option.
 * Must be >= minver
 *
 * Any "too high" value is just clamped, so the caller
 * doesn't need to know what's the highest we support.
 */
int
smb_ctx_setmaxver(struct smb_ctx *ctx, int ver)
{
        if (ver < 1 || ver < ctx->ct_minver)
                return (EINVAL);
        if (ver > SMB2_DIALECT_MAX)
                ver = SMB2_DIALECT_MAX;
        ctx->ct_maxver = (uint16_t)ver;
        return (0);
}

static int
smb_parse_owner(char *pair, uid_t *uid, gid_t *gid)
{
        struct group gr;
        struct passwd pw;
        char buf[NSS_BUFLEN_PASSWD];
        char *cp;

        cp = strchr(pair, ':');
        if (cp) {
                *cp++ = '\0';
                if (*cp && gid) {
                        if (getgrnam_r(cp, &gr, buf, sizeof (buf)) != NULL) {
                                *gid = gr.gr_gid;
                        } else
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "Invalid group name %s, ignored"), 0, cp);
                }
        }
        if (*pair) {
                if (getpwnam_r(pair, &pw, buf, sizeof (buf)) != NULL) {
                        *uid = pw.pw_uid;
                } else
                        smb_error(dgettext(TEXT_DOMAIN,
                            "Invalid user name %s, ignored"), 0, pair);
        }

        return (0);
}

/*
 * Suport a securty options arg, i.e. -S lm,ntlm
 * for testing various type of authenticators.
 */
static struct nv
sectype_table[] = {
        { "anon",       SMB_AT_ANON },
        { "lm",         SMB_AT_LM1 },
        { "ntlm",       SMB_AT_NTLM1 },
        { "ntlm2",      SMB_AT_NTLM2 },
        { "krb5",       SMB_AT_KRB5 },
        { NULL,         0 },
};
int
smb_parse_secopts(struct smb_ctx *ctx, const char *arg)
{
        const char *sep = ":;,";
        const char *p = arg;
        struct nv *nv;
        int nlen, tlen;
        int authflags = 0;

        for (;;) {
                /* skip separators */
                tlen = strspn(p, sep);
                p += tlen;

                nlen = strcspn(p, sep);
                if (nlen == 0)
                        break;

                /* This is rarely called, so not optimized. */
                for (nv = sectype_table; nv->name; nv++) {
                        tlen = strlen(nv->name);
                        if (tlen == nlen && 0 == strncmp(p, nv->name, tlen))
                                break;
                }
                if (nv->name == NULL) {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "%s: invalid security options"), 0, p);
                        return (EINVAL);
                }
                authflags |= nv->value;
                p += nlen;
        }

        if (authflags)
                ctx->ct_authflags = authflags;

        return (0);
}

/*
 * Commands use this with getopt.  See:
 *   STDPARAM_OPT, STDPARAM_ARGS
 * Called after smb_ctx_readrc().
 */
int
smb_ctx_opt(struct smb_ctx *ctx, int opt, const char *arg)
{
        int error = 0;
        char *p, *cp;
        char tmp[1024];

        switch (opt) {
        case 'A':
        case 'U':
                /* Handled in smb_ctx_init() */
                break;
        case 'I':
                error = smb_ctx_setsrvaddr(ctx, arg);
                break;
        case 'M':
                /* share connect rights - ignored */
                ctx->ct_flags |= SMBCF_SRIGHTS;
                break;
        case 'N':
                ctx->ct_flags |= SMBCF_NOPWD;
                break;
        case 'O':
                p = strdup(arg);
                cp = strchr(p, '/');
                if (cp)
                        *cp = '\0';
                error = smb_parse_owner(cp, &ctx->ct_owner, NULL);
                free(p);
                break;
        case 'P':
/*              ctx->ct_vopt |= SMBCOPT_PERMANENT; */
                break;
        case 'R':
                /* retry count - ignored */
                break;
        case 'S':
                /* Security options (undocumented, just for tests) */
                error = smb_parse_secopts(ctx, arg);
                break;
        case 'T':
                /* timeout - ignored */
                break;
        case 'D':       /* domain */
        case 'W':       /* workgroup (legacy alias) */
                error = smb_ctx_setdomain(ctx, tmp, TRUE);
                break;
        }
        return (error);
}


/*
 * Original code injected iconv tables into the kernel.
 * Not sure if we'll need this or not...  REVISIT
 */
#ifdef KICONV_SUPPORT
static int
smb_addiconvtbl(const char *to, const char *from, const uchar_t *tbl)
{
        int error = 0;

        error = kiconv_add_xlat_table(to, from, tbl);
        if (error && error != EEXIST) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "can not setup kernel iconv table (%s:%s)"),
                    error, from, to);
                return (error);
        }
        return (error);
}
#endif  /* KICONV_SUPPORT */

/*
 * Verify context info. before connect operation(s),
 * lookup specified server and try to fill all forgotten fields.
 * Legacy name used by commands.
 */
int
smb_ctx_resolve(struct smb_ctx *ctx)
{
        struct smbioc_ossn *ssn = &ctx->ct_ssn;
        int error = 0;
#ifdef KICONV_SUPPORT
        uchar_t cstbl[256];
        uint_t i;
#endif

        if (smb_debug)
                dump_ctx("before smb_ctx_resolve", ctx);

        ctx->ct_flags &= ~SMBCF_RESOLVED;

        if (ctx->ct_fullserver == NULL) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "no server name specified"), 0);
                return (EINVAL);
        }

        if (ctx->ct_minlevel >= SMBL_SHARE &&
            ctx->ct_origshare == NULL) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "no share name specified for %s@%s"),
                    0, ssn->ssn_user, ctx->ct_fullserver);
                return (EINVAL);
        }
        error = nb_ctx_resolve(ctx->ct_nb);
        if (error)
                return (error);
#ifdef KICONV_SUPPORT
        if (ssn->ioc_localcs[0] == 0)
                strcpy(ssn->ioc_localcs, "default");    /* XXX: locale name ? */
        error = smb_addiconvtbl("tolower", ssn->ioc_localcs, nls_lower);
        if (error)
                return (error);
        error = smb_addiconvtbl("toupper", ssn->ioc_localcs, nls_upper);
        if (error)
                return (error);
        if (ssn->ioc_servercs[0] != 0) {
                for (i = 0; i < sizeof (cstbl); i++)
                        cstbl[i] = i;
                nls_mem_toext(cstbl, cstbl, sizeof (cstbl));
                error = smb_addiconvtbl(ssn->ioc_servercs, ssn->ioc_localcs,
                    cstbl);
                if (error)
                        return (error);
                for (i = 0; i < sizeof (cstbl); i++)
                        cstbl[i] = i;
                nls_mem_toloc(cstbl, cstbl, sizeof (cstbl));
                error = smb_addiconvtbl(ssn->ioc_localcs, ssn->ioc_servercs,
                    cstbl);
                if (error)
                        return (error);
        }
#endif  /* KICONV_SUPPORT */

        /*
         * Lookup the IP address and fill in ct_addrinfo.
         *
         * Note: smb_ctx_getaddr() returns a EAI_xxx
         * error value like getaddrinfo(3), but this
         * function needs to return an errno value.
         */
        error = smb_ctx_getaddr(ctx);
        if (error) {
                const char *ais = gai_strerror(error);
                smb_error(dgettext(TEXT_DOMAIN,
                    "can't resolve name\"%s\", %s"),
                    0, ctx->ct_fullserver, ais);
                return (ENODATA);
        }
        assert(ctx->ct_addrinfo != NULL);

        /*
         * Empty user name means an explicit request for
         * NULL session setup, which is a special case.
         * (No SMB signing, per [MS-SMB] 3.3.5.3)
         */
        if (ctx->ct_user[0] == '\0') {
                /* Null user should have null domain too. */
                ctx->ct_domain[0] = '\0';
                ctx->ct_authflags = SMB_AT_ANON;
                ctx->ct_vopt |= SMBVOPT_ANONYMOUS;
                ctx->ct_vopt &= ~SMBVOPT_SIGNING_REQUIRED;
        }

        /*
         * If we have a user name but no password,
         * check for a keychain entry.
         * XXX: Only for auth NTLM?
         */
        if (ctx->ct_user[0] != '\0') {
                /*
                 * Have a user name.
                 * If we don't have a p/w yet,
                 * try the keychain.
                 */
                if (ctx->ct_password[0] == '\0' &&
                    smb_get_keychain(ctx) == 0) {
                        strlcpy(ctx->ct_password, "$HASH",
                            sizeof (ctx->ct_password));
                }

                /*
                 * Mask out disallowed auth types.
                 */
                ctx->ct_authflags &= ctx->ct_minauth;
        }

        if (ctx->ct_authflags == 0) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "no valid auth. types"), 0);
                return (ENOTSUP);
        }

        ctx->ct_flags |= SMBCF_RESOLVED;
        if (smb_debug)
                dump_ctx("after smb_ctx_resolve", ctx);

        return (0);
}

/*
 * Note: The next three have NODIRECT binding so the
 * "fksmbcl" development tool can provide its own.
 */
int
smb_open_driver()
{
        int fd;

        fd = open("/dev/"NSMB_NAME, O_RDWR);
        if (fd < 0) {
                return (-1);
        }

        /* This handle controls per-process resources. */
        (void) fcntl(fd, F_SETFD, FD_CLOEXEC);

        return (fd);
}

int
nsmb_close(int fd)
{
        return (close(fd));
}

int
nsmb_ioctl(int fd, int cmd, void *arg)
{
        return (ioctl(fd, cmd, arg));
}


int
smb_ctx_gethandle(struct smb_ctx *ctx)
{
        int fd, err;
        uint32_t version;

        if (ctx->ct_dev_fd != -1) {
                rpc_cleanup_smbctx(ctx);
                nsmb_close(ctx->ct_dev_fd);
                ctx->ct_dev_fd = -1;
        }

        fd = smb_open_driver();
        if (fd < 0) {
                err = errno;
                smb_error(dgettext(TEXT_DOMAIN,
                    "failed to open driver"), err);
                return (err);
        }

        /*
         * Check the driver version (paranoia)
         */
        if (nsmb_ioctl(fd, SMBIOC_GETVERS, &version) < 0)
                version = 0;
        if (version != NSMB_VERSION) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "incorrect driver version"), 0);
                nsmb_close(fd);
                return (ENODEV);
        }

        ctx->ct_dev_fd = fd;
        return (0);
}


/*
 * Find or create a connection + logon session
 */
int
smb_ctx_get_ssn(struct smb_ctx *ctx)
{
        int err = 0;

        if ((ctx->ct_flags & SMBCF_RESOLVED) == 0)
                return (EINVAL);

        /*
         * Check whether the driver already has a VC
         * we can use.  If so, we're done!
         */
        err = smb_ctx_findvc(ctx);
        if (err == 0) {
                DPRINT("found an existing VC");
        } else {
                /*
                 * If we're authenticating (real user, not NULL session)
                 * and we don't yet have a password, return EAUTH and
                 * the caller will prompt for it and call again.
                 */
                if (ctx->ct_user[0] != '\0' &&
                    ctx->ct_password[0] == '\0')
                        return (EAUTH);

                /*
                 * This calls the IOD to create a new session.
                 */
                DPRINT("setup a new VC");
                err = smb_ctx_newvc(ctx);
                if (err != 0)
                        return (err);

                /*
                 * Call findvc again.  The new VC sould be
                 * found in the driver this time.
                 */
                err = smb_ctx_findvc(ctx);
        }

        return (err);
}

/*
 * Find or create a tree connection
 */
int
smb_ctx_get_tree(struct smb_ctx *ctx)
{
        smbioc_tcon_t *tcon = NULL;
        int cmd, err = 0;

        if (ctx->ct_dev_fd < 0 ||
            ctx->ct_origshare == NULL) {
                return (EINVAL);
        }

        cmd = SMBIOC_TREE_CONNECT;
        tcon = malloc(sizeof (*tcon));
        if (tcon == NULL)
                return (ENOMEM);
        bzero(tcon, sizeof (*tcon));
        tcon->tc_flags = SMBLK_CREATE;
        tcon->tc_opt = 0;

        /* The share name */
        strlcpy(tcon->tc_sh.sh_name, ctx->ct_origshare,
            sizeof (tcon->tc_sh.sh_name));

        /* The share "use" type. */
        tcon->tc_sh.sh_use = ctx->ct_shtype_req;

        /*
         * Todo: share passwords for share-level security.
         *
         * The driver does the actual TCON call.
         */
        if (nsmb_ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) {
                err = errno;
                goto out;
        }

        /*
         * Check the returned share type
         */
        DPRINT("ret. sh_type: \"%d\"", tcon->tc_sh.sh_type);
        if (ctx->ct_shtype_req != USE_WILDCARD &&
            ctx->ct_shtype_req != tcon->tc_sh.sh_type) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "%s: incompatible share type"),
                    0, ctx->ct_origshare);
        }

out:
        if (tcon != NULL)
                free(tcon);

        return (err);
}

/*
 * Return the hflags2 word for an smb_ctx.
 */
int
smb_ctx_flags2(struct smb_ctx *ctx)
{
        uint16_t flags2;

        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_FLAGS2, &flags2) == -1) {
                smb_error(dgettext(TEXT_DOMAIN,
                    "can't get flags2 for a session"), errno);
                return (-1);
        }
        return (flags2);
}

/*
 * Get the transport level session key.
 * Must already have an active SMB session.
 */
int
smb_fh_getssnkey(int dev_fd, uchar_t *key, size_t len)
{
        if (len < SMBIOC_HASH_SZ)
                return (EINVAL);

        if (nsmb_ioctl(dev_fd, SMBIOC_GETSSNKEY, key) == -1)
                return (errno);

        return (0);
}

/*
 * RC file parsing stuff
 */

static struct nv
minauth_table[] = {
        /* Allowed auth. types */
        { "kerberos",   SMB_AT_KRB5 },
        { "ntlmv2",     SMB_AT_KRB5|SMB_AT_NTLM2 },
        { "ntlm",       SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1 },
        { "lm",         SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1 },
        { "none",       SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1|
                        SMB_AT_ANON },
        { NULL }
};

int
smb_cf_minauth_from_str(char *str)
{
        struct nv *nvp;

        for (nvp = minauth_table; nvp->name; nvp++)
                if (strcmp(nvp->name, str) == 0)
                        return (nvp->value);
        return (-1);
}

/*
 * SMB 2.1 is the oldest SMB2 dialect implemented (we skipped SMB 2.002)
 * so if we see a_protocol value of just "2" assume they meant 2.1
 */
static struct nv
smbver_table[] = {
        { "3.02",       SMB2_DIALECT_0302 },
        { "3.0",        SMB2_DIALECT_0300 },
        { "2.1",        SMB2_DIALECT_0210 },
        { "2",          SMB2_DIALECT_0210 },
        { "1",          1 },
        { NULL,         0 }
};

int
smb_cf_version_from_str(char *str)
{
        struct nv *nvp;

        for (nvp = smbver_table; nvp->name; nvp++)
                if (strcmp(nvp->name, str) == 0)
                        return (nvp->value);
        return (-1);
}

/*
 * level values:
 * 0 - default
 * 1 - server
 * 2 - server:user
 * 3 - server:user:share
 */
static int
smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level)
{
        char *p;
        int ival;
        int error;
        int minver, maxver;

#ifdef  KICONV_SUPPORT
        if (level > 0) {
                rc_getstringptr(smb_rc, sname, "charsets", &p);
                if (p) {
                        error = smb_ctx_setcharset(ctx, p);
                        if (error)
                                smb_error(dgettext(TEXT_DOMAIN,
        "charset specification in the section '%s' ignored"),
                                    error, sname);
                }
        }
#endif

        if (level <= 1) {
                /* Section is: [default] or [server] */

                /*
                 * Handle min_protocol, max_protocol
                 * (SMB protocol versions)
                 */
                minver = -1;
                rc_getstringptr(smb_rc, sname, "min_protocol", &p);
                if (p != NULL) {
                        minver = smb_cf_version_from_str(p);
                        if (minver == -1) {
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid min_protocol value \"%s\" specified in the section %s"),
                                    0, p, sname);
                        }
                }
                maxver = -1;
                rc_getstringptr(smb_rc, sname, "max_protocol", &p);
                if (p != NULL) {
                        maxver = smb_cf_version_from_str(p);
                        if (maxver == -1) {
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid max_protocol value \"%s\" specified in the section %s"),
                                    0, p, sname);
                        }
                }

                /*
                 * If setting both min/max protocol,
                 * validate against each other
                 */
                if (minver != -1 && maxver != -1) {
                        if (minver > maxver) {
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid min/max protocol combination in the section %s"),
                                    0, sname);
                        } else {
                                ctx->ct_minver = minver;
                                ctx->ct_maxver = maxver;
                        }
                }

                /*
                 * Setting just min or max, validate against
                 * current settings
                 */
                if (minver != -1) {
                        if (minver > ctx->ct_maxver) {
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid min/max protocol combination in the section %s"),
                                    0, sname);
                        } else {
                                ctx->ct_minver = minver;
                        }
                }
                if (maxver != -1) {
                        if (maxver < ctx->ct_minver) {
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid min/max protocol combination in the section %s"),
                                    0, sname);
                        } else {
                                ctx->ct_maxver = maxver;
                        }
                }

                rc_getstringptr(smb_rc, sname, "minauth", &p);
                if (p) {
                        /*
                         * "minauth" was set in this section; override
                         * the current minimum authentication setting.
                         */
                        ival = smb_cf_minauth_from_str(p);
                        if (ival != -1) {
                                ctx->ct_minauth = ival;
                        } else {
                                /*
                                 * Unknown minimum authentication level.
                                 */
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid minimum authentication level \"%s\" specified in the section %s"),
                                    0, p, sname);
                                return (EINVAL);
                        }
                }

                rc_getstringptr(smb_rc, sname, "signing", &p);
                if (p) {
                        /*
                         * "signing" was set in this section; override
                         * the current signing settings.  Note:
                         * setsigning flags are: enable, require
                         */
                        if (strcmp(p, "disabled") == 0) {
                                (void) smb_ctx_setsigning(ctx, FALSE, FALSE);
                        } else if (strcmp(p, "enabled") == 0) {
                                (void) smb_ctx_setsigning(ctx, TRUE, FALSE);
                        } else if (strcmp(p, "required") == 0) {
                                (void) smb_ctx_setsigning(ctx, TRUE, TRUE);
                        } else {
                                /*
                                 * Unknown "signing" value.
                                 */
                                smb_error(dgettext(TEXT_DOMAIN,
"invalid signing policy \"%s\" specified in the section %s"),
                                    0, p, sname);
                                return (EINVAL);
                        }
                }

                /*
                 * Domain name.  Allow both keywords:
                 * "workgroup", "domain"
                 *
                 * Note: these are NOT marked "from CMD".
                 * See long comment at smb_ctx_init()
                 */
                rc_getstringptr(smb_rc, sname, "workgroup", &p);
                if (p) {
                        error = smb_ctx_setdomain(ctx, p, 0);
                        if (error)
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "workgroup specification in the "
                                    "section '%s' ignored"), error, sname);
                }
                rc_getstringptr(smb_rc, sname, "domain", &p);
                if (p) {
                        error = smb_ctx_setdomain(ctx, p, 0);
                        if (error)
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "domain specification in the "
                                    "section '%s' ignored"), error, sname);
                }

                rc_getstringptr(smb_rc, sname, "user", &p);
                if (p) {
                        error = smb_ctx_setuser(ctx, p, 0);
                        if (error)
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "user specification in the "
                                    "section '%s' ignored"), error, sname);
                }
        }

        if (level == 1) {
                /* Section is: [server] */
                rc_getstringptr(smb_rc, sname, "addr", &p);
                if (p) {
                        error = smb_ctx_setsrvaddr(ctx, p);
                        if (error) {
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "invalid address specified in section %s"),
                                    0, sname);
                                return (error);
                        }
                }
        }

        rc_getstringptr(smb_rc, sname, "password", &p);
        if (p) {
                error = smb_ctx_setpassword(ctx, p, 0);
                if (error)
                        smb_error(dgettext(TEXT_DOMAIN,
            "password specification in the section '%s' ignored"),
                            error, sname);
        }

        return (0);
}

/*
 * read rc file as follows:
 * 0: read [default] section
 * 1: override with [server] section
 * 2: override with [server:user] section
 * 3: override with [server:user:share] section
 * Since absence of rcfile is not fatal, silently ignore this fact.
 * smb_rc file should be closed by caller.
 */
int
smb_ctx_readrc(struct smb_ctx *ctx)
{
        char pwbuf[NSS_BUFLEN_PASSWD];
        struct passwd pw;
        char *sname = NULL;
        int sname_max;
        int err = 0;

        /*
         * If the user name is not specified some other way,
         * use the current user name.  Also save the homedir.
         * NB: ct_home=NULL is allowed, and we don't want to
         * bail out with an error for a missing ct_home.
         */
        if (getpwuid_r(getuid(), &pw, pwbuf, sizeof (pwbuf)) != NULL) {
                if (ctx->ct_user[0] == 0)
                        (void) smb_ctx_setuser(ctx, pw.pw_name, B_FALSE);
                if (ctx->ct_home == NULL)
                        ctx->ct_home = strdup(pw.pw_dir);
        }

        if ((err = smb_open_rcfile(ctx->ct_home)) != 0) {
                DPRINT("smb_open_rcfile, err=%d", err);
                /* ignore any error here */
                return (0);
        }

        sname_max = 3 * SMBIOC_MAX_NAME + 4;
        sname = malloc(sname_max);
        if (sname == NULL) {
                err = ENOMEM;
                goto done;
        }

        /*
         * default parameters (level=0)
         */
        smb_ctx_readrcsection(ctx, "default", 0);
        nb_ctx_readrcsection(smb_rc, ctx->ct_nb, "default", 0);

        /*
         * If we don't have a server name, we can't read any of the
         * [server...] sections.
         */
        if (ctx->ct_fullserver == NULL)
                goto done;
        /*
         * SERVER parameters.
         */
        smb_ctx_readrcsection(ctx, ctx->ct_fullserver, 1);

        /*
         * If we don't have a user name, we can't read any of the
         * [server:user...] sections.
         */
        if (ctx->ct_user[0] == 0)
                goto done;
        /*
         * SERVER:USER parameters
         */
        snprintf(sname, sname_max, "%s:%s",
            ctx->ct_fullserver,
            ctx->ct_user);
        smb_ctx_readrcsection(ctx, sname, 2);


        /*
         * If we don't have a share name, we can't read any of the
         * [server:user:share] sections.
         */
        if (ctx->ct_origshare == NULL)
                goto done;
        /*
         * SERVER:USER:SHARE parameters
         */
        snprintf(sname, sname_max, "%s:%s:%s",
            ctx->ct_fullserver,
            ctx->ct_user,
            ctx->ct_origshare);
        smb_ctx_readrcsection(ctx, sname, 3);

done:
        if (sname)
                free(sname);
        smb_close_rcfile();
        if (smb_debug)
                dump_ctx("after smb_ctx_readrc", ctx);
        if (err)
                DPRINT("err=%d\n", err);

        return (err);
}

void
smbfs_set_default_domain(const char *domain)
{
        strlcpy(default_domain, domain, sizeof (default_domain));
}

void
smbfs_set_default_user(const char *user)
{
        strlcpy(default_user, user, sizeof (default_user));
}