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
smb2_query_dir(smb_request_t * sr)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
smb2_find_entries(smb_request_t * sr,smb_odir_t * od,smb2_find_args_t * args)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
smb2_find_mbc_encode(smb_request_t * sr,smb2_find_args_t * args)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