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 }