xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
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