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