1/*
2   Unix SMB/CIFS implementation.
3   client directory list routines
4   Copyright (C) Andrew Tridgell 1994-1998
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "includes.h"
21
22/****************************************************************************
23 Calculate a safe next_entry_offset.
24****************************************************************************/
25
26static size_t calc_next_entry_offset(const char *base, const char *pdata_end)
27{
28	size_t next_entry_offset = (size_t)IVAL(base,0);
29
30	if (next_entry_offset == 0 ||
31			base + next_entry_offset < base ||
32			base + next_entry_offset > pdata_end) {
33		next_entry_offset = pdata_end - base;
34	}
35	return next_entry_offset;
36}
37
38/****************************************************************************
39 Interpret a long filename structure - this is mostly guesses at the moment.
40 The length of the structure is returned
41 The structure of a long filename depends on the info level.
42 SMB_FIND_FILE_BOTH_DIRECTORY_INFO is used
43 by NT and SMB_FIND_EA_SIZE is used by OS/2
44****************************************************************************/
45
46static size_t interpret_long_filename(TALLOC_CTX *ctx,
47					struct cli_state *cli,
48					int level,
49					const char *p,
50					const char *pdata_end,
51					file_info *finfo,
52					uint32 *p_resume_key,
53					DATA_BLOB *p_last_name_raw)
54{
55	int len;
56	size_t ret;
57	const char *base = p;
58
59	data_blob_free(p_last_name_raw);
60
61	if (p_resume_key) {
62		*p_resume_key = 0;
63	}
64	ZERO_STRUCTP(finfo);
65	finfo->cli = cli;
66
67	switch (level) {
68		case SMB_FIND_INFO_STANDARD: /* OS/2 understands this */
69			/* these dates are converted to GMT by
70                           make_unix_date */
71			if (pdata_end - base < 27) {
72				return pdata_end - base;
73			}
74			finfo->ctime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+4));
75			finfo->atime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+8));
76			finfo->mtime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+12));
77			finfo->size = IVAL(p,16);
78			finfo->mode = CVAL(p,24);
79			len = CVAL(p, 26);
80			p += 27;
81			p += clistr_align_in(cli, p, 0);
82
83			/* We can safely use len here (which is required by OS/2)
84			 * and the NAS-BASIC server instead of +2 or +1 as the
85			 * STR_TERMINATE flag below is
86			 * actually used as the length calculation.
87			 * The len is merely an upper bound.
88			 * Due to the explicit 2 byte null termination
89			 * in cli_receive_trans/cli_receive_nt_trans
90			 * we know this is safe. JRA + kukks
91			 */
92
93			if (p + len > pdata_end) {
94				return pdata_end - base;
95			}
96
97			/* the len+2 below looks strange but it is
98			   important to cope with the differences
99			   between win2000 and win9x for this call
100			   (tridge) */
101			ret = clistr_pull_talloc(ctx,
102						cli->inbuf,
103						&finfo->name,
104						p,
105						len+2,
106						STR_TERMINATE);
107			if (ret == (size_t)-1) {
108				return pdata_end - base;
109			}
110			p += ret;
111			return PTR_DIFF(p, base);
112
113		case SMB_FIND_EA_SIZE: /* this is what OS/2 uses mostly */
114			/* these dates are converted to GMT by
115                           make_unix_date */
116			if (pdata_end - base < 31) {
117				return pdata_end - base;
118			}
119			finfo->ctime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+4));
120			finfo->atime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+8));
121			finfo->mtime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+12));
122			finfo->size = IVAL(p,16);
123			finfo->mode = CVAL(p,24);
124			len = CVAL(p, 30);
125			p += 31;
126			/* check for unisys! */
127			if (p + len + 1 > pdata_end) {
128				return pdata_end - base;
129			}
130			ret = clistr_pull_talloc(ctx,
131						cli->inbuf,
132						&finfo->name,
133						p,
134					 	len,
135						STR_NOALIGN);
136			if (ret == (size_t)-1) {
137				return pdata_end - base;
138			}
139			p += ret;
140			return PTR_DIFF(p, base) + 1;
141
142		case SMB_FIND_FILE_BOTH_DIRECTORY_INFO: /* NT uses this, but also accepts 2 */
143		{
144			size_t namelen, slen;
145
146			if (pdata_end - base < 94) {
147				return pdata_end - base;
148			}
149
150			p += 4; /* next entry offset */
151
152			if (p_resume_key) {
153				*p_resume_key = IVAL(p,0);
154			}
155			p += 4; /* fileindex */
156
157			/* Offset zero is "create time", not "change time". */
158			p += 8;
159			finfo->atime_ts = interpret_long_date(p);
160			p += 8;
161			finfo->mtime_ts = interpret_long_date(p);
162			p += 8;
163			finfo->ctime_ts = interpret_long_date(p);
164			p += 8;
165			finfo->size = IVAL2_TO_SMB_BIG_UINT(p,0);
166			p += 8;
167			p += 8; /* alloc size */
168			finfo->mode = CVAL(p,0);
169			p += 4;
170			namelen = IVAL(p,0);
171			p += 4;
172			p += 4; /* EA size */
173			slen = SVAL(p, 0);
174			if (slen > 24) {
175				/* Bad short name length. */
176				return pdata_end - base;
177			}
178			p += 2;
179			{
180				/* stupid NT bugs. grr */
181				int flags = 0;
182				if (p[1] == 0 && namelen > 1) flags |= STR_UNICODE;
183				clistr_pull(cli->inbuf, finfo->short_name, p,
184					    sizeof(finfo->short_name),
185					    slen, flags);
186			}
187			p += 24; /* short name? */
188			if (p + namelen < p || p + namelen > pdata_end) {
189				return pdata_end - base;
190			}
191			ret = clistr_pull_talloc(ctx,
192						cli->inbuf,
193						&finfo->name,
194						p,
195				    		namelen,
196						0);
197			if (ret == (size_t)-1) {
198				return pdata_end - base;
199			}
200
201			/* To be robust in the face of unicode conversion failures
202			   we need to copy the raw bytes of the last name seen here.
203			   Namelen doesn't include the terminating unicode null, so
204			   copy it here. */
205
206			if (p_last_name_raw) {
207				*p_last_name_raw = data_blob(NULL, namelen+2);
208				memcpy(p_last_name_raw->data, p, namelen);
209				SSVAL(p_last_name_raw->data, namelen, 0);
210			}
211			return calc_next_entry_offset(base, pdata_end);
212		}
213	}
214
215	DEBUG(1,("Unknown long filename format %d\n",level));
216	return calc_next_entry_offset(base, pdata_end);
217}
218
219/****************************************************************************
220 Do a directory listing, calling fn on each file found.
221****************************************************************************/
222
223int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
224		 void (*fn)(const char *, file_info *, const char *, void *), void *state)
225{
226#if 1
227	int max_matches = 1366; /* Match W2k - was 512. */
228#else
229	int max_matches = 512;
230#endif
231	int info_level;
232	char *p, *p2, *rdata_end;
233	char *mask = NULL;
234	file_info finfo;
235	int i;
236	char *dirlist = NULL;
237	int dirlist_len = 0;
238	int total_received = -1;
239	bool First = True;
240	int ff_searchcount=0;
241	int ff_eos=0;
242	int ff_dir_handle=0;
243	int loop_count = 0;
244	char *rparam=NULL, *rdata=NULL;
245	unsigned int param_len, data_len;
246	uint16 setup;
247	char *param;
248	uint32 resume_key = 0;
249	TALLOC_CTX *frame = talloc_stackframe();
250	DATA_BLOB last_name_raw = data_blob(NULL, 0);
251
252	/* NT uses SMB_FIND_FILE_BOTH_DIRECTORY_INFO,
253	   OS/2 uses SMB_FIND_EA_SIZE. Both accept SMB_FIND_INFO_STANDARD. */
254	info_level = (cli->capabilities&CAP_NT_SMBS)?
255		SMB_FIND_FILE_BOTH_DIRECTORY_INFO : SMB_FIND_INFO_STANDARD;
256
257	mask = SMB_STRDUP(Mask);
258	if (!mask) {
259		TALLOC_FREE(frame);
260		return -1;
261	}
262
263	while (ff_eos == 0) {
264		size_t nlen = 2*(strlen(mask)+1);
265
266		loop_count++;
267		if (loop_count > 200) {
268			DEBUG(0,("Error: Looping in FIND_NEXT??\n"));
269			break;
270		}
271
272		param = SMB_MALLOC_ARRAY(char, 12+nlen+last_name_raw.length+2);
273		if (!param) {
274			break;
275		}
276
277		if (First) {
278			setup = TRANSACT2_FINDFIRST;
279			SSVAL(param,0,attribute); /* attribute */
280			SSVAL(param,2,max_matches); /* max count */
281			SSVAL(param,4,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END));	/* resume required + close on end */
282			SSVAL(param,6,info_level);
283			SIVAL(param,8,0);
284			p = param+12;
285			p += clistr_push(cli, param+12, mask,
286					 nlen, STR_TERMINATE);
287		} else {
288			setup = TRANSACT2_FINDNEXT;
289			SSVAL(param,0,ff_dir_handle);
290			SSVAL(param,2,max_matches); /* max count */
291			SSVAL(param,4,info_level);
292			/* For W2K servers serving out FAT filesystems we *must* set the
293			   resume key. If it's not FAT then it's returned as zero. */
294			SIVAL(param,6,resume_key); /* ff_resume_key */
295			/* NB. *DON'T* use continue here. If you do it seems that W2K and bretheren
296			   can miss filenames. Use last filename continue instead. JRA */
297			SSVAL(param,10,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END));	/* resume required + close on end */
298			p = param+12;
299			if (last_name_raw.length) {
300				memcpy(p, last_name_raw.data, last_name_raw.length);
301				p += last_name_raw.length;
302			} else {
303				p += clistr_push(cli, param+12, mask,
304						nlen, STR_TERMINATE);
305			}
306		}
307
308		param_len = PTR_DIFF(p, param);
309
310		if (!cli_send_trans(cli, SMBtrans2,
311				    NULL,                   /* Name */
312				    -1, 0,                  /* fid, flags */
313				    &setup, 1, 0,           /* setup, length, max */
314				    param, param_len, 10,   /* param, length, max */
315				    NULL, 0,
316#if 0
317				    /* w2k value. */
318				    MIN(16384,cli->max_xmit) /* data, length, max. */
319#else
320				    cli->max_xmit	    /* data, length, max. */
321#endif
322				    )) {
323			SAFE_FREE(param);
324			TALLOC_FREE(frame);
325			break;
326		}
327
328		SAFE_FREE(param);
329
330		if (!cli_receive_trans(cli, SMBtrans2,
331				       &rparam, &param_len,
332				       &rdata, &data_len) &&
333                    cli_is_dos_error(cli)) {
334			/* We need to work around a Win95 bug - sometimes
335			   it gives ERRSRV/ERRerror temprarily */
336			uint8 eclass;
337			uint32 ecode;
338
339			SAFE_FREE(rdata);
340			SAFE_FREE(rparam);
341
342			cli_dos_error(cli, &eclass, &ecode);
343
344			/*
345			 * OS/2 might return "no more files",
346			 * which just tells us, that searchcount is zero
347			 * in this search.
348			 * Guenter Kukkukk <linux@kukkukk.com>
349			 */
350
351			if (eclass == ERRDOS && ecode == ERRnofiles) {
352				ff_searchcount = 0;
353				cli_reset_error(cli);
354				break;
355			}
356
357			if (eclass != ERRSRV || ecode != ERRerror)
358				break;
359			smb_msleep(100);
360			continue;
361		}
362
363                if (cli_is_error(cli) || !rdata || !rparam) {
364			SAFE_FREE(rdata);
365			SAFE_FREE(rparam);
366			break;
367		}
368
369		if (total_received == -1)
370			total_received = 0;
371
372		/* parse out some important return info */
373		p = rparam;
374		if (First) {
375			ff_dir_handle = SVAL(p,0);
376			ff_searchcount = SVAL(p,2);
377			ff_eos = SVAL(p,4);
378		} else {
379			ff_searchcount = SVAL(p,0);
380			ff_eos = SVAL(p,2);
381		}
382
383		if (ff_searchcount == 0) {
384			SAFE_FREE(rdata);
385			SAFE_FREE(rparam);
386			break;
387		}
388
389		/* point to the data bytes */
390		p = rdata;
391		rdata_end = rdata + data_len;
392
393		/* we might need the lastname for continuations */
394		for (p2=p,i=0;i<ff_searchcount && p2 < rdata_end;i++) {
395			if ((info_level == SMB_FIND_FILE_BOTH_DIRECTORY_INFO) &&
396					(i == ff_searchcount-1)) {
397				/* Last entry - fixup the last offset length. */
398				SIVAL(p2,0,PTR_DIFF((rdata + data_len),p2));
399			}
400			p2 += interpret_long_filename(frame,
401							cli,
402							info_level,
403							p2,
404							rdata_end,
405							&finfo,
406							&resume_key,
407							&last_name_raw);
408
409			if (!finfo.name) {
410				DEBUG(0,("cli_list_new: Error: unable to parse name from info level %d\n",
411					info_level));
412				ff_eos = 1;
413				break;
414			}
415			if (!First && *mask && strcsequal(finfo.name, mask)) {
416				DEBUG(0,("Error: Looping in FIND_NEXT as name %s has already been seen?\n",
417					finfo.name));
418				ff_eos = 1;
419				break;
420			}
421		}
422
423		SAFE_FREE(mask);
424		if (ff_searchcount > 0 && ff_eos == 0 && finfo.name) {
425			mask = SMB_STRDUP(finfo.name);
426		} else {
427			mask = SMB_STRDUP("");
428		}
429		if (!mask) {
430			SAFE_FREE(rdata);
431			SAFE_FREE(rparam);
432			break;
433		}
434
435		/* grab the data for later use */
436		/* and add them to the dirlist pool */
437		dirlist = (char *)SMB_REALLOC(dirlist,dirlist_len + data_len);
438
439		if (!dirlist) {
440			DEBUG(0,("cli_list_new: Failed to expand dirlist\n"));
441			SAFE_FREE(rdata);
442			SAFE_FREE(rparam);
443			break;
444		}
445
446		memcpy(dirlist+dirlist_len,p,data_len);
447		dirlist_len += data_len;
448
449		total_received += ff_searchcount;
450
451		SAFE_FREE(rdata);
452		SAFE_FREE(rparam);
453
454		DEBUG(3,("received %d entries (eos=%d)\n",
455			 ff_searchcount,ff_eos));
456
457		if (ff_searchcount > 0)
458			loop_count = 0;
459
460		First = False;
461	}
462
463        /* see if the server disconnected or the connection otherwise failed */
464        if (cli_is_error(cli)) {
465                total_received = -1;
466        } else {
467                /* no connection problem.  let user function add each entry */
468		rdata_end = dirlist + dirlist_len;
469                for (p=dirlist,i=0;i<total_received;i++) {
470                        p += interpret_long_filename(frame,
471							cli,
472							info_level,
473							p,
474							rdata_end,
475							&finfo,
476							NULL,
477							NULL);
478			if (!finfo.name) {
479				DEBUG(0,("cli_list_new: unable to parse name from info level %d\n",
480					info_level));
481				break;
482			}
483                        fn(cli->dfs_mountpoint, &finfo, Mask, state);
484                }
485        }
486
487	/* free up the dirlist buffer and last name raw blob */
488	SAFE_FREE(dirlist);
489	data_blob_free(&last_name_raw);
490	SAFE_FREE(mask);
491	TALLOC_FREE(frame);
492	return(total_received);
493}
494
495/****************************************************************************
496 Interpret a short filename structure.
497 The length of the structure is returned.
498****************************************************************************/
499
500static bool interpret_short_filename(TALLOC_CTX *ctx,
501				struct cli_state *cli,
502				char *p,
503				file_info *finfo)
504{
505	size_t ret;
506	ZERO_STRUCTP(finfo);
507
508	finfo->cli = cli;
509	finfo->mode = CVAL(p,21);
510
511	/* this date is converted to GMT by make_unix_date */
512	finfo->ctime_ts.tv_sec = cli_make_unix_date(cli, p+22);
513	finfo->ctime_ts.tv_nsec = 0;
514	finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec;
515	finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0;
516	finfo->size = IVAL(p,26);
517	ret = clistr_pull_talloc(ctx,
518			cli->inbuf,
519			&finfo->name,
520			p+30,
521			12,
522			STR_ASCII);
523	if (ret == (size_t)-1) {
524		return false;
525	}
526
527	if (finfo->name) {
528		strlcpy(finfo->short_name,
529			finfo->name,
530			sizeof(finfo->short_name));
531	}
532	return true;
533	return(DIR_STRUCT_SIZE);
534}
535
536/****************************************************************************
537 Do a directory listing, calling fn on each file found.
538 this uses the old SMBsearch interface. It is needed for testing Samba,
539 but should otherwise not be used.
540****************************************************************************/
541
542int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
543		 void (*fn)(const char *, file_info *, const char *, void *), void *state)
544{
545	char *p;
546	int received = 0;
547	bool first = True;
548	char status[21];
549	int num_asked = (cli->max_xmit - 100)/DIR_STRUCT_SIZE;
550	int num_received = 0;
551	int i;
552	char *dirlist = NULL;
553	char *mask = NULL;
554	TALLOC_CTX *frame = NULL;
555
556	ZERO_ARRAY(status);
557
558	mask = SMB_STRDUP(Mask);
559	if (!mask) {
560		return -1;
561	}
562
563	while (1) {
564		memset(cli->outbuf,'\0',smb_size);
565		memset(cli->inbuf,'\0',smb_size);
566
567		cli_set_message(cli->outbuf,2,0,True);
568
569		SCVAL(cli->outbuf,smb_com,SMBsearch);
570
571		SSVAL(cli->outbuf,smb_tid,cli->cnum);
572		cli_setup_packet(cli);
573
574		SSVAL(cli->outbuf,smb_vwv0,num_asked);
575		SSVAL(cli->outbuf,smb_vwv1,attribute);
576
577		p = smb_buf(cli->outbuf);
578		*p++ = 4;
579
580		p += clistr_push(cli, p, first?mask:"",
581				cli->bufsize - PTR_DIFF(p,cli->outbuf),
582				STR_TERMINATE);
583		*p++ = 5;
584		if (first) {
585			SSVAL(p,0,0);
586			p += 2;
587		} else {
588			SSVAL(p,0,21);
589			p += 2;
590			memcpy(p,status,21);
591			p += 21;
592		}
593
594		cli_setup_bcc(cli, p);
595		cli_send_smb(cli);
596		if (!cli_receive_smb(cli)) break;
597
598		received = SVAL(cli->inbuf,smb_vwv0);
599		if (received <= 0) break;
600
601		/* Ensure we received enough data. */
602		if ((cli->inbuf+4+smb_len(cli->inbuf) - (smb_buf(cli->inbuf)+3)) <
603				received*DIR_STRUCT_SIZE) {
604			break;
605		}
606
607		first = False;
608
609		dirlist = (char *)SMB_REALLOC(
610			dirlist,(num_received + received)*DIR_STRUCT_SIZE);
611		if (!dirlist) {
612			DEBUG(0,("cli_list_old: failed to expand dirlist"));
613			SAFE_FREE(mask);
614			return 0;
615		}
616
617		p = smb_buf(cli->inbuf) + 3;
618
619		memcpy(dirlist+num_received*DIR_STRUCT_SIZE,
620		       p,received*DIR_STRUCT_SIZE);
621
622		memcpy(status,p + ((received-1)*DIR_STRUCT_SIZE),21);
623
624		num_received += received;
625
626		if (cli_is_error(cli)) break;
627	}
628
629	if (!first) {
630		memset(cli->outbuf,'\0',smb_size);
631		memset(cli->inbuf,'\0',smb_size);
632
633		cli_set_message(cli->outbuf,2,0,True);
634		SCVAL(cli->outbuf,smb_com,SMBfclose);
635		SSVAL(cli->outbuf,smb_tid,cli->cnum);
636		cli_setup_packet(cli);
637
638		SSVAL(cli->outbuf, smb_vwv0, 0); /* find count? */
639		SSVAL(cli->outbuf, smb_vwv1, attribute);
640
641		p = smb_buf(cli->outbuf);
642		*p++ = 4;
643		fstrcpy(p, "");
644		p += strlen(p) + 1;
645		*p++ = 5;
646		SSVAL(p, 0, 21);
647		p += 2;
648		memcpy(p,status,21);
649		p += 21;
650
651		cli_setup_bcc(cli, p);
652		cli_send_smb(cli);
653		if (!cli_receive_smb(cli)) {
654			DEBUG(0,("Error closing search: %s\n",cli_errstr(cli)));
655		}
656	}
657
658	frame = talloc_stackframe();
659	for (p=dirlist,i=0;i<num_received;i++) {
660		file_info finfo;
661		if (!interpret_short_filename(frame, cli, p, &finfo)) {
662			break;
663		}
664		p += DIR_STRUCT_SIZE;
665		fn("\\", &finfo, Mask, state);
666	}
667	TALLOC_FREE(frame);
668
669	SAFE_FREE(mask);
670	SAFE_FREE(dirlist);
671	return(num_received);
672}
673
674/****************************************************************************
675 Do a directory listing, calling fn on each file found.
676 This auto-switches between old and new style.
677****************************************************************************/
678
679int cli_list(struct cli_state *cli,const char *Mask,uint16 attribute,
680	     void (*fn)(const char *, file_info *, const char *, void *), void *state)
681{
682	if (cli->protocol <= PROTOCOL_LANMAN1)
683		return cli_list_old(cli, Mask, attribute, fn, state);
684	return cli_list_new(cli, Mask, attribute, fn, state);
685}
686