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