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 #include <smbsrv/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  * Note: SMB1 callers in smb_trans2_dfs.c
  88  * smb_com_trans2_report_dfs_inconsistency
  89  * smb_com_trans2_get_dfs_referral
  90  */
  91 
  92 /*
  93  * See [MS-DFSC] for details about this command
  94  */
  95 uint32_t
  96 smb_dfs_get_referrals(smb_request_t *sr, smb_fsctl_t *fsctl)
  97 {
  98         dfs_info_t *referrals;
  99         dfs_referral_response_t refrsp;
 100         dfs_reftype_t reftype;
 101         char *path;
 102         uint16_t maxver;
 103         uint32_t status;
 104         int rc;
 105 
 106         /*
 107          * The caller checks this, because the error reporting method
 108          * varies across SMB versions.
 109          */
 110         ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
 111 
 112         /*
 113          * XXX Instead of decoding the referral request and encoding
 114          * the response here (in-kernel) we could pass the given
 115          * request buffer in our door call, and let that return the
 116          * response buffer ready to stuff into out_mbc.  That would
 117          * allow all this decoding/encoding to happen at user-level.
 118          * (and most of this file would go away. :-)
 119          */
 120         switch (fsctl->CtlCode) {
 121         case FSCTL_DFS_GET_REFERRALS:
 122                 /*
 123                  * Input data is (w) MaxReferralLevel, (U) path
 124                  */
 125                 rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
 126                     sr, &maxver, &path);
 127                 if (rc != 0)
 128                         return (NT_STATUS_INVALID_PARAMETER);
 129                 break;
 130 
 131         case FSCTL_DFS_GET_REFERRALS_EX: /* XXX - todo */
 132         default:
 133                 return (NT_STATUS_NOT_SUPPORTED);
 134         }
 135 
 136         reftype = smb_dfs_get_reftype((const char *)path);
 137         switch (reftype) {
 138         case DFS_REFERRAL_INVALID:
 139                 /* Need to check the error for this case */
 140                 return (NT_STATUS_INVALID_PARAMETER);
 141 
 142         case DFS_REFERRAL_DOMAIN:
 143         case DFS_REFERRAL_DC:
 144                 /* MS-DFSC: this error is returned by non-DC root */
 145                 return (NT_STATUS_INVALID_PARAMETER);
 146 
 147         case DFS_REFERRAL_SYSVOL:
 148                 /* MS-DFSC: this error is returned by non-DC root */
 149                 return (NT_STATUS_NO_SUCH_DEVICE);
 150 
 151         default:
 152                 break;
 153         }
 154 
 155         status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
 156         if (status != NT_STATUS_SUCCESS)
 157                 return (status);
 158 
 159         referrals = &refrsp.rp_referrals;
 160         smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
 161 
 162         /*
 163          * Server may respond with any referral version at or below
 164          * the maximum specified in the request.
 165          */
 166         switch (maxver) {
 167         case DFS_REFERRAL_V1:
 168                 status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
 169                 break;
 170 
 171         case DFS_REFERRAL_V2:
 172                 status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
 173                 break;
 174 
 175         case DFS_REFERRAL_V3:
 176                 status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
 177                     DFS_REFERRAL_V3);
 178                 break;
 179 
 180         case DFS_REFERRAL_V4:
 181         default:
 182                 status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
 183                     DFS_REFERRAL_V4);
 184                 break;
 185         }
 186 
 187         smb_dfs_referrals_free(&refrsp);
 188 
 189         return (status);
 190 }
 191 
 192 /*
 193  * [MS-DFSC]: REQ_GET_DFS_REFERRAL
 194  *
 195  * Determines the referral type based on the specified path:
 196  *
 197  * Domain referral:
 198  *    ""
 199  *
 200  * DC referral:
 201  *    \<domain>
 202  *
 203  * Sysvol referral:
 204  *    \<domain>\SYSVOL
 205  *    \<domain>\NETLOGON
 206  *
 207  * Root referral:
 208  *    \<domain>\<dfsname>
 209  *    \<server>\<dfsname>
 210  *
 211  * Link referral:
 212  *    \<domain>\<dfsname>\<linkpath>
 213  *    \<server>\<dfsname>\<linkpath>
 214  */
 215 static dfs_reftype_t
 216 smb_dfs_get_reftype(const char *path)
 217 {
 218         smb_unc_t unc;
 219         dfs_reftype_t reftype = 0;
 220 
 221         if (*path == '\0')
 222                 return (DFS_REFERRAL_DOMAIN);
 223 
 224         if (smb_unc_init(path, &unc) != 0)
 225                 return (DFS_REFERRAL_INVALID);
 226 
 227         if (unc.unc_path != NULL) {
 228                 reftype = DFS_REFERRAL_LINK;
 229         } else if (unc.unc_share != NULL) {
 230                 if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) ||
 231                     (smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) {
 232                         reftype = DFS_REFERRAL_SYSVOL;
 233                 } else {
 234                         reftype = DFS_REFERRAL_ROOT;
 235                 }
 236         } else if (unc.unc_server != NULL) {
 237                 reftype = DFS_REFERRAL_DC;
 238         }
 239 
 240         smb_unc_free(&unc);
 241         return (reftype);
 242 }
 243 
 244 static void
 245 smb_dfs_encode_hdr(mbuf_chain_t *mbc, dfs_info_t *referrals)
 246 {
 247         uint16_t path_consumed;
 248         uint32_t flags;
 249 
 250         path_consumed = smb_wcequiv_strlen(referrals->i_uncpath);
 251         flags = DFS_HDRFLG_S;
 252         if (referrals->i_type == DFS_OBJECT_ROOT)
 253                 flags |= DFS_HDRFLG_R;
 254 
 255         /* Fill rep_param_mb in SMB1 caller. */
 256         (void) smb_mbc_encodef(mbc, "wwl", path_consumed,
 257             referrals->i_ntargets, flags);
 258 }
 259 
 260 static uint32_t
 261 smb_dfs_encode_refv1(smb_request_t *sr, mbuf_chain_t *mbc,
 262         dfs_info_t *referrals)
 263 {
 264         _NOTE(ARGUNUSED(sr))
 265         uint16_t entsize, rep_bufsize;
 266         uint16_t server_type;
 267         uint16_t flags = 0;
 268         uint16_t r;
 269         char *target;
 270 
 271         rep_bufsize = MBC_MAXBYTES(mbc);
 272 
 273         server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
 274             DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
 275 
 276         target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
 277 
 278         for (r = 0; r < referrals->i_ntargets; r++) {
 279                 (void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
 280                     referrals->i_targets[r].t_server,
 281                     referrals->i_targets[r].t_share);
 282 
 283                 entsize = DFS_REFV1_ENTSZ + smb_wcequiv_strlen(target) + 2;
 284                 if (entsize > rep_bufsize)
 285                         break;
 286 
 287                 (void) smb_mbc_encodef(mbc, "wwwwU",
 288                     DFS_REFERRAL_V1, entsize, server_type, flags, target);
 289                 rep_bufsize -= entsize;
 290         }
 291 
 292         kmem_free(target, MAXPATHLEN);
 293 
 294         /*
 295          * Need room for at least one entry.
 296          * Windows will silently drop targets that do not fit in
 297          * the response buffer.
 298          */
 299         if (r == 0) {
 300                 return (NT_STATUS_BUFFER_OVERFLOW);
 301         }
 302 
 303         return (NT_STATUS_SUCCESS);
 304 }
 305 
 306 /*
 307  * Prepare a response with V2 referral format.
 308  *
 309  * Here is the response packet format.
 310  * All the strings come after all the fixed size entry headers.
 311  * These headers contain offsets to the strings at the end. Note
 312  * that the two "dfs_path" after the last entry is shared between
 313  * all the entries.
 314  *
 315  * ent1-hdr
 316  * ent2-hdr
 317  * ...
 318  * entN-hdr
 319  *   dfs_path
 320  *   dfs_path
 321  *   target1
 322  *   target2
 323  *   ...
 324  *   targetN
 325  *
 326  * MS-DFSC mentions that strings can come after each entry header or all after
 327  * the last entry header. Windows responses are in the format above.
 328  */
 329 static uint32_t
 330 smb_dfs_encode_refv2(smb_request_t *sr, mbuf_chain_t *mbc,
 331         dfs_info_t *referrals)
 332 {
 333         _NOTE(ARGUNUSED(sr))
 334         uint16_t entsize, rep_bufsize;
 335         uint16_t server_type;
 336         uint16_t flags = 0;
 337         uint32_t proximity = 0;
 338         uint16_t path_offs, altpath_offs, netpath_offs;
 339         uint16_t targetsz, total_targetsz = 0;
 340         uint16_t dfs_pathsz;
 341         uint16_t r;
 342 
 343         rep_bufsize = MBC_MAXBYTES(mbc);
 344         dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
 345         entsize = DFS_REFV2_ENTSZ + dfs_pathsz + dfs_pathsz +
 346             smb_dfs_referrals_unclen(referrals, 0);
 347 
 348         if (entsize > rep_bufsize) {
 349                 /* need room for at least one referral */
 350                 return (NT_STATUS_BUFFER_OVERFLOW);
 351         }
 352 
 353         server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
 354             DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
 355 
 356         rep_bufsize -= entsize;
 357         entsize = DFS_REFV2_ENTSZ;
 358 
 359         for (r = 0; r < referrals->i_ntargets; r++) {
 360                 path_offs = (referrals->i_ntargets - r) * DFS_REFV2_ENTSZ;
 361                 altpath_offs = path_offs + dfs_pathsz;
 362                 netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
 363                 targetsz = smb_dfs_referrals_unclen(referrals, r);
 364 
 365                 if (r != 0) {
 366                         entsize = DFS_REFV2_ENTSZ + targetsz;
 367                         if (entsize > rep_bufsize)
 368                                 /* silently drop targets that do not fit */
 369                                 break;
 370                         rep_bufsize -= entsize;
 371                 }
 372 
 373                 (void) smb_mbc_encodef(mbc, "wwwwllwww",
 374                     DFS_REFERRAL_V2, DFS_REFV2_ENTSZ, server_type, flags,
 375                     proximity, referrals->i_timeout, path_offs, altpath_offs,
 376                     netpath_offs);
 377 
 378                 total_targetsz += targetsz;
 379         }
 380 
 381         smb_dfs_encode_targets(mbc, referrals);
 382 
 383         return (NT_STATUS_SUCCESS);
 384 }
 385 
 386 /*
 387  * Prepare a response with V3/V4 referral format.
 388  *
 389  * For more details, see comments for smb_dfs_encode_refv2() or see
 390  * MS-DFSC specification.
 391  */
 392 static uint32_t
 393 smb_dfs_encode_refv3x(smb_request_t *sr, mbuf_chain_t *mbc,
 394         dfs_info_t *referrals,
 395     uint16_t ver)
 396 {
 397         _NOTE(ARGUNUSED(sr))
 398         uint16_t entsize, rep_bufsize, hdrsize;
 399         uint16_t server_type;
 400         uint16_t flags = 0;
 401         uint16_t path_offs, altpath_offs, netpath_offs;
 402         uint16_t targetsz, total_targetsz = 0;
 403         uint16_t dfs_pathsz;
 404         uint16_t r;
 405 
 406         hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ;
 407         rep_bufsize = MBC_MAXBYTES(mbc);
 408         dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
 409         entsize = hdrsize + dfs_pathsz + dfs_pathsz +
 410             smb_dfs_referrals_unclen(referrals, 0);
 411 
 412         if (entsize > rep_bufsize) {
 413                 /* need room for at least one referral */
 414                 return (NT_STATUS_BUFFER_OVERFLOW);
 415         }
 416 
 417         server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
 418             DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
 419 
 420         rep_bufsize -= entsize;
 421 
 422         for (r = 0; r < referrals->i_ntargets; r++) {
 423                 path_offs = (referrals->i_ntargets - r) * hdrsize;
 424                 altpath_offs = path_offs + dfs_pathsz;
 425                 netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
 426                 targetsz = smb_dfs_referrals_unclen(referrals, r);
 427 
 428                 if (r != 0) {
 429                         entsize = hdrsize + targetsz;
 430                         if (entsize > rep_bufsize)
 431                                 /* silently drop targets that do not fit */
 432                                 break;
 433                         rep_bufsize -= entsize;
 434                         flags = 0;
 435                 } else if (ver == DFS_REFERRAL_V4) {
 436                         flags = DFS_ENTFLG_T;
 437                 }
 438 
 439                 (void) smb_mbc_encodef(mbc, "wwwwlwww16.",
 440                     ver, hdrsize, server_type, flags,
 441                     referrals->i_timeout, path_offs, altpath_offs,
 442                     netpath_offs);
 443 
 444                 total_targetsz += targetsz;
 445         }
 446 
 447         smb_dfs_encode_targets(mbc, referrals);
 448 
 449         return (NT_STATUS_SUCCESS);
 450 }
 451 
 452 /*
 453  * Encodes DFS path, and target strings which come after fixed header
 454  * entries.
 455  *
 456  * Windows 2000 and earlier set the DFSAlternatePathOffset to point to
 457  * an 8.3 string representation of the string pointed to by
 458  * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if
 459  * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset
 460  * points to a separate copy of the same string. Windows Server 2003,
 461  * Windows Server 2008 and Windows Server 2008 R2 set the
 462  * DFSPathOffset and DFSAlternatePathOffset fields to point to separate
 463  * copies of the identical string.
 464  *
 465  * Following Windows 2003 and later here.
 466  */
 467 static void
 468 smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals)
 469 {
 470         char *target;
 471         int r;
 472 
 473         (void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath,
 474             referrals->i_uncpath);
 475 
 476         target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
 477         for (r = 0; r < referrals->i_ntargets; r++) {
 478                 (void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
 479                     referrals->i_targets[r].t_server,
 480                     referrals->i_targets[r].t_share);
 481                 (void) smb_mbc_encodef(mbc, "U", target);
 482         }
 483         kmem_free(target, MAXPATHLEN);
 484 }
 485 
 486 /*
 487  * Get referral information for the specified path from user space
 488  * using a door call.
 489  */
 490 static uint32_t
 491 smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype,
 492     dfs_referral_response_t *refrsp)
 493 {
 494         dfs_referral_query_t    req;
 495         int                     rc;
 496 
 497         req.rq_type = reftype;
 498         req.rq_path = dfs_path;
 499 
 500         bzero(refrsp, sizeof (dfs_referral_response_t));
 501         refrsp->rp_status = NT_STATUS_NOT_FOUND;
 502 
 503         rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS,
 504             &req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr);
 505 
 506         if (rc != 0 || refrsp->rp_status != ERROR_SUCCESS) {
 507                 return (NT_STATUS_NO_SUCH_DEVICE);
 508         }
 509 
 510         (void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\');
 511         return (NT_STATUS_SUCCESS);
 512 }
 513 
 514 static void
 515 smb_dfs_referrals_free(dfs_referral_response_t *refrsp)
 516 {
 517         xdr_free(dfs_referral_response_xdr, (char *)refrsp);
 518 }
 519 
 520 /*
 521  * Returns the Unicode string length for the target UNC of
 522  * the specified entry by 'refno'
 523  *
 524  * Note that the UNC path should be encoded with ONE leading
 525  * slash not two as is common to user-visible UNC paths.
 526  */
 527 static uint16_t
 528 smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno)
 529 {
 530         uint16_t len;
 531 
 532         if (refno >= referrals->i_ntargets)
 533                 return (0);
 534 
 535         /* Encoded target UNC \server\share */
 536         len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) +
 537             smb_wcequiv_strlen(referrals->i_targets[refno].t_share) +
 538             smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */
 539 
 540         return (len);
 541 }