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 2014 Nexenta Systems, Inc.  All rights reserved.
  26  */
  27 
  28 /*
  29  * Dispatch function for SMB2_QUERY_DIRECTORY
  30  *
  31  * Similar to smb_trans2_find.c (from SMB1)
  32  */
  33 
  34 #include <smbsrv/smb2_kproto.h>
  35 
  36 /*
  37  * Args (and other state) that we carry around among the
  38  * various functions involved in SMB2 Query Directory.
  39  */
  40 typedef struct smb2_find_args {
  41         uint32_t fa_maxdata;
  42         uint8_t fa_infoclass;
  43         uint8_t fa_fflags;
  44         uint16_t fa_maxcount;
  45         uint16_t fa_eos;        /* End Of Search */
  46         uint16_t fa_fixedsize;  /* size of fixed part of a returned entry */
  47         uint32_t fa_lastkey;    /* Last resume key */
  48         int fa_last_entry;      /* offset of last entry */
  49 } smb2_find_args_t;
  50 
  51 static uint32_t smb2_find_entries(smb_request_t *,
  52     smb_odir_t *, smb2_find_args_t *);
  53 static uint32_t smb2_find_mbc_encode(smb_request_t *,
  54     smb_fileinfo_t *, smb2_find_args_t *);
  55 
  56 /*
  57  * Tunable parameter to limit the maximum
  58  * number of entries to be returned.
  59  */
  60 uint16_t smb2_find_max = 128;
  61 
  62 smb_sdrc_t
  63 smb2_query_dir(smb_request_t *sr)
  64 {
  65         smb2_find_args_t                args;
  66         smb_odir_resume_t       odir_resume;
  67         smb_ofile_t *of = NULL;
  68         smb_odir_t *od = NULL;
  69         char *pattern = NULL;
  70         uint16_t StructSize;
  71         uint32_t FileIndex;
  72         uint16_t NameOffset;
  73         uint16_t NameLength;
  74         smb2fid_t smb2fid;
  75         uint16_t sattr = SMB_SEARCH_ATTRIBUTES;
  76         uint16_t DataOff;
  77         uint32_t DataLen;
  78         uint32_t status;
  79         int skip, rc = 0;
  80 
  81         bzero(&args, sizeof (args));
  82         bzero(&odir_resume, sizeof (odir_resume));
  83 
  84         /*
  85          * SMB2 Query Directory request
  86          */
  87         rc = smb_mbc_decodef(
  88             &sr->smb_data, "wbblqqwwl",
  89             &StructSize,            /* w */
  90             &args.fa_infoclass,             /* b */
  91             &args.fa_fflags,                /* b */
  92             &FileIndex,                     /* l */
  93             &smb2fid.persistent,    /* q */
  94             &smb2fid.temporal,              /* q */
  95             &NameOffset,            /* w */
  96             &NameLength,            /* w */
  97             &args.fa_maxdata);              /* l */
  98         if (rc || StructSize != 33)
  99                 return (SDRC_ERROR);
 100 
 101         status = smb2sr_lookup_fid(sr, &smb2fid);
 102         if (status)
 103                 goto errout;
 104         of = sr->fid_ofile;
 105 
 106         /*
 107          * If there's an input buffer (search pattern), decode it.
 108          * Two times MAXNAMELEN because it represents the UNICODE string
 109          * length in bytes.
 110          */
 111         if (NameLength >= (2 * MAXNAMELEN)) {
 112                 status = NT_STATUS_OBJECT_PATH_INVALID;
 113                 goto errout;
 114         }
 115         if (NameLength != 0) {
 116                 /*
 117                  * We're normally positioned at the pattern now,
 118                  * but there could be some padding before it.
 119                  */
 120                 skip = (sr->smb2_cmd_hdr + NameOffset) -
 121                     sr->smb_data.chain_offset;
 122                 if (skip < 0) {
 123                         status = NT_STATUS_OBJECT_PATH_INVALID;
 124                         goto errout;
 125                 }
 126                 if (skip > 0)
 127                         (void) smb_mbc_decodef(&sr->smb_data, "#.", skip);
 128                 rc = smb_mbc_decodef(&sr->smb_data, "%#U", sr,
 129                     NameLength, &pattern);
 130                 if (rc || pattern == NULL) {
 131                         status = NT_STATUS_OBJECT_PATH_INVALID;
 132                         goto errout;
 133                 }
 134         } else
 135                 pattern = "*";
 136 
 137         /*
 138          * Setup the output buffer.
 139          */
 140         if (args.fa_maxdata > smb2_max_trans)
 141                 args.fa_maxdata = smb2_max_trans;
 142         sr->raw_data.max_bytes = args.fa_maxdata;
 143 
 144         /*
 145          * Get the mininum size of entries we will return, which
 146          * lets us estimate the number of entries we'll need.
 147          * This should be the size with a one character name.
 148          * Compare w/ smb2_find_get_maxdata().
 149          *
 150          * Also use this opportunity to validate fa_infoclass.
 151          */
 152 
 153         switch (args.fa_infoclass) {
 154         case FileDirectoryInformation:          /* 1 */
 155                 args.fa_fixedsize = 64;
 156                 break;
 157         case FileFullDirectoryInformation:      /* 2 */
 158                 args.fa_fixedsize = 68;
 159                 break;
 160         case FileBothDirectoryInformation:      /* 3 */
 161                 args.fa_fixedsize = 94;
 162                 break;
 163         case FileNamesInformation:              /* 12 */
 164                 args.fa_fixedsize = 12;
 165                 break;
 166         case FileIdBothDirectoryInformation:    /* 37 */
 167                 args.fa_fixedsize = 96;
 168                 break;
 169         case FileIdFullDirectoryInformation:    /* 38 */
 170                 args.fa_fixedsize = 84;
 171                 break;
 172         default:
 173                 status = NT_STATUS_INVALID_INFO_CLASS;
 174                 goto errout;
 175         }
 176 
 177         args.fa_maxcount = args.fa_maxdata / (args.fa_fixedsize + 4);
 178         if (args.fa_maxcount == 0)
 179                 args.fa_maxcount = 1;
 180         if ((smb2_find_max != 0) && (args.fa_maxcount > smb2_find_max))
 181                 args.fa_maxcount = smb2_find_max;
 182         if (args.fa_fflags & SMB2_QDIR_FLAG_SINGLE)
 183                 args.fa_maxcount = 1;
 184 
 185         /*
 186          * If this ofile does not have an odir yet, get one.
 187          */
 188         mutex_enter(&of->f_mutex);
 189         if ((od = of->f_odir) == NULL) {
 190                 status = smb_odir_openfh(sr, pattern, sattr, &od);
 191                 of->f_odir = od;
 192         }
 193         mutex_exit(&of->f_mutex);
 194         if (od == NULL) {
 195                 if (status == 0)
 196                         status = NT_STATUS_INTERNAL_ERROR;
 197                 goto errout;
 198         }
 199 
 200         /*
 201          * "Reopen" sets a new pattern and restart.
 202          */
 203         if (args.fa_fflags & SMB2_QDIR_FLAG_REOPEN) {
 204                 smb_odir_reopen(od, pattern, sattr);
 205         }
 206 
 207         /*
 208          * Set the correct position in the directory.
 209          */
 210         if (args.fa_fflags & SMB2_QDIR_FLAG_RESTART) {
 211                 odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
 212                 odir_resume.or_cookie = 0;
 213         } else if (args.fa_fflags & SMB2_QDIR_FLAG_INDEX) {
 214                 odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
 215                 odir_resume.or_cookie = FileIndex;
 216         } else {
 217                 odir_resume.or_type = SMB_ODIR_RESUME_CONT;
 218         }
 219         smb_odir_resume_at(od, &odir_resume);
 220         of->f_seek_pos = od->d_offset;
 221 
 222         /*
 223          * The real work of readdir and format conversion.
 224          */
 225         status = smb2_find_entries(sr, od, &args);
 226 
 227         of->f_seek_pos = od->d_offset;
 228 
 229         if (status == NT_STATUS_NO_MORE_FILES) {
 230                 if (args.fa_fflags & SMB2_QDIR_FLAG_SINGLE) {
 231                         status = NT_STATUS_NO_SUCH_FILE;
 232                         goto errout;
 233                 }
 234                 /*
 235                  * This is not an error, but a warning that can be
 236                  * used to tell the client that this data return
 237                  * is the last of the enumeration.  Returning this
 238                  * warning now (with the data) saves the client a
 239                  * round trip that would otherwise be needed to
 240                  * find out it's at the end.
 241                  */
 242                 sr->smb2_status = status;
 243                 status = 0;
 244         }
 245         if (status)
 246                 goto errout;
 247 
 248         /*
 249          * SMB2 Query Directory reply
 250          */
 251         StructSize = 9;
 252         DataOff = SMB2_HDR_SIZE + 8;
 253         DataLen = MBC_LENGTH(&sr->raw_data);
 254         rc = smb_mbc_encodef(
 255             &sr->reply, "wwlC",
 256             StructSize,         /* w */
 257             DataOff,            /* w */
 258             DataLen,            /* l */
 259             &sr->raw_data);      /* C */
 260         if (DataLen == 0)
 261                 (void) smb_mbc_encodef(&sr->reply, ".");
 262         if (rc == 0)
 263                 return (SDRC_SUCCESS);
 264         status = NT_STATUS_UNSUCCESSFUL;
 265 
 266 errout:
 267         smb2sr_put_error(sr, status);
 268         return (SDRC_SUCCESS);
 269 }
 270 
 271 /*
 272  * smb2_find_entries
 273  *
 274  * Find and encode up to args->fa_maxcount directory entries.
 275  *
 276  * Returns:
 277  *   NT status
 278  */
 279 static uint32_t
 280 smb2_find_entries(smb_request_t *sr, smb_odir_t *od, smb2_find_args_t *args)
 281 {
 282         smb_fileinfo_t  fileinfo;
 283         smb_odir_resume_t odir_resume;
 284         uint16_t        count;
 285         uint16_t        minsize;
 286         uint32_t        status = 0;
 287         int             rc = -1;
 288 
 289         /*
 290          * Let's stop when the remaining space will not hold a
 291          * minimum size entry.  That's the fixed part plus the
 292          * storage size of a 1 char unicode string.
 293          */
 294         minsize = args->fa_fixedsize + 2;
 295 
 296         count = 0;
 297         while (count < args->fa_maxcount) {
 298 
 299                 if (!MBC_ROOM_FOR(&sr->raw_data, minsize)) {
 300                         status = NT_STATUS_BUFFER_OVERFLOW;
 301                         break;
 302                 }
 303 
 304                 rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &args->fa_eos);
 305                 if (rc == ENOENT) {
 306                         status = NT_STATUS_NO_MORE_FILES;
 307                         break;
 308                 }
 309                 if (rc != 0) {
 310                         status = smb_errno2status(rc);
 311                         break;
 312                 }
 313                 if (args->fa_eos != 0) {
 314                         /* The readdir call hit the end. */
 315                         status = NT_STATUS_NO_MORE_FILES;
 316                         break;
 317                 }
 318 
 319                 status = smb2_find_mbc_encode(sr, &fileinfo, args);
 320                 if (status) {
 321                         /*
 322                          * We read a directory entry but failed to
 323                          * copy it into the output buffer.  Rewind
 324                          * the directory pointer so this will be
 325                          * the first entry read next time.
 326                          */
 327                         bzero(&odir_resume, sizeof (odir_resume));
 328                         odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
 329                         odir_resume.or_cookie = args->fa_lastkey;
 330                         smb_odir_resume_at(od, &odir_resume);
 331                         break;
 332                 }
 333 
 334                 /*
 335                  * Save the offset of the next entry we'll read.
 336                  * If we fail copying, we'll need this offset.
 337                  */
 338                 args->fa_lastkey = fileinfo.fi_cookie;
 339                 ++count;
 340         }
 341 
 342         if (count == 0) {
 343                 ASSERT(status != 0);
 344         } else {
 345                 /*
 346                  * We copied some directory entries, but stopped for
 347                  * NT_STATUS_NO_MORE_FILES, or something.
 348                  *
 349                  * Per [MS-FSCC] sec. 2.4, the last entry in the
 350                  * enumeration MUST have its NextEntryOffset value
 351                  * set to zero.  Overwrite that in the last entry.
 352                  */
 353                 (void) smb_mbc_poke(&sr->raw_data,
 354                     args->fa_last_entry, "l", 0);
 355                 status = 0;
 356         }
 357 
 358         return (status);
 359 }
 360 
 361 /*
 362  * smb2_mbc_encode
 363  *
 364  * This function encodes the mbc for one directory entry.
 365  *
 366  * The function returns -1 when the max data requested by client
 367  * is reached. If the entry is valid and successful encoded, 0
 368  * will be returned; otherwise, 1 will be returned.
 369  *
 370  * We always null terminate the filename. The space for the null
 371  * is included in the maxdata calculation and is therefore included
 372  * in the next_entry_offset. namelen is the unterminated length of
 373  * the filename. For levels except STANDARD and EA_SIZE, if the
 374  * filename is ascii the name length returned to the client should
 375  * include the null terminator. Otherwise the length returned to
 376  * the client should not include the terminator.
 377  *
 378  * Returns: 0 - data successfully encoded
 379  *      NT status
 380  */
 381 static uint32_t
 382 smb2_find_mbc_encode(smb_request_t *sr, smb_fileinfo_t *fileinfo,
 383         smb2_find_args_t *args)
 384 {
 385         uint8_t         buf83[26];
 386         smb_msgbuf_t    mb;
 387         int             namelen, padsz;
 388         int             shortlen = 0;
 389         int             rc, starting_offset;
 390         uint32_t        next_entry_offset;
 391         uint32_t        mb_flags = SMB_MSGBUF_UNICODE;
 392         uint32_t        resume_key;
 393 
 394         namelen = smb_wcequiv_strlen(fileinfo->fi_name);
 395         if (namelen == -1)
 396                 return (NT_STATUS_INTERNAL_ERROR);
 397 
 398         /*
 399          * Keep track of where the last entry starts so we can
 400          * come back and poke the NextEntryOffset field.  Also,
 401          * after enumeration finishes, the caller uses this to
 402          * poke the last entry again with zero to mark it as
 403          * the end of the enumeration.
 404          */
 405         starting_offset = sr->raw_data.chain_offset;
 406 
 407         /*
 408          * Technically (per MS-SMB2) resume keys are optional.
 409          * Windows doesn't need them, but MacOS does.
 410          */
 411         resume_key = fileinfo->fi_cookie;
 412 
 413         /*
 414          * This switch handles all the "information levels" (formats)
 415          * that we support.  Note that all formats have the file name
 416          * placed after some fixed-size data, and the code to write
 417          * the file name is factored out at the end of this switch.
 418          */
 419         switch (args->fa_infoclass) {
 420 
 421         /* See also: SMB_FIND_FILE_DIRECTORY_INFO */
 422         case FileDirectoryInformation:          /* 1 */
 423                 rc = smb_mbc_encodef(
 424                     &sr->raw_data, "llTTTTqqll",
 425                     0,  /* NextEntryOffset (set later) */
 426                     resume_key,
 427                     &fileinfo->fi_crtime,
 428                     &fileinfo->fi_atime,
 429                     &fileinfo->fi_mtime,
 430                     &fileinfo->fi_ctime,
 431                     fileinfo->fi_size,
 432                     fileinfo->fi_alloc_size,
 433                     fileinfo->fi_dosattr,
 434                     namelen);
 435                 break;
 436 
 437         /* See also: SMB_FIND_FILE_FULL_DIRECTORY_INFO */
 438         case FileFullDirectoryInformation:      /* 2 */
 439                 rc = smb_mbc_encodef(
 440                     &sr->raw_data, "llTTTTqqlll",
 441                     0,  /* NextEntryOffset (set later) */
 442                     resume_key,
 443                     &fileinfo->fi_crtime,
 444                     &fileinfo->fi_atime,
 445                     &fileinfo->fi_mtime,
 446                     &fileinfo->fi_ctime,
 447                     fileinfo->fi_size,
 448                     fileinfo->fi_alloc_size,
 449                     fileinfo->fi_dosattr,
 450                     namelen,
 451                     0L);        /* EaSize */
 452                 break;
 453 
 454         /* See also: SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO */
 455         case FileIdFullDirectoryInformation:    /* 38 */
 456                 rc = smb_mbc_encodef(
 457                     &sr->raw_data, "llTTTTqqllllq",
 458                     0,  /* NextEntryOffset (set later) */
 459                     resume_key,
 460                     &fileinfo->fi_crtime,
 461                     &fileinfo->fi_atime,
 462                     &fileinfo->fi_mtime,
 463                     &fileinfo->fi_ctime,
 464                     fileinfo->fi_size,
 465                     fileinfo->fi_alloc_size,
 466                     fileinfo->fi_dosattr,
 467                     namelen,
 468                     0L,         /* EaSize */
 469                     0L,         /* reserved */
 470                     fileinfo->fi_nodeid);
 471                 break;
 472 
 473         /* See also: SMB_FIND_FILE_BOTH_DIRECTORY_INFO */
 474         case FileBothDirectoryInformation:      /* 3 */
 475                 bzero(buf83, sizeof (buf83));
 476                 smb_msgbuf_init(&mb, buf83, sizeof (buf83), mb_flags);
 477                 if (!smb_msgbuf_encode(&mb, "U", fileinfo->fi_shortname))
 478                         shortlen = smb_wcequiv_strlen(fileinfo->fi_shortname);
 479 
 480                 rc = smb_mbc_encodef(
 481                     &sr->raw_data, "llTTTTqqlllb.24c",
 482                     0,  /* NextEntryOffset (set later) */
 483                     resume_key,
 484                     &fileinfo->fi_crtime,
 485                     &fileinfo->fi_atime,
 486                     &fileinfo->fi_mtime,
 487                     &fileinfo->fi_ctime,
 488                     fileinfo->fi_size,
 489                     fileinfo->fi_alloc_size,
 490                     fileinfo->fi_dosattr,
 491                     namelen,
 492                     0L,         /* EaSize */
 493                     shortlen,
 494                     buf83);
 495 
 496                 smb_msgbuf_term(&mb);
 497                 break;
 498 
 499         /* See also: SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO */
 500         case FileIdBothDirectoryInformation:    /* 37 */
 501                 bzero(buf83, sizeof (buf83));
 502                 smb_msgbuf_init(&mb, buf83, sizeof (buf83), mb_flags);
 503                 if (!smb_msgbuf_encode(&mb, "U", fileinfo->fi_shortname))
 504                         shortlen = smb_wcequiv_strlen(fileinfo->fi_shortname);
 505 
 506                 rc = smb_mbc_encodef(
 507                     &sr->raw_data, "llTTTTqqlllb.24c..q",
 508                     0,  /* NextEntryOffset (set later) */
 509                     resume_key,
 510                     &fileinfo->fi_crtime,
 511                     &fileinfo->fi_atime,
 512                     &fileinfo->fi_mtime,
 513                     &fileinfo->fi_ctime,
 514                     fileinfo->fi_size,               /* q */
 515                     fileinfo->fi_alloc_size, /* q */
 516                     fileinfo->fi_dosattr,    /* l */
 517                     namelen,                    /* l */
 518                     0L,         /* EaSize          l */
 519                     shortlen,                   /* b. */
 520                     buf83,                      /* 24c */
 521                     /* reserved                    .. */
 522                     fileinfo->fi_nodeid);    /* q */
 523 
 524                 smb_msgbuf_term(&mb);
 525                 break;
 526 
 527         /* See also: SMB_FIND_FILE_NAMES_INFO */
 528         case FileNamesInformation:              /* 12 */
 529                 rc = smb_mbc_encodef(
 530                     &sr->raw_data, "lll",
 531                     0,  /* NextEntryOffset (set later) */
 532                     resume_key,
 533                     namelen);
 534                 break;
 535 
 536         default:
 537                 return (NT_STATUS_INVALID_INFO_CLASS);
 538         }
 539         if (rc) /* smb_mbc_encodef failed */
 540                 return (NT_STATUS_BUFFER_OVERFLOW);
 541 
 542         /*
 543          * At this point we have written all the fixed-size data
 544          * for the specified info. class.  Now put the name and
 545          * alignment padding, and then patch the NextEntryOffset.
 546          * Also store this offset for the caller so they can
 547          * patch this (again) to zero on the very last entry.
 548          */
 549         rc = smb_mbc_encodef(
 550             &sr->raw_data, "U",
 551             fileinfo->fi_name);
 552         if (rc)
 553                 return (NT_STATUS_BUFFER_OVERFLOW);
 554 
 555         /* Next entry needs to be 8-byte aligned. */
 556         padsz = sr->raw_data.chain_offset & 7;
 557         if (padsz) {
 558                 padsz = 8 - padsz;
 559                 (void) smb_mbc_encodef(&sr->raw_data, "#.", padsz);
 560         }
 561         next_entry_offset = sr->raw_data.chain_offset -      starting_offset;
 562         (void) smb_mbc_poke(&sr->raw_data, starting_offset, "l",
 563             next_entry_offset);
 564         args->fa_last_entry = starting_offset;
 565 
 566         return (0);
 567 }