#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_dfs.h>
#include <smbsrv/smb_door.h>
#include <smb/winioctl.h>
#define DFS_HDRFLG_R 0x00000001
#define DFS_HDRFLG_S 0x00000002
#define DFS_HDRFLG_T 0x00000004
#define DFS_ENTFLG_T 0x0004
#define DFS_REFERRAL_V1 0x0001
#define DFS_REFERRAL_V2 0x0002
#define DFS_REFERRAL_V3 0x0003
#define DFS_REFERRAL_V4 0x0004
#define DFS_SRVTYPE_NONROOT 0x0000
#define DFS_SRVTYPE_ROOT 0x0001
#define DFS_REFV1_ENTSZ 8
#define DFS_REFV2_ENTSZ 22
#define DFS_REFV3_ENTSZ 34
#define DFS_REFV4_ENTSZ 34
static dfs_reftype_t smb_dfs_get_reftype(const char *);
static void smb_dfs_encode_hdr(mbuf_chain_t *, dfs_info_t *);
static uint32_t smb_dfs_encode_refv1(smb_request_t *, mbuf_chain_t *,
dfs_info_t *);
static uint32_t smb_dfs_encode_refv2(smb_request_t *, mbuf_chain_t *,
dfs_info_t *);
static uint32_t smb_dfs_encode_refv3x(smb_request_t *, mbuf_chain_t *,
dfs_info_t *, uint16_t);
static void smb_dfs_encode_targets(mbuf_chain_t *, dfs_info_t *);
static uint32_t smb_dfs_referrals_get(smb_request_t *, char *, dfs_reftype_t,
dfs_referral_response_t *);
static void smb_dfs_referrals_free(dfs_referral_response_t *);
static uint16_t smb_dfs_referrals_unclen(dfs_info_t *, uint16_t);
static uint32_t smb_dfs_get_referrals_ex(smb_request_t *, smb_fsctl_t *);
uint32_t
smb_dfs_fsctl(smb_request_t *sr, smb_fsctl_t *fsctl)
{
uint32_t status;
if (!STYPE_ISIPC(sr->tid_tree->t_res_type))
return (NT_STATUS_INVALID_DEVICE_REQUEST);
if ((sr->session->srv_cap & SMB2_CAP_DFS) == 0)
return (NT_STATUS_FS_DRIVER_REQUIRED);
switch (fsctl->CtlCode) {
case FSCTL_DFS_GET_REFERRALS:
status = smb_dfs_get_referrals(sr, fsctl);
break;
case FSCTL_DFS_GET_REFERRALS_EX:
status = smb_dfs_get_referrals_ex(sr, fsctl);
break;
default:
status = NT_STATUS_FS_DRIVER_REQUIRED;
}
return (status);
}
uint32_t
smb_dfs_get_referrals_ex(smb_request_t *sr, smb_fsctl_t *fsctl)
{
dfs_info_t *referrals;
dfs_referral_response_t refrsp;
dfs_reftype_t reftype;
char *path;
uint16_t maxver;
uint16_t flags;
uint16_t fnlen;
uint32_t datalen;
uint32_t status;
int rc;
ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
rc = smb_mbc_decodef(fsctl->in_mbc, "wwl",
&maxver, &flags, &datalen);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
if (MBC_ROOM_FOR(fsctl->in_mbc, datalen) == 0)
return (NT_STATUS_INVALID_PARAMETER);
rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
sr, &fnlen, &path);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
reftype = smb_dfs_get_reftype((const char *)path);
switch (reftype) {
case DFS_REFERRAL_INVALID:
return (NT_STATUS_INVALID_PARAMETER);
case DFS_REFERRAL_DOMAIN:
case DFS_REFERRAL_DC:
return (NT_STATUS_INVALID_PARAMETER);
case DFS_REFERRAL_SYSVOL:
return (NT_STATUS_NO_SUCH_DEVICE);
default:
break;
}
status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
if (status != NT_STATUS_SUCCESS)
return (status);
referrals = &refrsp.rp_referrals;
smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
switch (maxver) {
case DFS_REFERRAL_V1:
status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
break;
case DFS_REFERRAL_V2:
status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
break;
case DFS_REFERRAL_V3:
status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
DFS_REFERRAL_V3);
break;
case DFS_REFERRAL_V4:
default:
status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
DFS_REFERRAL_V4);
break;
}
smb_dfs_referrals_free(&refrsp);
return (status);
}
uint32_t
smb_dfs_get_referrals(smb_request_t *sr, smb_fsctl_t *fsctl)
{
dfs_info_t *referrals;
dfs_referral_response_t refrsp;
dfs_reftype_t reftype;
char *path;
uint16_t maxver;
uint32_t status;
int rc;
ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
sr, &maxver, &path);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
reftype = smb_dfs_get_reftype((const char *)path);
switch (reftype) {
case DFS_REFERRAL_INVALID:
return (NT_STATUS_INVALID_PARAMETER);
case DFS_REFERRAL_DOMAIN:
case DFS_REFERRAL_DC:
return (NT_STATUS_INVALID_PARAMETER);
case DFS_REFERRAL_SYSVOL:
return (NT_STATUS_NO_SUCH_DEVICE);
default:
break;
}
status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
if (status != NT_STATUS_SUCCESS)
return (status);
referrals = &refrsp.rp_referrals;
smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
switch (maxver) {
case DFS_REFERRAL_V1:
status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
break;
case DFS_REFERRAL_V2:
status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
break;
case DFS_REFERRAL_V3:
status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
DFS_REFERRAL_V3);
break;
case DFS_REFERRAL_V4:
default:
status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
DFS_REFERRAL_V4);
break;
}
smb_dfs_referrals_free(&refrsp);
return (status);
}
static dfs_reftype_t
smb_dfs_get_reftype(const char *path)
{
smb_unc_t unc;
dfs_reftype_t reftype = 0;
if (*path == '\0')
return (DFS_REFERRAL_DOMAIN);
if (smb_unc_init(path, &unc) != 0)
return (DFS_REFERRAL_INVALID);
if (unc.unc_path != NULL) {
reftype = DFS_REFERRAL_LINK;
} else if (unc.unc_share != NULL) {
if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) ||
(smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) {
reftype = DFS_REFERRAL_SYSVOL;
} else {
reftype = DFS_REFERRAL_ROOT;
}
} else if (unc.unc_server != NULL) {
reftype = DFS_REFERRAL_DC;
}
smb_unc_free(&unc);
return (reftype);
}
static void
smb_dfs_encode_hdr(mbuf_chain_t *mbc, dfs_info_t *referrals)
{
uint16_t path_consumed;
uint32_t flags;
path_consumed = smb_wcequiv_strlen(referrals->i_uncpath);
flags = DFS_HDRFLG_S;
if (referrals->i_type == DFS_OBJECT_ROOT)
flags |= DFS_HDRFLG_R;
(void) smb_mbc_encodef(mbc, "wwl", path_consumed,
referrals->i_ntargets, flags);
}
static uint32_t
smb_dfs_encode_refv1(smb_request_t *sr, mbuf_chain_t *mbc,
dfs_info_t *referrals)
{
_NOTE(ARGUNUSED(sr))
uint16_t entsize, rep_bufsize;
uint16_t server_type;
uint16_t flags = 0;
uint16_t r;
char *target;
rep_bufsize = MBC_MAXBYTES(mbc);
server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
for (r = 0; r < referrals->i_ntargets; r++) {
(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
referrals->i_targets[r].t_server,
referrals->i_targets[r].t_share);
entsize = DFS_REFV1_ENTSZ + smb_wcequiv_strlen(target) + 2;
if (entsize > rep_bufsize)
break;
(void) smb_mbc_encodef(mbc, "wwwwU",
DFS_REFERRAL_V1, entsize, server_type, flags, target);
rep_bufsize -= entsize;
}
kmem_free(target, MAXPATHLEN);
if (r == 0) {
return (NT_STATUS_BUFFER_OVERFLOW);
}
return (NT_STATUS_SUCCESS);
}
static uint32_t
smb_dfs_encode_refv2(smb_request_t *sr, mbuf_chain_t *mbc,
dfs_info_t *referrals)
{
_NOTE(ARGUNUSED(sr))
uint16_t entsize, rep_bufsize;
uint16_t server_type;
uint16_t flags = 0;
uint32_t proximity = 0;
uint16_t path_offs, altpath_offs, netpath_offs;
uint16_t targetsz, total_targetsz = 0;
uint16_t dfs_pathsz;
uint16_t r;
rep_bufsize = MBC_MAXBYTES(mbc);
dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
entsize = DFS_REFV2_ENTSZ + dfs_pathsz + dfs_pathsz +
smb_dfs_referrals_unclen(referrals, 0);
if (entsize > rep_bufsize) {
return (NT_STATUS_BUFFER_OVERFLOW);
}
server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
rep_bufsize -= entsize;
entsize = DFS_REFV2_ENTSZ;
for (r = 0; r < referrals->i_ntargets; r++) {
path_offs = (referrals->i_ntargets - r) * DFS_REFV2_ENTSZ;
altpath_offs = path_offs + dfs_pathsz;
netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
targetsz = smb_dfs_referrals_unclen(referrals, r);
if (r != 0) {
entsize = DFS_REFV2_ENTSZ + targetsz;
if (entsize > rep_bufsize)
break;
rep_bufsize -= entsize;
}
(void) smb_mbc_encodef(mbc, "wwwwllwww",
DFS_REFERRAL_V2, DFS_REFV2_ENTSZ, server_type, flags,
proximity, referrals->i_timeout, path_offs, altpath_offs,
netpath_offs);
total_targetsz += targetsz;
}
smb_dfs_encode_targets(mbc, referrals);
return (NT_STATUS_SUCCESS);
}
static uint32_t
smb_dfs_encode_refv3x(smb_request_t *sr, mbuf_chain_t *mbc,
dfs_info_t *referrals, uint16_t ver)
{
_NOTE(ARGUNUSED(sr))
uint16_t entsize, rep_bufsize, hdrsize;
uint16_t server_type;
uint16_t flags = 0;
uint16_t path_offs, altpath_offs, netpath_offs;
uint16_t targetsz, total_targetsz = 0;
uint16_t dfs_pathsz;
uint16_t r;
hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ;
rep_bufsize = MBC_MAXBYTES(mbc);
dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
entsize = hdrsize + dfs_pathsz + dfs_pathsz +
smb_dfs_referrals_unclen(referrals, 0);
if (entsize > rep_bufsize) {
return (NT_STATUS_BUFFER_OVERFLOW);
}
server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
rep_bufsize -= entsize;
for (r = 0; r < referrals->i_ntargets; r++) {
path_offs = (referrals->i_ntargets - r) * hdrsize;
altpath_offs = path_offs + dfs_pathsz;
netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
targetsz = smb_dfs_referrals_unclen(referrals, r);
if (r != 0) {
entsize = hdrsize + targetsz;
if (entsize > rep_bufsize)
break;
rep_bufsize -= entsize;
flags = 0;
} else if (ver == DFS_REFERRAL_V4) {
flags = DFS_ENTFLG_T;
}
(void) smb_mbc_encodef(mbc, "wwwwlwww16.",
ver, hdrsize, server_type, flags,
referrals->i_timeout, path_offs, altpath_offs,
netpath_offs);
total_targetsz += targetsz;
}
smb_dfs_encode_targets(mbc, referrals);
return (NT_STATUS_SUCCESS);
}
static void
smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals)
{
char *target;
int r;
(void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath,
referrals->i_uncpath);
target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
for (r = 0; r < referrals->i_ntargets; r++) {
(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
referrals->i_targets[r].t_server,
referrals->i_targets[r].t_share);
(void) smb_mbc_encodef(mbc, "U", target);
}
kmem_free(target, MAXPATHLEN);
}
static uint32_t
smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype,
dfs_referral_response_t *refrsp)
{
dfs_referral_query_t req;
int rc;
req.rq_type = reftype;
req.rq_path = dfs_path;
bzero(refrsp, sizeof (dfs_referral_response_t));
refrsp->rp_status = NT_STATUS_NOT_FOUND;
rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS,
&req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr);
if (rc != 0)
return (NT_STATUS_FS_DRIVER_REQUIRED);
switch (refrsp->rp_status) {
case ERROR_SUCCESS:
break;
case ERROR_INVALID_PARAMETER:
return (NT_STATUS_INVALID_PARAMETER);
case ERROR_NOT_ENOUGH_MEMORY:
return (NT_STATUS_INSUFFICIENT_RESOURCES);
case ERROR_NOT_FOUND:
return (NT_STATUS_NOT_FOUND);
default:
return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
}
(void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\');
return (NT_STATUS_SUCCESS);
}
static void
smb_dfs_referrals_free(dfs_referral_response_t *refrsp)
{
xdr_free(dfs_referral_response_xdr, (char *)refrsp);
}
static uint16_t
smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno)
{
uint16_t len;
if (refno >= referrals->i_ntargets)
return (0);
len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) +
smb_wcequiv_strlen(referrals->i_targets[refno].t_share) +
smb_wcequiv_strlen("\\\\") + 2;
return (len);
}