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