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