1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Copyright 2018 Nexenta Systems, Inc. All rights reserved. 26 */ 27 28 #include <smbsrv/smb_kproto.h> 29 #include <smbsrv/smb_dfs.h> 30 #include <smbsrv/smb_door.h> 31 #include <smb/winioctl.h> 32 33 /* 34 * Get Referral response header flags 35 * For exact meaning refer to MS-DFSC spec. 36 * 37 * R: ReferralServers 38 * S: StorageServers 39 * T: TargetFailback 40 */ 41 #define DFS_HDRFLG_R 0x00000001 42 #define DFS_HDRFLG_S 0x00000002 43 #define DFS_HDRFLG_T 0x00000004 44 45 /* 46 * Entry flags 47 */ 48 #define DFS_ENTFLG_T 0x0004 49 50 /* 51 * Referral entry types/versions 52 */ 53 #define DFS_REFERRAL_V1 0x0001 54 #define DFS_REFERRAL_V2 0x0002 55 #define DFS_REFERRAL_V3 0x0003 56 #define DFS_REFERRAL_V4 0x0004 57 58 /* 59 * Valid values for ServerType field in referral entries 60 */ 61 #define DFS_SRVTYPE_NONROOT 0x0000 62 #define DFS_SRVTYPE_ROOT 0x0001 63 64 /* 65 * Size of the fix part for each referral entry type 66 */ 67 #define DFS_REFV1_ENTSZ 8 68 #define DFS_REFV2_ENTSZ 22 69 #define DFS_REFV3_ENTSZ 34 70 #define DFS_REFV4_ENTSZ 34 71 72 static dfs_reftype_t smb_dfs_get_reftype(const char *); 73 static void smb_dfs_encode_hdr(mbuf_chain_t *, dfs_info_t *); 74 static uint32_t smb_dfs_encode_refv1(smb_request_t *, mbuf_chain_t *, 75 dfs_info_t *); 76 static uint32_t smb_dfs_encode_refv2(smb_request_t *, mbuf_chain_t *, 77 dfs_info_t *); 78 static uint32_t smb_dfs_encode_refv3x(smb_request_t *, mbuf_chain_t *, 79 dfs_info_t *, uint16_t); 80 static void smb_dfs_encode_targets(mbuf_chain_t *, dfs_info_t *); 81 static uint32_t smb_dfs_referrals_get(smb_request_t *, char *, dfs_reftype_t, 82 dfs_referral_response_t *); 83 static void smb_dfs_referrals_free(dfs_referral_response_t *); 84 static uint16_t smb_dfs_referrals_unclen(dfs_info_t *, uint16_t); 85 86 /* 87 * Note: SMB1 callers in smb_trans2_dfs.c 88 * smb_com_trans2_report_dfs_inconsistency 89 * smb_com_trans2_get_dfs_referral 90 */ 91 92 /* 93 * See [MS-DFSC] for details about this command 94 */ 95 uint32_t 96 smb_dfs_get_referrals(smb_request_t *sr, smb_fsctl_t *fsctl) 97 { 98 dfs_info_t *referrals; 99 dfs_referral_response_t refrsp; 100 dfs_reftype_t reftype; 101 char *path; 102 uint16_t maxver; 103 uint32_t status; 104 int rc; 105 106 /* 107 * The caller checks this, because the error reporting method 108 * varies across SMB versions. 109 */ 110 ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type)); 111 112 /* 113 * XXX Instead of decoding the referral request and encoding 114 * the response here (in-kernel) we could pass the given 115 * request buffer in our door call, and let that return the 116 * response buffer ready to stuff into out_mbc. That would 117 * allow all this decoding/encoding to happen at user-level. 118 * (and most of this file would go away. :-) 119 */ 120 switch (fsctl->CtlCode) { 121 case FSCTL_DFS_GET_REFERRALS: 122 /* 123 * Input data is (w) MaxReferralLevel, (U) path 124 */ 125 rc = smb_mbc_decodef(fsctl->in_mbc, "%wu", 126 sr, &maxver, &path); 127 if (rc != 0) 128 return (NT_STATUS_INVALID_PARAMETER); 129 break; 130 131 case FSCTL_DFS_GET_REFERRALS_EX: /* XXX - todo */ 132 default: 133 return (NT_STATUS_NOT_SUPPORTED); 134 } 135 136 reftype = smb_dfs_get_reftype((const char *)path); 137 switch (reftype) { 138 case DFS_REFERRAL_INVALID: 139 /* Need to check the error for this case */ 140 return (NT_STATUS_INVALID_PARAMETER); 141 142 case DFS_REFERRAL_DOMAIN: 143 case DFS_REFERRAL_DC: 144 /* MS-DFSC: this error is returned by non-DC root */ 145 return (NT_STATUS_INVALID_PARAMETER); 146 147 case DFS_REFERRAL_SYSVOL: 148 /* MS-DFSC: this error is returned by non-DC root */ 149 return (NT_STATUS_NO_SUCH_DEVICE); 150 151 default: 152 break; 153 } 154 155 status = smb_dfs_referrals_get(sr, path, reftype, &refrsp); 156 if (status != NT_STATUS_SUCCESS) 157 return (status); 158 159 referrals = &refrsp.rp_referrals; 160 smb_dfs_encode_hdr(fsctl->out_mbc, referrals); 161 162 /* 163 * Server may respond with any referral version at or below 164 * the maximum specified in the request. 165 */ 166 switch (maxver) { 167 case DFS_REFERRAL_V1: 168 status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals); 169 break; 170 171 case DFS_REFERRAL_V2: 172 status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals); 173 break; 174 175 case DFS_REFERRAL_V3: 176 status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals, 177 DFS_REFERRAL_V3); 178 break; 179 180 case DFS_REFERRAL_V4: 181 default: 182 status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals, 183 DFS_REFERRAL_V4); 184 break; 185 } 186 187 smb_dfs_referrals_free(&refrsp); 188 189 return (status); 190 } 191 192 /* 193 * [MS-DFSC]: REQ_GET_DFS_REFERRAL 194 * 195 * Determines the referral type based on the specified path: 196 * 197 * Domain referral: 198 * "" 199 * 200 * DC referral: 201 * \<domain> 202 * 203 * Sysvol referral: 204 * \<domain>\SYSVOL 205 * \<domain>\NETLOGON 206 * 207 * Root referral: 208 * \<domain>\<dfsname> 209 * \<server>\<dfsname> 210 * 211 * Link referral: 212 * \<domain>\<dfsname>\<linkpath> 213 * \<server>\<dfsname>\<linkpath> 214 */ 215 static dfs_reftype_t 216 smb_dfs_get_reftype(const char *path) 217 { 218 smb_unc_t unc; 219 dfs_reftype_t reftype = 0; 220 221 if (*path == '\0') 222 return (DFS_REFERRAL_DOMAIN); 223 224 if (smb_unc_init(path, &unc) != 0) 225 return (DFS_REFERRAL_INVALID); 226 227 if (unc.unc_path != NULL) { 228 reftype = DFS_REFERRAL_LINK; 229 } else if (unc.unc_share != NULL) { 230 if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) || 231 (smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) { 232 reftype = DFS_REFERRAL_SYSVOL; 233 } else { 234 reftype = DFS_REFERRAL_ROOT; 235 } 236 } else if (unc.unc_server != NULL) { 237 reftype = DFS_REFERRAL_DC; 238 } 239 240 smb_unc_free(&unc); 241 return (reftype); 242 } 243 244 static void 245 smb_dfs_encode_hdr(mbuf_chain_t *mbc, dfs_info_t *referrals) 246 { 247 uint16_t path_consumed; 248 uint32_t flags; 249 250 path_consumed = smb_wcequiv_strlen(referrals->i_uncpath); 251 flags = DFS_HDRFLG_S; 252 if (referrals->i_type == DFS_OBJECT_ROOT) 253 flags |= DFS_HDRFLG_R; 254 255 /* Fill rep_param_mb in SMB1 caller. */ 256 (void) smb_mbc_encodef(mbc, "wwl", path_consumed, 257 referrals->i_ntargets, flags); 258 } 259 260 static uint32_t 261 smb_dfs_encode_refv1(smb_request_t *sr, mbuf_chain_t *mbc, 262 dfs_info_t *referrals) 263 { 264 _NOTE(ARGUNUSED(sr)) 265 uint16_t entsize, rep_bufsize; 266 uint16_t server_type; 267 uint16_t flags = 0; 268 uint16_t r; 269 char *target; 270 271 rep_bufsize = MBC_MAXBYTES(mbc); 272 273 server_type = (referrals->i_type == DFS_OBJECT_ROOT) ? 274 DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT; 275 276 target = kmem_alloc(MAXPATHLEN, KM_SLEEP); 277 278 for (r = 0; r < referrals->i_ntargets; r++) { 279 (void) snprintf(target, MAXPATHLEN, "\\%s\\%s", 280 referrals->i_targets[r].t_server, 281 referrals->i_targets[r].t_share); 282 283 entsize = DFS_REFV1_ENTSZ + smb_wcequiv_strlen(target) + 2; 284 if (entsize > rep_bufsize) 285 break; 286 287 (void) smb_mbc_encodef(mbc, "wwwwU", 288 DFS_REFERRAL_V1, entsize, server_type, flags, target); 289 rep_bufsize -= entsize; 290 } 291 292 kmem_free(target, MAXPATHLEN); 293 294 /* 295 * Need room for at least one entry. 296 * Windows will silently drop targets that do not fit in 297 * the response buffer. 298 */ 299 if (r == 0) { 300 return (NT_STATUS_BUFFER_OVERFLOW); 301 } 302 303 return (NT_STATUS_SUCCESS); 304 } 305 306 /* 307 * Prepare a response with V2 referral format. 308 * 309 * Here is the response packet format. 310 * All the strings come after all the fixed size entry headers. 311 * These headers contain offsets to the strings at the end. Note 312 * that the two "dfs_path" after the last entry is shared between 313 * all the entries. 314 * 315 * ent1-hdr 316 * ent2-hdr 317 * ... 318 * entN-hdr 319 * dfs_path 320 * dfs_path 321 * target1 322 * target2 323 * ... 324 * targetN 325 * 326 * MS-DFSC mentions that strings can come after each entry header or all after 327 * the last entry header. Windows responses are in the format above. 328 */ 329 static uint32_t 330 smb_dfs_encode_refv2(smb_request_t *sr, mbuf_chain_t *mbc, 331 dfs_info_t *referrals) 332 { 333 _NOTE(ARGUNUSED(sr)) 334 uint16_t entsize, rep_bufsize; 335 uint16_t server_type; 336 uint16_t flags = 0; 337 uint32_t proximity = 0; 338 uint16_t path_offs, altpath_offs, netpath_offs; 339 uint16_t targetsz, total_targetsz = 0; 340 uint16_t dfs_pathsz; 341 uint16_t r; 342 343 rep_bufsize = MBC_MAXBYTES(mbc); 344 dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2; 345 entsize = DFS_REFV2_ENTSZ + dfs_pathsz + dfs_pathsz + 346 smb_dfs_referrals_unclen(referrals, 0); 347 348 if (entsize > rep_bufsize) { 349 /* need room for at least one referral */ 350 return (NT_STATUS_BUFFER_OVERFLOW); 351 } 352 353 server_type = (referrals->i_type == DFS_OBJECT_ROOT) ? 354 DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT; 355 356 rep_bufsize -= entsize; 357 entsize = DFS_REFV2_ENTSZ; 358 359 for (r = 0; r < referrals->i_ntargets; r++) { 360 path_offs = (referrals->i_ntargets - r) * DFS_REFV2_ENTSZ; 361 altpath_offs = path_offs + dfs_pathsz; 362 netpath_offs = altpath_offs + dfs_pathsz + total_targetsz; 363 targetsz = smb_dfs_referrals_unclen(referrals, r); 364 365 if (r != 0) { 366 entsize = DFS_REFV2_ENTSZ + targetsz; 367 if (entsize > rep_bufsize) 368 /* silently drop targets that do not fit */ 369 break; 370 rep_bufsize -= entsize; 371 } 372 373 (void) smb_mbc_encodef(mbc, "wwwwllwww", 374 DFS_REFERRAL_V2, DFS_REFV2_ENTSZ, server_type, flags, 375 proximity, referrals->i_timeout, path_offs, altpath_offs, 376 netpath_offs); 377 378 total_targetsz += targetsz; 379 } 380 381 smb_dfs_encode_targets(mbc, referrals); 382 383 return (NT_STATUS_SUCCESS); 384 } 385 386 /* 387 * Prepare a response with V3/V4 referral format. 388 * 389 * For more details, see comments for smb_dfs_encode_refv2() or see 390 * MS-DFSC specification. 391 */ 392 static uint32_t 393 smb_dfs_encode_refv3x(smb_request_t *sr, mbuf_chain_t *mbc, 394 dfs_info_t *referrals, 395 uint16_t ver) 396 { 397 _NOTE(ARGUNUSED(sr)) 398 uint16_t entsize, rep_bufsize, hdrsize; 399 uint16_t server_type; 400 uint16_t flags = 0; 401 uint16_t path_offs, altpath_offs, netpath_offs; 402 uint16_t targetsz, total_targetsz = 0; 403 uint16_t dfs_pathsz; 404 uint16_t r; 405 406 hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ; 407 rep_bufsize = MBC_MAXBYTES(mbc); 408 dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2; 409 entsize = hdrsize + dfs_pathsz + dfs_pathsz + 410 smb_dfs_referrals_unclen(referrals, 0); 411 412 if (entsize > rep_bufsize) { 413 /* need room for at least one referral */ 414 return (NT_STATUS_BUFFER_OVERFLOW); 415 } 416 417 server_type = (referrals->i_type == DFS_OBJECT_ROOT) ? 418 DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT; 419 420 rep_bufsize -= entsize; 421 422 for (r = 0; r < referrals->i_ntargets; r++) { 423 path_offs = (referrals->i_ntargets - r) * hdrsize; 424 altpath_offs = path_offs + dfs_pathsz; 425 netpath_offs = altpath_offs + dfs_pathsz + total_targetsz; 426 targetsz = smb_dfs_referrals_unclen(referrals, r); 427 428 if (r != 0) { 429 entsize = hdrsize + targetsz; 430 if (entsize > rep_bufsize) 431 /* silently drop targets that do not fit */ 432 break; 433 rep_bufsize -= entsize; 434 flags = 0; 435 } else if (ver == DFS_REFERRAL_V4) { 436 flags = DFS_ENTFLG_T; 437 } 438 439 (void) smb_mbc_encodef(mbc, "wwwwlwww16.", 440 ver, hdrsize, server_type, flags, 441 referrals->i_timeout, path_offs, altpath_offs, 442 netpath_offs); 443 444 total_targetsz += targetsz; 445 } 446 447 smb_dfs_encode_targets(mbc, referrals); 448 449 return (NT_STATUS_SUCCESS); 450 } 451 452 /* 453 * Encodes DFS path, and target strings which come after fixed header 454 * entries. 455 * 456 * Windows 2000 and earlier set the DFSAlternatePathOffset to point to 457 * an 8.3 string representation of the string pointed to by 458 * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if 459 * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset 460 * points to a separate copy of the same string. Windows Server 2003, 461 * Windows Server 2008 and Windows Server 2008 R2 set the 462 * DFSPathOffset and DFSAlternatePathOffset fields to point to separate 463 * copies of the identical string. 464 * 465 * Following Windows 2003 and later here. 466 */ 467 static void 468 smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals) 469 { 470 char *target; 471 int r; 472 473 (void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath, 474 referrals->i_uncpath); 475 476 target = kmem_alloc(MAXPATHLEN, KM_SLEEP); 477 for (r = 0; r < referrals->i_ntargets; r++) { 478 (void) snprintf(target, MAXPATHLEN, "\\%s\\%s", 479 referrals->i_targets[r].t_server, 480 referrals->i_targets[r].t_share); 481 (void) smb_mbc_encodef(mbc, "U", target); 482 } 483 kmem_free(target, MAXPATHLEN); 484 } 485 486 /* 487 * Get referral information for the specified path from user space 488 * using a door call. 489 */ 490 static uint32_t 491 smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype, 492 dfs_referral_response_t *refrsp) 493 { 494 dfs_referral_query_t req; 495 int rc; 496 497 req.rq_type = reftype; 498 req.rq_path = dfs_path; 499 500 bzero(refrsp, sizeof (dfs_referral_response_t)); 501 refrsp->rp_status = NT_STATUS_NOT_FOUND; 502 503 rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS, 504 &req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr); 505 506 if (rc != 0 || refrsp->rp_status != ERROR_SUCCESS) { 507 return (NT_STATUS_NO_SUCH_DEVICE); 508 } 509 510 (void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\'); 511 return (NT_STATUS_SUCCESS); 512 } 513 514 static void 515 smb_dfs_referrals_free(dfs_referral_response_t *refrsp) 516 { 517 xdr_free(dfs_referral_response_xdr, (char *)refrsp); 518 } 519 520 /* 521 * Returns the Unicode string length for the target UNC of 522 * the specified entry by 'refno' 523 * 524 * Note that the UNC path should be encoded with ONE leading 525 * slash not two as is common to user-visible UNC paths. 526 */ 527 static uint16_t 528 smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno) 529 { 530 uint16_t len; 531 532 if (refno >= referrals->i_ntargets) 533 return (0); 534 535 /* Encoded target UNC \server\share */ 536 len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) + 537 smb_wcequiv_strlen(referrals->i_targets[refno].t_share) + 538 smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */ 539 540 return (len); 541 } 542