1/*
2   Unix SMB/CIFS implementation.
3   Core SMB2 server
4
5   Copyright (C) Stefan Metzmacher 2009
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#include "includes.h"
22#include "smbd/globals.h"
23#include "../libcli/smb/smb_common.h"
24
25static struct tevent_req *smbd_smb2_find_send(TALLOC_CTX *mem_ctx,
26					      struct tevent_context *ev,
27					      struct smbd_smb2_request *smb2req,
28					      uint8_t in_file_info_class,
29					      uint8_t in_flags,
30					      uint32_t in_file_index,
31					      uint64_t in_file_id_volatile,
32					      uint32_t in_output_buffer_length,
33					      const char *in_file_name);
34static NTSTATUS smbd_smb2_find_recv(struct tevent_req *req,
35				    TALLOC_CTX *mem_ctx,
36				    DATA_BLOB *out_output_buffer);
37
38static void smbd_smb2_request_find_done(struct tevent_req *subreq);
39NTSTATUS smbd_smb2_request_process_find(struct smbd_smb2_request *req)
40{
41	const uint8_t *inhdr;
42	const uint8_t *inbody;
43	int i = req->current_idx;
44	size_t expected_body_size = 0x21;
45	size_t body_size;
46	uint8_t in_file_info_class;
47	uint8_t in_flags;
48	uint32_t in_file_index;
49	uint64_t in_file_id_persistent;
50	uint64_t in_file_id_volatile;
51	uint16_t in_file_name_offset;
52	uint16_t in_file_name_length;
53	DATA_BLOB in_file_name_buffer;
54	char *in_file_name_string;
55	size_t in_file_name_string_size;
56	uint32_t in_output_buffer_length;
57	struct tevent_req *subreq;
58	bool ok;
59
60	inhdr = (const uint8_t *)req->in.vector[i+0].iov_base;
61	if (req->in.vector[i+1].iov_len != (expected_body_size & 0xFFFFFFFE)) {
62		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
63	}
64
65	inbody = (const uint8_t *)req->in.vector[i+1].iov_base;
66
67	body_size = SVAL(inbody, 0x00);
68	if (body_size != expected_body_size) {
69		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
70	}
71
72	in_file_info_class		= CVAL(inbody, 0x02);
73	in_flags			= CVAL(inbody, 0x03);
74	in_file_index			= IVAL(inbody, 0x04);
75	in_file_id_persistent		= BVAL(inbody, 0x08);
76	in_file_id_volatile		= BVAL(inbody, 0x10);
77	in_file_name_offset		= SVAL(inbody, 0x18);
78	in_file_name_length		= SVAL(inbody, 0x1A);
79	in_output_buffer_length		= IVAL(inbody, 0x1C);
80
81	if (in_file_name_offset == 0 && in_file_name_length == 0) {
82		/* This is ok */
83	} else if (in_file_name_offset !=
84		   (SMB2_HDR_BODY + (body_size & 0xFFFFFFFE))) {
85		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
86	}
87
88	if (in_file_name_length > req->in.vector[i+2].iov_len) {
89		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
90	}
91
92	in_file_name_buffer.data = (uint8_t *)req->in.vector[i+2].iov_base;
93	in_file_name_buffer.length = in_file_name_length;
94
95	ok = convert_string_talloc(req, CH_UTF16, CH_UNIX,
96				   in_file_name_buffer.data,
97				   in_file_name_buffer.length,
98				   &in_file_name_string,
99				   &in_file_name_string_size, false);
100	if (!ok) {
101		return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER);
102	}
103
104	if (req->compat_chain_fsp) {
105		/* skip check */
106	} else if (in_file_id_persistent != 0) {
107		return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
108	}
109
110	subreq = smbd_smb2_find_send(req,
111				     req->sconn->smb2.event_ctx,
112				     req,
113				     in_file_info_class,
114				     in_flags,
115				     in_file_index,
116				     in_file_id_volatile,
117				     in_output_buffer_length,
118				     in_file_name_string);
119	if (subreq == NULL) {
120		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
121	}
122	tevent_req_set_callback(subreq, smbd_smb2_request_find_done, req);
123
124	return smbd_smb2_request_pending_queue(req, subreq);
125}
126
127static void smbd_smb2_request_find_done(struct tevent_req *subreq)
128{
129	struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
130					struct smbd_smb2_request);
131	int i = req->current_idx;
132	uint8_t *outhdr;
133	DATA_BLOB outbody;
134	DATA_BLOB outdyn;
135	uint16_t out_output_buffer_offset;
136	DATA_BLOB out_output_buffer = data_blob_null;
137	NTSTATUS status;
138	NTSTATUS error; /* transport error */
139
140	status = smbd_smb2_find_recv(subreq,
141				     req,
142				     &out_output_buffer);
143	TALLOC_FREE(subreq);
144	if (!NT_STATUS_IS_OK(status)) {
145		error = smbd_smb2_request_error(req, status);
146		if (!NT_STATUS_IS_OK(error)) {
147			smbd_server_connection_terminate(req->sconn,
148							 nt_errstr(error));
149			return;
150		}
151		return;
152	}
153
154	out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
155
156	outhdr = (uint8_t *)req->out.vector[i].iov_base;
157
158	outbody = data_blob_talloc(req->out.vector, NULL, 0x08);
159	if (outbody.data == NULL) {
160		error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
161		if (!NT_STATUS_IS_OK(error)) {
162			smbd_server_connection_terminate(req->sconn,
163							 nt_errstr(error));
164			return;
165		}
166		return;
167	}
168
169	SSVAL(outbody.data, 0x00, 0x08 + 1);	/* struct size */
170	SSVAL(outbody.data, 0x02,
171	      out_output_buffer_offset);	/* output buffer offset */
172	SIVAL(outbody.data, 0x04,
173	      out_output_buffer.length);	/* output buffer length */
174
175	outdyn = out_output_buffer;
176
177	error = smbd_smb2_request_done(req, outbody, &outdyn);
178	if (!NT_STATUS_IS_OK(error)) {
179		smbd_server_connection_terminate(req->sconn,
180						 nt_errstr(error));
181		return;
182	}
183}
184
185struct smbd_smb2_find_state {
186	struct smbd_smb2_request *smb2req;
187	DATA_BLOB out_output_buffer;
188};
189
190static struct tevent_req *smbd_smb2_find_send(TALLOC_CTX *mem_ctx,
191					      struct tevent_context *ev,
192					      struct smbd_smb2_request *smb2req,
193					      uint8_t in_file_info_class,
194					      uint8_t in_flags,
195					      uint32_t in_file_index,
196					      uint64_t in_file_id_volatile,
197					      uint32_t in_output_buffer_length,
198					      const char *in_file_name)
199{
200	struct tevent_req *req;
201	struct smbd_smb2_find_state *state;
202	struct smb_request *smbreq;
203	connection_struct *conn = smb2req->tcon->compat_conn;
204	files_struct *fsp;
205	NTSTATUS status;
206	NTSTATUS empty_status;
207	uint32_t info_level;
208	uint32_t max_count;
209	char *pdata;
210	char *base_data;
211	char *end_data;
212	int last_entry_off = 0;
213	uint64_t off = 0;
214	uint32_t num = 0;
215	uint32_t dirtype = aHIDDEN | aSYSTEM | aDIR;
216	const char *directory;
217	bool dont_descend = false;
218	bool ask_sharemode = true;
219
220	req = tevent_req_create(mem_ctx, &state,
221				struct smbd_smb2_find_state);
222	if (req == NULL) {
223		return NULL;
224	}
225	state->smb2req = smb2req;
226	state->out_output_buffer = data_blob_null;
227
228	DEBUG(10,("smbd_smb2_find_send: file_id[0x%016llX]\n",
229		  (unsigned long long)in_file_id_volatile));
230
231	smbreq = smbd_smb2_fake_smb_request(smb2req);
232	if (tevent_req_nomem(smbreq, req)) {
233		return tevent_req_post(req, ev);
234	}
235
236	fsp = file_fsp(smbreq, (uint16_t)in_file_id_volatile);
237	if (fsp == NULL) {
238		tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
239		return tevent_req_post(req, ev);
240	}
241	if (conn != fsp->conn) {
242		tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
243		return tevent_req_post(req, ev);
244	}
245	if (smb2req->session->vuid != fsp->vuid) {
246		tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
247		return tevent_req_post(req, ev);
248	}
249
250	if (!fsp->is_directory) {
251		tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
252		return tevent_req_post(req, ev);
253	}
254
255	directory = fsp->fsp_name->base_name;
256
257	if (strcmp(in_file_name, "") == 0) {
258		tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
259		return tevent_req_post(req, ev);
260	}
261	if (strcmp(in_file_name, "\\") == 0) {
262		tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
263		return tevent_req_post(req, ev);
264	}
265	if (strcmp(in_file_name, "/") == 0) {
266		tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
267		return tevent_req_post(req, ev);
268	}
269
270	if (in_output_buffer_length > 0x10000) {
271		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
272		return tevent_req_post(req, ev);
273	}
274
275	switch (in_file_info_class) {
276	case SMB2_FIND_DIRECTORY_INFO:
277		info_level = SMB_FIND_FILE_DIRECTORY_INFO;
278		break;
279
280	case SMB2_FIND_FULL_DIRECTORY_INFO:
281		info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
282		break;
283
284	case SMB2_FIND_BOTH_DIRECTORY_INFO:
285		info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
286		break;
287
288	case SMB2_FIND_NAME_INFO:
289		info_level = SMB_FIND_FILE_NAMES_INFO;
290		break;
291
292	case SMB2_FIND_ID_BOTH_DIRECTORY_INFO:
293		info_level = SMB_FIND_ID_BOTH_DIRECTORY_INFO;
294		break;
295
296	case SMB2_FIND_ID_FULL_DIRECTORY_INFO:
297		info_level = SMB_FIND_ID_FULL_DIRECTORY_INFO;
298		break;
299
300	default:
301		tevent_req_nterror(req, NT_STATUS_INVALID_INFO_CLASS);
302		return tevent_req_post(req, ev);
303	}
304
305	if (in_flags & SMB2_CONTINUE_FLAG_REOPEN) {
306		if (fsp->dptr) {
307			dptr_CloseDir(fsp->dptr);
308			fsp->dptr = NULL;
309		}
310	}
311
312	if (fsp->dptr == NULL) {
313		bool wcard_has_wild;
314
315		if (!(fsp->access_mask & SEC_DIR_LIST)) {
316			tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
317			return tevent_req_post(req, ev);
318		}
319
320		wcard_has_wild = ms_has_wild(in_file_name);
321
322		status = dptr_create(conn,
323				     directory,
324				     false, /* old_handle */
325				     false, /* expect_close */
326				     0, /* spid */
327				     in_file_name, /* wcard */
328				     wcard_has_wild,
329				     dirtype,
330				     &fsp->dptr);
331		if (!NT_STATUS_IS_OK(status)) {
332			tevent_req_nterror(req, status);
333			return tevent_req_post(req, ev);
334		}
335
336		empty_status = NT_STATUS_NO_SUCH_FILE;
337	} else {
338		empty_status = STATUS_NO_MORE_FILES;
339	}
340
341	if (in_flags & SMB2_CONTINUE_FLAG_RESTART) {
342		dptr_SeekDir(fsp->dptr, 0);
343	}
344
345	if (in_flags & SMB2_CONTINUE_FLAG_SINGLE) {
346		max_count = 1;
347	} else {
348		max_count = UINT16_MAX;
349	}
350
351#define DIR_ENTRY_SAFETY_MARGIN 4096
352
353	state->out_output_buffer = data_blob_talloc(state, NULL,
354			in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN);
355	if (tevent_req_nomem(state->out_output_buffer.data, req)) {
356		return tevent_req_post(req, ev);
357	}
358
359	state->out_output_buffer.length = 0;
360	pdata = (char *)state->out_output_buffer.data;
361	base_data = pdata;
362	end_data = pdata + in_output_buffer_length;
363	last_entry_off = 0;
364	off = 0;
365	num = 0;
366
367	DEBUG(8,("smbd_smb2_find_send: dirpath=<%s> dontdescend=<%s>\n",
368		directory, lp_dontdescend(SNUM(conn))));
369	if (in_list(directory,lp_dontdescend(SNUM(conn)),conn->case_sensitive)) {
370		dont_descend = true;
371	}
372
373	ask_sharemode = lp_parm_bool(SNUM(conn),
374				     "smbd", "search ask sharemode",
375				     true);
376
377	while (true) {
378		bool ok;
379		bool got_exact_match = false;
380		bool out_of_space = false;
381		int space_remaining = in_output_buffer_length - off;
382
383		ok = smbd_dirptr_lanman2_entry(state,
384					       conn,
385					       fsp->dptr,
386					       smbreq->flags2,
387					       in_file_name,
388					       dirtype,
389					       info_level,
390					       false, /* requires_resume_key */
391					       dont_descend,
392					       ask_sharemode,
393					       8, /* align to 8 bytes */
394					       false, /* no padding */
395					       &pdata,
396					       base_data,
397					       end_data,
398					       space_remaining,
399					       &out_of_space,
400					       &got_exact_match,
401					       &last_entry_off,
402					       NULL);
403
404		off = PTR_DIFF(pdata, base_data);
405
406		if (!ok) {
407			if (num > 0) {
408				SIVAL(state->out_output_buffer.data, last_entry_off, 0);
409				tevent_req_done(req);
410				return tevent_req_post(req, ev);
411			} else if (out_of_space) {
412				tevent_req_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
413				return tevent_req_post(req, ev);
414			} else {
415				tevent_req_nterror(req, empty_status);
416				return tevent_req_post(req, ev);
417			}
418		}
419
420		num++;
421		state->out_output_buffer.length = off;
422
423		if (num < max_count) {
424			continue;
425		}
426
427		SIVAL(state->out_output_buffer.data, last_entry_off, 0);
428		tevent_req_done(req);
429		return tevent_req_post(req, ev);
430	}
431
432	tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
433	return tevent_req_post(req, ev);
434}
435
436static NTSTATUS smbd_smb2_find_recv(struct tevent_req *req,
437				    TALLOC_CTX *mem_ctx,
438				    DATA_BLOB *out_output_buffer)
439{
440	NTSTATUS status;
441	struct smbd_smb2_find_state *state = tevent_req_data(req,
442					     struct smbd_smb2_find_state);
443
444	if (tevent_req_is_nterror(req, &status)) {
445		tevent_req_received(req);
446		return status;
447	}
448
449	*out_output_buffer = state->out_output_buffer;
450	talloc_steal(mem_ctx, out_output_buffer->data);
451
452	tevent_req_received(req);
453	return NT_STATUS_OK;
454}
455