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, 413 uint16_t ver) 414 { 415 _NOTE(ARGUNUSED(sr)) 416 uint16_t entsize, rep_bufsize, hdrsize; 417 uint16_t server_type; 418 uint16_t flags = 0; 419 uint16_t path_offs, altpath_offs, netpath_offs; 420 uint16_t targetsz, total_targetsz = 0; 421 uint16_t dfs_pathsz; 422 uint16_t r; 423 424 hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ; 425 rep_bufsize = MBC_MAXBYTES(mbc); 426 dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2; 427 entsize = hdrsize + dfs_pathsz + dfs_pathsz + 428 smb_dfs_referrals_unclen(referrals, 0); 429 430 if (entsize > rep_bufsize) { 431 /* need room for at least one referral */ 432 return (NT_STATUS_BUFFER_OVERFLOW); 433 } 434 435 server_type = (referrals->i_type == DFS_OBJECT_ROOT) ? 436 DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT; 437 438 rep_bufsize -= entsize; 439 440 for (r = 0; r < referrals->i_ntargets; r++) { 441 path_offs = (referrals->i_ntargets - r) * hdrsize; 442 altpath_offs = path_offs + dfs_pathsz; 443 netpath_offs = altpath_offs + dfs_pathsz + total_targetsz; 444 targetsz = smb_dfs_referrals_unclen(referrals, r); 445 446 if (r != 0) { 447 entsize = hdrsize + targetsz; 448 if (entsize > rep_bufsize) 449 /* silently drop targets that do not fit */ 450 break; 451 rep_bufsize -= entsize; 452 flags = 0; 453 } else if (ver == DFS_REFERRAL_V4) { 454 flags = DFS_ENTFLG_T; 455 } 456 457 (void) smb_mbc_encodef(mbc, "wwwwlwww16.", 458 ver, hdrsize, server_type, flags, 459 referrals->i_timeout, path_offs, altpath_offs, 460 netpath_offs); 461 462 total_targetsz += targetsz; 463 } 464 465 smb_dfs_encode_targets(mbc, referrals); 466 467 return (NT_STATUS_SUCCESS); 468 } 469 470 /* 471 * Encodes DFS path, and target strings which come after fixed header 472 * entries. 473 * 474 * Windows 2000 and earlier set the DFSAlternatePathOffset to point to 475 * an 8.3 string representation of the string pointed to by 476 * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if 477 * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset 478 * points to a separate copy of the same string. Windows Server 2003, 479 * Windows Server 2008 and Windows Server 2008 R2 set the 480 * DFSPathOffset and DFSAlternatePathOffset fields to point to separate 481 * copies of the identical string. 482 * 483 * Following Windows 2003 and later here. 484 */ 485 static void 486 smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals) 487 { 488 char *target; 489 int r; 490 491 (void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath, 492 referrals->i_uncpath); 493 494 target = kmem_alloc(MAXPATHLEN, KM_SLEEP); 495 for (r = 0; r < referrals->i_ntargets; r++) { 496 (void) snprintf(target, MAXPATHLEN, "\\%s\\%s", 497 referrals->i_targets[r].t_server, 498 referrals->i_targets[r].t_share); 499 (void) smb_mbc_encodef(mbc, "U", target); 500 } 501 kmem_free(target, MAXPATHLEN); 502 } 503 504 /* 505 * Get referral information for the specified path from user space 506 * using a door call. 507 */ 508 static uint32_t 509 smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype, 510 dfs_referral_response_t *refrsp) 511 { 512 dfs_referral_query_t req; 513 int rc; 514 515 req.rq_type = reftype; 516 req.rq_path = dfs_path; 517 518 bzero(refrsp, sizeof (dfs_referral_response_t)); 519 refrsp->rp_status = NT_STATUS_NOT_FOUND; 520 521 rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS, 522 &req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr); 523 524 if (rc != 0 || refrsp->rp_status != ERROR_SUCCESS) { 525 return (NT_STATUS_NO_SUCH_DEVICE); 526 } 527 528 (void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\'); 529 return (NT_STATUS_SUCCESS); 530 } 531 532 static void 533 smb_dfs_referrals_free(dfs_referral_response_t *refrsp) 534 { 535 xdr_free(dfs_referral_response_xdr, (char *)refrsp); 536 } 537 538 /* 539 * Returns the Unicode string length for the target UNC of 540 * the specified entry by 'refno' 541 * 542 * Note that the UNC path should be encoded with ONE leading 543 * slash not two as is common to user-visible UNC paths. 544 */ 545 static uint16_t 546 smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno) 547 { 548 uint16_t len; 549 550 if (refno >= referrals->i_ntargets) 551 return (0); 552 553 /* Encoded target UNC \server\share */ 554 len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) + 555 smb_wcequiv_strlen(referrals->i_targets[refno].t_share) + 556 smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */ 557 558 return (len); 559 } 560