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