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 } 568