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