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