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;
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 (void) smb_mbc_put_align(&sr->raw_data, 8);
651
652 next_entry_offset = sr->raw_data.chain_offset - starting_offset;
653 (void) smb_mbc_poke(&sr->raw_data, starting_offset, "l",
654 next_entry_offset);
655 args->fa_last_entry = starting_offset;
656
657 return (0);
658 }