/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Nexenta Systems, Inc. All rights reserved. * Copyright 2021 RackTop Systems, Inc. */ #include #include #include #include /* * Get Referral response header flags * For exact meaning refer to MS-DFSC spec. * * R: ReferralServers * S: StorageServers * T: TargetFailback */ #define DFS_HDRFLG_R 0x00000001 #define DFS_HDRFLG_S 0x00000002 #define DFS_HDRFLG_T 0x00000004 /* * Entry flags */ #define DFS_ENTFLG_T 0x0004 /* * Referral entry types/versions */ #define DFS_REFERRAL_V1 0x0001 #define DFS_REFERRAL_V2 0x0002 #define DFS_REFERRAL_V3 0x0003 #define DFS_REFERRAL_V4 0x0004 /* * Valid values for ServerType field in referral entries */ #define DFS_SRVTYPE_NONROOT 0x0000 #define DFS_SRVTYPE_ROOT 0x0001 /* * Size of the fix part for each referral entry type */ #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 *); /* * Handle device type FILE_DEVICE_DFS * for smb2_ioctl */ 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 the connection is not DFS capable, we should return * NT_STATUS_FS_DRIVER_REQUIRED for both of these DFS ioctls. * See [MS-SMB2] 3.3.5.15.2. */ 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: /* * MS-SMB2 suggests INVALID_DEVICE_REQUEST * for unknown control codes, but using that * here makes Windows unhappy. */ status = NT_STATUS_FS_DRIVER_REQUIRED; } return (status); } /* * XXX Instead of decoding the referral request and encoding * the response here (in-kernel) we could pass the given * request buffer in our door call, and let that return the * response buffer ready to stuff into out_mbc. That would * allow all this decoding/encoding to happen at user-level. * (and most of this file would go away. :-) */ /* * See [MS-DFSC] for details about this command * Handles FSCTL_DFS_GET_REFERRALS_EX (only) */ 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; /* * The caller checks this, because the error reporting method * varies across SMB versions. */ ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type)); /* * Decode fixed part. * Input data is (w) MaxReferralLevel, (w) Flags, * (l) RequestDataLength, ... variable data ... */ rc = smb_mbc_decodef(fsctl->in_mbc, "wwl", &maxver, &flags, &datalen); if (rc != 0) return (NT_STATUS_INVALID_PARAMETER); /* * Decode variable part: * (w) file name length, (u) filename, * ( if flags & 1 ) * (w) site name len, (u) site name * We don't decode or use the site name */ 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: /* Need to check the error for this case */ return (NT_STATUS_INVALID_PARAMETER); case DFS_REFERRAL_DOMAIN: case DFS_REFERRAL_DC: /* MS-DFSC: this error is returned by non-DC root */ return (NT_STATUS_INVALID_PARAMETER); case DFS_REFERRAL_SYSVOL: /* MS-DFSC: this error is returned by non-DC root */ 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); /* * Server may respond with any referral version at or below * the maximum specified in the request. */ 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); } /* * Note: SMB1 callers in smb_trans2_dfs.c * smb_com_trans2_report_dfs_inconsistency * smb_com_trans2_get_dfs_referral */ /* * See [MS-DFSC] for details about this command * Handles FSCTL_DFS_GET_REFERRALS (only) */ 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; /* * The caller checks this, because the error reporting method * varies across SMB versions. */ ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type)); /* * Input data is (w) MaxReferralLevel, (U) path */ 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: /* Need to check the error for this case */ return (NT_STATUS_INVALID_PARAMETER); case DFS_REFERRAL_DOMAIN: case DFS_REFERRAL_DC: /* MS-DFSC: this error is returned by non-DC root */ return (NT_STATUS_INVALID_PARAMETER); case DFS_REFERRAL_SYSVOL: /* MS-DFSC: this error is returned by non-DC root */ 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); /* * Server may respond with any referral version at or below * the maximum specified in the request. */ 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); } /* * [MS-DFSC]: REQ_GET_DFS_REFERRAL * * Determines the referral type based on the specified path: * * Domain referral: * "" * * DC referral: * \ * * Sysvol referral: * \\SYSVOL * \\NETLOGON * * Root referral: * \\ * \\ * * Link referral: * \\\ * \\\ */ 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; /* Fill rep_param_mb in SMB1 caller. */ (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); /* * Need room for at least one entry. * Windows will silently drop targets that do not fit in * the response buffer. */ if (r == 0) { return (NT_STATUS_BUFFER_OVERFLOW); } return (NT_STATUS_SUCCESS); } /* * Prepare a response with V2 referral format. * * Here is the response packet format. * All the strings come after all the fixed size entry headers. * These headers contain offsets to the strings at the end. Note * that the two "dfs_path" after the last entry is shared between * all the entries. * * ent1-hdr * ent2-hdr * ... * entN-hdr * dfs_path * dfs_path * target1 * target2 * ... * targetN * * MS-DFSC mentions that strings can come after each entry header or all after * the last entry header. Windows responses are in the format above. */ 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) { /* need room for at least one referral */ 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) /* silently drop targets that do not fit */ 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); } /* * Prepare a response with V3/V4 referral format. * * For more details, see comments for smb_dfs_encode_refv2() or see * MS-DFSC specification. */ 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) { /* need room for at least one referral */ 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) /* silently drop targets that do not fit */ 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); } /* * Encodes DFS path, and target strings which come after fixed header * entries. * * Windows 2000 and earlier set the DFSAlternatePathOffset to point to * an 8.3 string representation of the string pointed to by * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset * points to a separate copy of the same string. Windows Server 2003, * Windows Server 2008 and Windows Server 2008 R2 set the * DFSPathOffset and DFSAlternatePathOffset fields to point to separate * copies of the identical string. * * Following Windows 2003 and later here. */ 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); } /* * Get referral information for the specified path from user space * using a door call. */ 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); /* * Map the Win error to one of the NT status codes * documented in MS-DFSC. The most common, when we * have no DFS root configured, is NOT_FOUND. */ 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); } /* * Returns the Unicode string length for the target UNC of * the specified entry by 'refno' * * Note that the UNC path should be encoded with ONE leading * slash not two as is common to user-visible UNC paths. */ static uint16_t smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno) { uint16_t len; if (refno >= referrals->i_ntargets) return (0); /* Encoded target UNC \server\share */ len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) + smb_wcequiv_strlen(referrals->i_targets[refno].t_share) + smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */ return (len); }