#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <sys/pathname.h>
#include <sys/priv_const.h>
#include <sys/policy.h>
#include <sys/sdt.h>
static char *smb_pathname_catia_v5tov4(smb_request_t *, char *, char *, int);
static char *smb_pathname_catia_v4tov5(smb_request_t *, char *, char *, int);
static int smb_pathname_lookup(pathname_t *, pathname_t *, int,
vnode_t **, vnode_t *, vnode_t *, smb_attr_t *attr, cred_t *);
static char *smb_pathname_strdup(smb_request_t *, const char *);
static char *smb_pathname_strcat(smb_request_t *, char *, const char *);
static void smb_pathname_preprocess(smb_request_t *, smb_pathname_t *);
static void smb_pathname_preprocess_quota(smb_request_t *, smb_pathname_t *);
static int smb_pathname_dfs_preprocess(smb_request_t *, char *, size_t);
static void smb_pathname_preprocess_adminshare(smb_request_t *,
smb_pathname_t *);
uint32_t
smb_is_executable(char *path)
{
char extension[5];
int len = strlen(path);
if ((len >= 4) && (path[len - 4] == '.')) {
(void) strcpy(extension, &path[len - 3]);
(void) smb_strupr(extension);
if (strcmp(extension, "EXE") == 0)
return (NODE_FLAGS_EXECUTABLE);
if (strcmp(extension, "COM") == 0)
return (NODE_FLAGS_EXECUTABLE);
if (strcmp(extension, "DLL") == 0)
return (NODE_FLAGS_EXECUTABLE);
if (strcmp(extension, "SYM") == 0)
return (NODE_FLAGS_EXECUTABLE);
}
return (0);
}
int
smb_pathname_reduce(
smb_request_t *sr,
cred_t *cred,
const char *path,
smb_node_t *share_root_node,
smb_node_t *cur_node,
smb_node_t **dir_node,
char *last_component)
{
smb_node_t *root_node;
pathname_t ppn = {0};
pathname_t mnt_pn = {0};
char *usepath;
int lookup_flags = FOLLOW;
int trailing_slash = 0;
int err = 0;
int len;
smb_node_t *vss_node;
smb_node_t *local_cur_node;
smb_node_t *local_root_node;
boolean_t chk_vss;
char *gmttoken;
ASSERT(dir_node);
ASSERT(last_component);
*dir_node = NULL;
*last_component = '\0';
vss_node = NULL;
gmttoken = NULL;
chk_vss = B_FALSE;
if (sr && sr->tid_tree) {
if (STYPE_ISIPC(sr->tid_tree->t_res_type))
return (EACCES);
}
if (SMB_TREE_IS_CASEINSENSITIVE(sr))
lookup_flags |= FIGNORECASE;
if (path == NULL)
return (EINVAL);
if (*path == '\0')
return (ENOENT);
usepath = kmem_alloc(SMB_MAXPATHLEN, KM_SLEEP);
len = strlcpy(usepath, path, SMB_MAXPATHLEN);
if (len >= SMB_MAXPATHLEN) {
kmem_free(usepath, SMB_MAXPATHLEN);
return (ENAMETOOLONG);
}
(void) strsubst(usepath, '\\', '/');
if (share_root_node)
root_node = share_root_node;
else
root_node = sr->sr_server->si_root_smb_node;
if (cur_node == NULL)
cur_node = root_node;
local_cur_node = cur_node;
local_root_node = root_node;
if (SMB_TREE_IS_DFSROOT(sr)) {
int is_dfs;
if (sr->session->dialect >= SMB_VERS_2_BASE)
is_dfs = sr->smb2_hdr_flags &
SMB2_FLAGS_DFS_OPERATIONS;
else
is_dfs = sr->smb_flg2 & SMB_FLAGS2_DFS;
if (is_dfs != 0) {
err = smb_pathname_dfs_preprocess(sr, usepath,
SMB_MAXPATHLEN);
if (err != 0) {
kmem_free(usepath, SMB_MAXPATHLEN);
return (err);
}
len = strlen(usepath);
}
}
if (sr != NULL) {
if (sr->session->dialect >= SMB_VERS_2_BASE) {
chk_vss = sr->arg.open.create_timewarp;
} else {
chk_vss = (sr->smb_flg2 &
SMB_FLAGS2_REPARSE_PATH) != 0;
if (chk_vss) {
gmttoken = kmem_alloc(SMB_VSS_GMT_SIZE,
KM_SLEEP);
err = smb_vss_extract_gmttoken(usepath,
gmttoken);
if (err != 0) {
kmem_free(usepath, SMB_MAXPATHLEN);
kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
return (err);
}
len = strlen(usepath);
}
}
if (chk_vss)
(void) pn_alloc(&mnt_pn);
}
if (usepath[len - 1] == '/')
trailing_slash = 1;
(void) strcanon(usepath, "/");
(void) pn_alloc_sz(&ppn, SMB_MAXPATHLEN);
if ((err = pn_set(&ppn, usepath)) != 0) {
(void) pn_free(&ppn);
kmem_free(usepath, SMB_MAXPATHLEN);
if (chk_vss)
(void) pn_free(&mnt_pn);
if (gmttoken != NULL)
kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
return (err);
}
if (!chk_vss) {
if (trailing_slash) {
(void) strlcpy(last_component, ".", MAXNAMELEN);
} else {
(void) pn_setlast(&ppn);
if (ppn.pn_pathlen >= MAXNAMELEN) {
err = ENAMETOOLONG;
goto end_not_vss;
}
(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
ppn.pn_path[0] = '\0';
}
}
if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
smb_node_ref(local_cur_node);
*dir_node = local_cur_node;
} else {
err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
local_root_node, local_cur_node, NULL, dir_node, cred,
chk_vss ? &mnt_pn : NULL);
}
end_not_vss:
(void) pn_free(&ppn);
kmem_free(usepath, SMB_MAXPATHLEN);
if (chk_vss && *dir_node != NULL) {
if ((err = smb_vss_lookup_nodes(sr, *dir_node, &vss_node,
gmttoken)) == 0) {
char *p = mnt_pn.pn_path;
size_t pathleft;
smb_node_release(*dir_node);
*dir_node = NULL;
pathleft = pn_pathleft(&mnt_pn);
if (pathleft == 0 || trailing_slash) {
(void) strlcpy(last_component, ".", MAXNAMELEN);
} else {
(void) pn_setlast(&mnt_pn);
if (ppn.pn_pathlen >= MAXNAMELEN) {
err = ENAMETOOLONG;
goto end_chk_vss;
}
(void) strlcpy(last_component, mnt_pn.pn_path,
MAXNAMELEN);
mnt_pn.pn_path[0] = '\0';
pathleft -= strlen(last_component);
}
if (pathleft != 0) {
err = smb_pathname(sr, p, lookup_flags,
vss_node, vss_node, NULL, dir_node, cred,
NULL);
} else {
*dir_node = vss_node;
vss_node = NULL;
}
}
}
end_chk_vss:
if (chk_vss)
(void) pn_free(&mnt_pn);
if (gmttoken != NULL)
kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
if ((err == 0) && share_root_node) {
if (share_root_node->vp->v_vfsp != (*dir_node)->vp->v_vfsp) {
err = EACCES;
if ((sr) && (sr)->tid_tree &&
smb_tree_has_feature((sr)->tid_tree,
SMB_TREE_TRAVERSE_MOUNTS))
err = 0;
}
}
if (err) {
if (*dir_node) {
(void) smb_node_release(*dir_node);
*dir_node = NULL;
}
*last_component = 0;
}
if (vss_node != NULL)
(void) smb_node_release(vss_node);
return (err);
}
int
smb_pathname(smb_request_t *sr, char *path, int flags,
smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
smb_node_t **ret_node, cred_t *cred, pathname_t *mnt_pn)
{
char *component, *real_name, *namep;
pathname_t pn, rpn, upn, link_pn;
smb_node_t *dnode, *fnode, *mnt_node;
smb_attr_t attr;
vnode_t *rootvp, *vp;
size_t pathleft;
int err = 0;
int nlink = 0;
int local_flags;
uint32_t abe_flag = 0;
char namebuf[MAXNAMELEN];
vnode_t *fsrootvp = NULL;
if (path == NULL)
return (EINVAL);
ASSERT(root_node);
ASSERT(cur_node);
ASSERT(ret_node);
*ret_node = NULL;
if (dir_node)
*dir_node = NULL;
(void) pn_alloc_sz(&upn, SMB_MAXPATHLEN);
if ((err = pn_set(&upn, path)) != 0) {
(void) pn_free(&upn);
return (err);
}
if (mnt_pn != NULL && (err = pn_set(mnt_pn, path) != 0)) {
(void) pn_free(&upn);
return (err);
}
if (SMB_TREE_SUPPORTS_ABE(sr))
abe_flag = SMB_ABE;
(void) pn_alloc(&pn);
(void) pn_alloc(&rpn);
component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
if (mnt_pn != NULL) {
mnt_node = cur_node;
smb_node_ref(cur_node);
} else
mnt_node = NULL;
fnode = NULL;
dnode = cur_node;
smb_node_ref(dnode);
rootvp = root_node->vp;
while ((pathleft = pn_pathleft(&upn)) != 0) {
if (fnode) {
smb_node_release(dnode);
dnode = fnode;
fnode = NULL;
}
if ((err = pn_getcomponent(&upn, component)) != 0)
break;
if ((namep = smb_pathname_catia_v5tov4(sr, component,
namebuf, sizeof (namebuf))) == NULL) {
err = EILSEQ;
break;
}
if ((err = pn_set(&pn, namep)) != 0)
break;
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_DOSATTR;
local_flags = flags & FIGNORECASE;
err = smb_pathname_lookup(&pn, &rpn, local_flags,
&vp, rootvp, dnode->vp, &attr, cred);
if (err) {
if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||
!smb_maybe_mangled(component))
break;
if ((err = smb_unmangle(dnode, component,
real_name, MAXNAMELEN, abe_flag)) != 0)
break;
if ((namep = smb_pathname_catia_v5tov4(sr, real_name,
namebuf, sizeof (namebuf))) == NULL) {
err = EILSEQ;
break;
}
if ((err = pn_set(&pn, namep)) != 0)
break;
local_flags = 0;
err = smb_pathname_lookup(&pn, &rpn, local_flags,
&vp, rootvp, dnode->vp, &attr, cred);
if (err)
break;
}
if (attr.sa_dosattr & FILE_ATTRIBUTE_REPARSE_POINT) {
err = EREMOTE;
VN_RELE(vp);
break;
}
if ((vp->v_type == VLNK) &&
((flags & FOLLOW) || pn_pathleft(&upn))) {
if (++nlink > MAXSYMLINKS) {
err = ELOOP;
VN_RELE(vp);
break;
}
(void) pn_alloc(&link_pn);
err = pn_getsymlink(vp, &link_pn, cred);
VN_RELE(vp);
if (err == 0) {
if (pn_pathleft(&link_pn) == 0)
(void) pn_set(&link_pn, ".");
err = pn_insert(&upn, &link_pn,
strlen(component));
}
pn_free(&link_pn);
if (err)
break;
if (upn.pn_pathlen == 0) {
err = ENOENT;
break;
}
if (upn.pn_path[0] == '/') {
fnode = root_node;
smb_node_ref(fnode);
}
if (pn_fixslash(&upn))
flags |= FOLLOW;
} else {
if (flags & FIGNORECASE) {
if (strcmp(rpn.pn_path, "/") != 0)
pn_setlast(&rpn);
namep = rpn.pn_path;
} else {
namep = pn.pn_path;
}
namep = smb_pathname_catia_v4tov5(sr, namep,
namebuf, sizeof (namebuf));
fnode = smb_node_lookup(sr, NULL, cred, vp, namep,
dnode, NULL);
VN_RELE(vp);
if (fnode == NULL) {
err = ENOMEM;
break;
}
}
while (upn.pn_path[0] == '/') {
upn.pn_path++;
upn.pn_pathlen--;
}
if (mnt_pn != NULL &&
fnode != NULL &&
(err = VFS_ROOT(fnode->vp->v_vfsp, &fsrootvp)) == 0) {
if (fsrootvp == fnode->vp) {
mnt_pn->pn_pathlen = pn_pathleft(&upn);
mnt_pn->pn_path = mnt_pn->pn_buf +
((ptrdiff_t)upn.pn_path -
(ptrdiff_t)upn.pn_buf);
smb_node_ref(fnode);
if (mnt_node != NULL)
smb_node_release(mnt_node);
mnt_node = fnode;
}
VN_RELE(fsrootvp);
}
}
if ((pathleft) && (err == ENOENT))
err = ENOTDIR;
if (mnt_node == NULL)
mnt_pn = NULL;
if (mnt_pn == NULL && err != 0) {
if (fnode)
smb_node_release(fnode);
if (dnode)
smb_node_release(dnode);
} else {
if (mnt_pn != NULL) {
*ret_node = mnt_node;
if (fnode != NULL)
smb_node_release(fnode);
} else {
*ret_node = fnode;
}
if (dir_node)
*dir_node = dnode;
else
smb_node_release(dnode);
}
kmem_free(component, MAXNAMELEN);
kmem_free(real_name, MAXNAMELEN);
(void) pn_free(&pn);
(void) pn_free(&rpn);
(void) pn_free(&upn);
return (err);
}
static int
smb_pathname_lookup(pathname_t *pn, pathname_t *rpn, int flags,
vnode_t **vp, vnode_t *rootvp, vnode_t *dvp, smb_attr_t *attr, cred_t *cred)
{
int err;
*vp = NULL;
VN_HOLD(dvp);
if (rootvp != rootdir)
VN_HOLD(rootvp);
#ifdef _KERNEL
if (smb_vop_priv_check(cred, PRIV_FILE_DAC_SEARCH, B_FALSE, dvp))
flags |= LOOKUP_NOACLCHECK;
#endif
err = lookuppnvp(pn, rpn, flags, NULL, vp, rootvp, dvp, cred);
if ((err == 0) && (attr != NULL))
(void) smb_vop_getattr(*vp, NULL, attr, 0, zone_kcred());
return (err);
}
static char *
smb_pathname_catia_v5tov4(smb_request_t *sr, char *name,
char *namebuf, int buflen)
{
char *namep;
if (SMB_TREE_SUPPORTS_CATIA(sr)) {
namep = smb_vop_catia_v5tov4(name, namebuf, buflen);
if (strchr(namep, '/') != NULL)
return (NULL);
return (namep);
}
return (name);
}
static char *
smb_pathname_catia_v4tov5(smb_request_t *sr, char *name,
char *namebuf, int buflen)
{
if (SMB_TREE_SUPPORTS_CATIA(sr)) {
smb_vop_catia_v4tov5(name, namebuf, buflen);
return (namebuf);
}
return (name);
}
vnode_t *
smb_lookuppathvptovp(smb_request_t *sr, char *path, vnode_t *startvp,
vnode_t *rootvp)
{
pathname_t pn;
vnode_t *vp = NULL;
int lookup_flags = FOLLOW;
if (SMB_TREE_IS_CASEINSENSITIVE(sr))
lookup_flags |= FIGNORECASE;
(void) pn_alloc(&pn);
if (pn_set(&pn, path) == 0) {
VN_HOLD(startvp);
if (rootvp != rootdir)
VN_HOLD(rootvp);
if (lookuppnvp(&pn, NULL, lookup_flags, NULL, &vp,
rootvp, startvp, zone_kcred()) != 0) {
pn_free(&pn);
return (NULL);
}
}
pn_free(&pn);
return (vp);
}
void
smb_pathname_init(smb_request_t *sr, smb_pathname_t *pn, char *path)
{
char *pname, *fname, *sname;
int len;
bzero(pn, sizeof (smb_pathname_t));
pn->pn_path = smb_pathname_strdup(sr, path);
smb_pathname_preprocess(sr, pn);
pname = pn->pn_path;
fname = strrchr(pname, '\\');
if (fname != NULL) {
if (fname == pname) {
pn->pn_pname = NULL;
} else {
*fname = '\0';
pn->pn_pname =
smb_pathname_strdup(sr, pname);
*fname = '\\';
}
++fname;
} else {
fname = pname;
pn->pn_pname = NULL;
}
sname = strchr(fname, ':');
if (sname == NULL) {
pn->pn_fname = smb_pathname_strdup(sr, fname);
return;
}
if (sname == fname) {
pn->pn_fname = NULL;
} else {
*sname = '\0';
pn->pn_fname = smb_pathname_strdup(sr, fname);
*sname = ':';
}
if (strcasecmp(sname, "::$DATA") == 0) {
ASSERT(sname >= pname &&
sname < (pname + strlen(pname)));
*sname = '\0';
return;
}
pn->pn_sname = smb_pathname_strdup(sr, sname);
pn->pn_stype = strchr(pn->pn_sname + 1, ':');
if (pn->pn_stype) {
(void) smb_strupr(pn->pn_stype);
} else {
len = strlen(pn->pn_sname);
pn->pn_sname = smb_pathname_strcat(sr, pn->pn_sname, ":$DATA");
pn->pn_stype = pn->pn_sname + len;
}
++pn->pn_stype;
}
static void
smb_pathname_preprocess(smb_request_t *sr, smb_pathname_t *pn)
{
char *p;
if (strlen(pn->pn_path) == 0) {
pn->pn_path = smb_pathname_strdup(sr, "\\");
return;
}
if (sr->session->dialect < NT_LM_0_12)
smb_convert_wildcards(pn->pn_path);
(void) strsubst(pn->pn_path, '/', '\\');
(void) strcanon(pn->pn_path, "\\");
p = pn->pn_path + strlen(pn->pn_path) - 1;
if ((p != pn->pn_path) && (*p == '\\'))
*p = '\0';
smb_pathname_preprocess_quota(sr, pn);
smb_pathname_preprocess_adminshare(sr, pn);
}
static void
smb_pathname_preprocess_quota(smb_request_t *sr, smb_pathname_t *pn)
{
char *name = "$EXTEND";
char *new_name = ".$EXTEND";
char *p, *slash;
int len;
if (!smb_node_is_vfsroot(sr->tid_tree->t_snode))
return;
p = pn->pn_path;
p += strspn(p, "\\");
if (smb_strcasecmp(p, name, strlen(name)) != 0)
return;
p += strlen(name);
if ((*p != ':') && (*p != '\\') && (*p != '\0'))
return;
slash = (pn->pn_path[0] == '\\') ? "\\" : "";
len = strlen(pn->pn_path) + 2;
pn->pn_path = smb_srm_alloc(sr, len);
(void) snprintf(pn->pn_path, len, "%s%s%s", slash, new_name, p);
(void) smb_strupr(pn->pn_path);
}
static void
smb_pathname_preprocess_adminshare(smb_request_t *sr, smb_pathname_t *pn)
{
if (strcasecmp(sr->tid_tree->t_sharename, "c$") == 0)
(void) smb_strlwr(pn->pn_path);
}
static char *
smb_pathname_strdup(smb_request_t *sr, const char *s)
{
char *s2;
size_t n;
n = strlen(s) + 1;
s2 = smb_srm_zalloc(sr, n);
(void) strlcpy(s2, s, n);
return (s2);
}
static char *
smb_pathname_strcat(smb_request_t *sr, char *s1, const char *s2)
{
size_t n;
n = strlen(s1) + strlen(s2) + 1;
s1 = smb_srm_rezalloc(sr, s1, n);
(void) strlcat(s1, s2, n);
return (s1);
}
boolean_t
smb_pathname_validate(smb_request_t *sr, smb_pathname_t *pn)
{
char *path = pn->pn_path;
path += strspn(path, "\\");
if ((strcmp(path, "..") == 0) || (strncmp(path, "..\\", 3) == 0)) {
smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
ERRDOS, ERROR_BAD_PATHNAME);
return (B_FALSE);
}
if (pn->pn_pname && smb_contains_wildcards(pn->pn_pname)) {
smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
ERRDOS, ERROR_INVALID_NAME);
return (B_FALSE);
}
if (pn->pn_fname && (strcmp(pn->pn_fname, ".") == 0)) {
smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
ERRDOS, ERROR_INVALID_NAME);
return (B_FALSE);
}
return (B_TRUE);
}
boolean_t
smb_validate_dirname(smb_request_t *sr, smb_pathname_t *pn)
{
char *name;
char *path = pn->pn_path;
if ((name = path) != 0) {
name += strspn(name, "\\");
if (strchr(name, ':') != 0) {
smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
ERRDOS, ERROR_INVALID_NAME);
return (B_FALSE);
}
}
if (pn->pn_sname)
return (smb_validate_stream_name(sr, pn));
return (B_TRUE);
}
boolean_t
smb_validate_object_name(smb_request_t *sr, smb_pathname_t *pn)
{
if (pn->pn_fname &&
strlen(pn->pn_fname) == 5 &&
smb_isdigit(pn->pn_fname[3]) &&
pn->pn_fname[4] == ':') {
smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
ERRDOS, ERROR_INVALID_NAME);
return (B_FALSE);
}
if (pn->pn_sname)
return (smb_validate_stream_name(sr, pn));
return (B_TRUE);
}
void
smb_stream_parse_name(char *path, char *filename, char *stream)
{
char *fname, *sname, *stype;
size_t flen, slen;
ASSERT(path);
ASSERT(filename);
ASSERT(stream);
fname = strrchr(path, '\\');
fname = (fname == NULL) ? path : fname + 1;
sname = strchr(fname, ':');
VERIFY(sname != NULL);
flen = sname - fname;
slen = strlen(sname);
if (flen > (MAXNAMELEN-1))
flen = (MAXNAMELEN-1);
(void) strncpy(filename, fname, flen);
filename[flen] = '\0';
if (slen > (MAXNAMELEN-1))
slen = (MAXNAMELEN-1);
(void) strncpy(stream, sname, slen);
stream[slen] = '\0';
stype = strchr(stream + 1, ':');
if (stype == NULL)
(void) strlcat(stream, ":$DATA", MAXNAMELEN);
else
(void) smb_strupr(stype);
}
boolean_t
smb_is_stream_name(char *path)
{
char *colonp;
if (path == NULL)
return (B_FALSE);
colonp = strchr(path, ':');
if ((colonp == NULL) || (*(colonp+1) == '\0'))
return (B_FALSE);
if (strstr(path, "::"))
return (B_FALSE);
return (B_TRUE);
}
boolean_t
smb_strname_restricted(char *strname)
{
char *stype;
stype = strrchr(strname, ':');
if (stype == NULL)
return (B_FALSE);
if (strcmp(stype, ":$CA") == 0)
return (B_TRUE);
return (B_FALSE);
}
boolean_t
smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn)
{
static char *strmtype[] = {
"$CA",
"$DATA",
"$INDEX_ALLOCATION"
};
int i;
ASSERT(pn);
ASSERT(pn->pn_sname);
if (pn->pn_stype != NULL) {
for (i = 0; i < sizeof (strmtype) / sizeof (strmtype[0]); ++i) {
if (strcasecmp(pn->pn_stype, strmtype[i]) == 0)
return (B_TRUE);
}
smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
ERRDOS, ERROR_INVALID_NAME);
return (B_FALSE);
}
return (B_TRUE);
}
static int
smb_pathname_dfs_preprocess(smb_request_t *sr, char *path, size_t pathsz)
{
smb_unc_t unc;
char *linkpath;
int rc;
if (sr->tid_tree == NULL)
return (0);
if ((rc = smb_unc_init(path, &unc)) != 0)
return (rc);
if (smb_strcasecmp(unc.unc_share, sr->tid_tree->t_sharename, 0)) {
smb_unc_free(&unc);
return (EINVAL);
}
linkpath = unc.unc_path;
(void) snprintf(path, pathsz, "/%s", (linkpath) ? linkpath : "");
smb_unc_free(&unc);
return (0);
}