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