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