ctx.c revision 613a2f6ba31e891e3d947a356daf5e563d43c1ce
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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 "spnego.h"
#include "derparse.h"
#include "private.h"
#include "ntlm.h"
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/* 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:";
/*
* 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_XXX)
printf("XXX ");
if (flags & SMBCF_SSNACTIVE)
printf("SSNACTIVE ");
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_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);
/* dump_iod_work()? */
}
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)
{
char pwbuf[NSS_BUFLEN_PASSWD];
struct passwd pw;
int error = 0;
bzero(ctx, sizeof (*ctx));
error = nb_ctx_create(&ctx->ct_nb);
if (error)
return (error);
ctx->ct_dev_fd = -1;
ctx->ct_tran_fd = -1;
ctx->ct_parsedlevel = SMBL_NONE;
ctx->ct_minlevel = SMBL_NONE;
ctx->ct_maxlevel = SMBL_PATH;
/* Fill in defaults */
ctx->ct_vopt = SMBVOPT_EXT_SEC;
ctx->ct_owner = SMBM_ANY_OWNER;
ctx->ct_authflags = SMB_AT_DEFAULT;
ctx->ct_minauth = SMB_AT_DEFAULT;
nb_ctx_setscope(ctx->ct_nb, "");
/*
* if the user name is not specified some other way,
* use the current user name (built-in default)
*/
if (getpwuid_r(getuid(), &pw, pwbuf, sizeof (pwbuf)) != NULL) {
smb_ctx_setuser(ctx, pw.pw_name, 0);
ctx->ct_home = strdup(pw.pw_name);
}
/*
* Set a built-in default domain (workgroup).
* Using the Windows/NT default for now.
*/
smb_ctx_setdomain(ctx, "WORKGROUP", 0);
return (error);
}
/*
* "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) {
opt = cf_getopt(argc, argv, STDPARAM_OPT);
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) {
close(ctx->ct_dev_fd);
ctx->ct_dev_fd = -1;
}
if (ctx->ct_tran_fd != -1) {
close(ctx->ct_tran_fd);
ctx->ct_tran_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);
if (ctx->ct_srv_OS) {
free(ctx->ct_srv_OS);
ctx->ct_srv_OS = NULL;
}
if (ctx->ct_srv_LM) {
free(ctx->ct_srv_LM);
ctx->ct_srv_LM = NULL;
}
if (ctx->ct_mackey) {
free(ctx->ct_mackey);
ctx->ct_mackey = NULL;
}
}
static int
getsubstring(const char *p, uchar_t sep, char *dest, int maxlen,
const char **next)
{
int len;
maxlen--;
for (len = 0; len < maxlen && *p != sep; p++, len++, dest++) {
if (*p == 0)
return (EINVAL);
*dest = *p;
}
*dest = 0;
*next = *p ? p + 1 : p;
return (0);
}
/*
* Parse the UNC path. Here we expect something like
* "//[workgroup;][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)
{
const char *p = unc;
char *p1, *colon;
char tmp[1024];
char tmp2[1024];
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;
if (*p++ != '/' || *p++ != '/') {
smb_error(dgettext(TEXT_DOMAIN,
"UNC should start with '//'"), 0);
error = EINVAL;
goto out;
}
p1 = tmp;
error = getsubstring(p, ';', p1, sizeof (tmp), &p);
if (!error) {
if (*p1 == 0) {
smb_error(dgettext(TEXT_DOMAIN,
"empty workgroup name"), 0);
error = EINVAL;
goto out;
}
nls_str_upper(tmp, tmp);
error = smb_ctx_setdomain(ctx, unpercent(tmp), TRUE);
if (error)
goto out;
}
colon = (char *)p;
error = getsubstring(p, '@', p1, sizeof (tmp), &p);
if (!error) {
if (ctx->ct_maxlevel < SMBL_VC) {
smb_error(dgettext(TEXT_DOMAIN,
"no user name required"), 0);
error = EINVAL;
goto out;
}
p1 = strchr(tmp, ':');
if (p1) {
colon += p1 - tmp;
*p1++ = (char)0;
error = smb_ctx_setpassword(ctx, unpercent(p1), TRUE);
if (error)
goto out;
if (p - colon > 2)
memset(colon+1, '*', p - colon - 2);
}
p1 = tmp;
if (*p1 == 0) {
smb_error(dgettext(TEXT_DOMAIN,
"empty user name"), 0);
error = EINVAL;
goto out;
}
error = smb_ctx_setuser(ctx, unpercent(tmp), TRUE);
if (error)
goto out;
ctx->ct_parsedlevel = SMBL_VC;
}
error = getsubstring(p, '/', p1, sizeof (tmp), &p);
if (error) {
error = getsubstring(p, '\0', p1, sizeof (tmp), &p);
if (error) {
smb_error(dgettext(TEXT_DOMAIN,
"no server name found"), 0);
goto out;
}
}
if (*p1 == 0) {
smb_error(dgettext(TEXT_DOMAIN, "empty server name"), 0);
error = EINVAL;
goto out;
}
/*
* It's safe to uppercase this string, which
* consists of ascii characters that should
* be uppercased, %s, and ascii characters representing
* hex digits 0-9 and A-F (already uppercased, and
* if not uppercased they need to be). However,
* it is NOT safe to uppercase after it has been
* "unpercent" converted, below!
*/
nls_str_upper(tmp2, tmp);
/*
* Save ct_fullserver without case conversion.
*/
if (strchr(tmp, '%'))
(void) unpercent(tmp);
smb_ctx_setfullserver(ctx, tmp);
if (error)
goto out;
#ifdef SMB_ST_NONE
if (sharetype == SMB_ST_NONE) {
if (next)
*next = p;
error = 0;
goto out;
}
#endif
if (*p != 0 && ctx->ct_maxlevel < SMBL_SHARE) {
smb_error(dgettext(TEXT_DOMAIN, "no share name required"), 0);
error = EINVAL;
goto out;
}
error = getsubstring(p, '/', p1, sizeof (tmp), &p);
if (error) {
error = getsubstring(p, '\0', p1, sizeof (tmp), &p);
if (error) {
smb_error(dgettext(TEXT_DOMAIN,
"unexpected end of line"), 0);
goto out;
}
}
if (*p1 == 0 && ctx->ct_minlevel >= SMBL_SHARE &&
!(ctx->ct_flags & SMBCF_BROWSEOK)) {
smb_error(dgettext(TEXT_DOMAIN, "empty share name"), 0);
error = EINVAL;
goto out;
}
if (next)
*next = p;
if (*p1 == 0) {
error = 0;
goto out;
}
error = smb_ctx_setshare(ctx, unpercent(p1), sharetype);
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);
}
/* this routine does not uppercase the server name */
int
smb_ctx_setserver(struct smb_ctx *ctx, const char *name)
{
/* don't uppercase the server 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);
/* don't uppercase the username, just copy it. */
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);
}
/*
* Never uppercase the workgroup
* name here, because it might come
* from a Windows codepage encoding.
*
* 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)
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);
}
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);
}
/*
* 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 'T':
/* timeout - ignored */
break;
case 'D': /* domain */
case 'W': /* workgroup (legacy alias) */
nls_str_upper(tmp, arg);
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
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.
* Puts a list in ct_addrinfo
*/
error = smb_ctx_getaddr(ctx);
if (error) {
smb_error(dgettext(TEXT_DOMAIN,
"can't get server address"), error);
return (error);
}
assert(ctx->ct_addrinfo != NULL);
/*
* 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') {
/*
* No user name (anonymous session).
* The minauth checks do not apply.
*/
ctx->ct_authflags = SMB_AT_ANON;
} else {
/*
* Have a user name.
* If we don't have a p/w yet,
* try the keychain.
*/
if (ctx->ct_password[0] == '\0')
(void) smb_get_keychain(ctx);
/*
* If we're doing p/w based auth,
* that means not using Kerberos.
*/
if (ctx->ct_password[0] != '\0')
ctx->ct_authflags &= ~SMB_AT_KRB5;
/*
* 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);
}
int
smb_open_driver()
{
int err, fd;
uint32_t version;
fd = open("/dev/"NSMB_NAME, O_RDWR);
if (fd < 0) {
err = errno;
smb_error(dgettext(TEXT_DOMAIN,
"failed to open driver"), err);
return (-1);
}
/*
* Check the driver version (paranoia)
* Do this BEFORE any other ioctl calls.
*/
if (ioctl(fd, SMBIOC_GETVERS, &version) < 0)
version = 0;
if (version != NSMB_VERSION) {
smb_error(dgettext(TEXT_DOMAIN,
"incorrect driver version"), 0);
close(fd);
return (-1);
}
/* This handle controls per-process resources. */
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
return (fd);
}
int
smb_ctx_gethandle(struct smb_ctx *ctx)
{
int fd;
if (ctx->ct_dev_fd != -1) {
rpc_cleanup_smbctx(ctx);
close(ctx->ct_dev_fd);
ctx->ct_dev_fd = -1;
ctx->ct_flags &= ~SMBCF_SSNACTIVE;
}
fd = smb_open_driver();
if (fd < 0)
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);
if (ctx->ct_dev_fd < 0) {
if ((err = smb_ctx_gethandle(ctx)))
return (err);
}
/*
* 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 {
/*
* 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);
}
/*
* Get the string representation of a share "use" type,
* as needed for the "service" in tree connect.
*/
static const char *
smb_use_type_str(smb_use_shtype_t stype)
{
const char *pp;
switch (stype) {
default:
case USE_WILDCARD:
pp = "?????";
break;
case USE_DISKDEV:
pp = "A:";
break;
case USE_SPOOLDEV:
pp = "LPT1:";
break;
case USE_CHARDEV:
pp = "COMM";
break;
case USE_IPC:
pp = "IPC";
break;
}
return (pp);
}
/*
* Find or create a tree connection
*/
int
smb_ctx_get_tree(struct smb_ctx *ctx)
{
smbioc_tcon_t *tcon = NULL;
const char *stype;
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. */
stype = smb_use_type_str(ctx->ct_shtype_req);
strlcpy(tcon->tc_sh.sh_type_req, stype,
sizeof (tcon->tc_sh.sh_type_req));
/*
* Todo: share passwords for share-level security.
*
* The driver does the actual TCON call.
*/
if (ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) {
err = errno;
goto out;
}
/*
* Check the returned share type
*/
DPRINT("ret. sh_type: \"%s\"", tcon->tc_sh.sh_type_ret);
if (ctx->ct_shtype_req != USE_WILDCARD &&
0 != strcmp(stype, tcon->tc_sh.sh_type_ret)) {
smb_error(dgettext(TEXT_DOMAIN,
"%s: incompatible share type"),
0, ctx->ct_origshare);
err = EINVAL;
}
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 (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_ctx_get_ssnkey(struct smb_ctx *ctx, uchar_t *key, size_t len)
{
if (len < SMBIOC_HASH_SZ)
return (EINVAL);
if (ioctl(ctx->ct_dev_fd, SMBIOC_GETSSNKEY, key) == -1)
return (errno);
return (0);
}
/*
* RC file parsing stuff
*/
struct nv {
char *name;
int value;
} 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 }
};
/*
* 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 error;
#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] */
rc_getstringptr(smb_rc, sname, "minauth", &p);
if (p) {
/*
* "minauth" was set in this section; override
* the current minimum authentication setting.
*/
struct nv *nvp;
for (nvp = minauth_table; nvp->name; nvp++)
if (strcmp(p, nvp->name) == 0)
break;
if (nvp->name)
ctx->ct_minauth = nvp->value;
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.
*/
ctx->ct_vopt &= ~SMBVOPT_SIGNING_MASK;
if (strcmp(p, "disabled") == 0) {
/* leave flags zero (expr for lint) */
(void) ctx->ct_vopt;
} else if (strcmp(p, "enabled") == 0) {
ctx->ct_vopt |=
SMBVOPT_SIGNING_ENABLED;
} else if (strcmp(p, "required") == 0) {
ctx->ct_vopt |=
SMBVOPT_SIGNING_ENABLED |
SMBVOPT_SIGNING_REQUIRED;
} 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) {
nls_str_upper(p, 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) {
nls_str_upper(p, 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 *home;
char *sname = NULL;
int sname_max;
int err = 0;
if ((home = getenv("HOME")) == NULL)
home = ctx->ct_home;
if ((err = smb_open_rcfile(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);
}