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 2 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, write to the Free Software
18   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19*/
20
21#define NO_SYSLOG
22
23#include "includes.h"
24
25/****************************************************************************
26 Interpret a long filename structure - this is mostly guesses at the moment.
27 The length of the structure is returned
28 The structure of a long filename depends on the info level. 260 is used
29 by NT and 2 is used by OS/2
30****************************************************************************/
31
32static size_t interpret_long_filename(struct cli_state *cli,
33				   int level,char *p,file_info *finfo)
34{
35	extern file_info def_finfo;
36	file_info finfo2;
37	int len;
38	char *base = p;
39
40	if (!finfo) finfo = &finfo2;
41
42	memcpy(finfo,&def_finfo,sizeof(*finfo));
43
44	switch (level) {
45		case 1: /* OS/2 understands this */
46			/* these dates are converted to GMT by
47                           make_unix_date */
48			finfo->ctime = make_unix_date2(p+4);
49			finfo->atime = make_unix_date2(p+8);
50			finfo->mtime = make_unix_date2(p+12);
51			finfo->size = IVAL(p,16);
52			finfo->mode = CVAL(p,24);
53			len = CVAL(p, 26);
54			p += 27;
55			p += clistr_align_in(cli, p, 0);
56			/* the len+2 below looks strange but it is
57			   important to cope with the differences
58			   between win2000 and win9x for this call
59			   (tridge) */
60			p += clistr_pull(cli, finfo->name, p,
61					 sizeof(finfo->name),
62					 len+2,
63					 STR_TERMINATE);
64			return PTR_DIFF(p, base);
65
66		case 2: /* this is what OS/2 uses mostly */
67			/* these dates are converted to GMT by
68                           make_unix_date */
69			finfo->ctime = make_unix_date2(p+4);
70			finfo->atime = make_unix_date2(p+8);
71			finfo->mtime = make_unix_date2(p+12);
72			finfo->size = IVAL(p,16);
73			finfo->mode = CVAL(p,24);
74			len = CVAL(p, 30);
75			p += 31;
76			/* check for unisys! */
77			p += clistr_pull(cli, finfo->name, p,
78					 sizeof(finfo->name),
79					 len,
80					 STR_NOALIGN);
81			return PTR_DIFF(p, base) + 1;
82
83		case 260: /* NT uses this, but also accepts 2 */
84		{
85			size_t namelen, slen;
86			p += 4; /* next entry offset */
87			p += 4; /* fileindex */
88
89			/* these dates appear to arrive in a
90			   weird way. It seems to be localtime
91			   plus the serverzone given in the
92			   initial connect. This is GMT when
93			   DST is not in effect and one hour
94			   from GMT otherwise. Can this really
95			   be right??
96
97			   I suppose this could be called
98			   kludge-GMT. Is is the GMT you get
99			   by using the current DST setting on
100			   a different localtime. It will be
101			   cheap to calculate, I suppose, as
102			   no DST tables will be needed */
103
104			finfo->ctime = interpret_long_date(p);
105			p += 8;
106			finfo->atime = interpret_long_date(p);
107			p += 8;
108			finfo->mtime = interpret_long_date(p);
109			p += 8;
110			p += 8;
111			finfo->size = IVAL2_TO_SMB_BIG_UINT(p,0);
112			p += 8;
113			p += 8; /* alloc size */
114			finfo->mode = CVAL(p,0);
115			p += 4;
116			namelen = IVAL(p,0);
117			p += 4;
118			p += 4; /* EA size */
119			slen = SVAL(p, 0);
120			p += 2;
121			{
122				/* stupid NT bugs. grr */
123				int flags = 0;
124				if (p[1] == 0 && namelen > 1) flags |= STR_UNICODE;
125				clistr_pull(cli, finfo->short_name, p,
126					    sizeof(finfo->short_name),
127					    slen, flags);
128			}
129			p += 24; /* short name? */
130			clistr_pull(cli, finfo->name, p,
131				    sizeof(finfo->name),
132				    namelen, 0);
133			return (size_t)IVAL(base, 0);
134		}
135	}
136
137	DEBUG(1,("Unknown long filename format %d\n",level));
138	return (size_t)IVAL(base,0);
139}
140
141/****************************************************************************
142 Do a directory listing, calling fn on each file found.
143****************************************************************************/
144
145int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
146		 void (*fn)(const char *, file_info *, const char *, void *), void *state)
147{
148#if 0
149	int max_matches = 1366; /* Match W2k - was 512. */
150#else
151	int max_matches = 512;
152#endif
153	int info_level;
154	char *p, *p2;
155	pstring mask;
156	file_info finfo;
157	int i;
158	char *tdl, *dirlist = NULL;
159	int dirlist_len = 0;
160	int total_received = -1;
161	BOOL First = True;
162	int ff_searchcount=0;
163	int ff_eos=0;
164	int ff_lastname=0;
165	int ff_dir_handle=0;
166	int loop_count = 0;
167	char *rparam=NULL, *rdata=NULL;
168	unsigned int param_len, data_len;
169	uint16 setup;
170	pstring param;
171	const char *mnt;
172
173	/* NT uses 260, OS/2 uses 2. Both accept 1. */
174	info_level = (cli->capabilities&CAP_NT_SMBS)?260:1;
175
176	/* when getting a directory listing from a 2k dfs root share,
177	   we have to include the full path (\server\share\mask) here */
178
179	if ( cli->dfsroot )
180		pstr_sprintf( mask, "\\%s\\%s\\%s", cli->desthost, cli->share, Mask );
181	else
182		pstrcpy(mask,Mask);
183
184	while (ff_eos == 0) {
185		loop_count++;
186		if (loop_count > 200) {
187			DEBUG(0,("Error: Looping in FIND_NEXT??\n"));
188			break;
189		}
190
191		if (First) {
192			setup = TRANSACT2_FINDFIRST;
193			SSVAL(param,0,attribute); /* attribute */
194			SSVAL(param,2,max_matches); /* max count */
195			SSVAL(param,4,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END));	/* resume required + close on end */
196			SSVAL(param,6,info_level);
197			SIVAL(param,8,0);
198			p = param+12;
199			p += clistr_push(cli, param+12, mask, sizeof(param)-12,
200					 STR_TERMINATE);
201		} else {
202			setup = TRANSACT2_FINDNEXT;
203			SSVAL(param,0,ff_dir_handle);
204			SSVAL(param,2,max_matches); /* max count */
205			SSVAL(param,4,info_level);
206			SIVAL(param,6,0); /* ff_resume_key */
207			/* NB. *DON'T* use continue here. If you do it seems that W2K and bretheren
208			   can miss filenames. Use last filename continue instead. JRA */
209			SSVAL(param,10,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END));	/* resume required + close on end */
210			p = param+12;
211			p += clistr_push(cli, param+12, mask, sizeof(param)-12,
212					 STR_TERMINATE);
213		}
214
215		param_len = PTR_DIFF(p, param);
216
217		if (!cli_send_trans(cli, SMBtrans2,
218				    NULL,                   /* Name */
219				    -1, 0,                  /* fid, flags */
220				    &setup, 1, 0,           /* setup, length, max */
221				    param, param_len, 10,   /* param, length, max */
222				    NULL, 0,
223#if 0
224				    /* w2k value. */
225				    MIN(16384,cli->max_xmit) /* data, length, max. */
226#else
227				    cli->max_xmit	    /* data, length, max. */
228#endif
229				    )) {
230			break;
231		}
232
233		if (!cli_receive_trans(cli, SMBtrans2,
234				       &rparam, &param_len,
235				       &rdata, &data_len) &&
236                    cli_is_dos_error(cli)) {
237			/* we need to work around a Win95 bug - sometimes
238			   it gives ERRSRV/ERRerror temprarily */
239			uint8 eclass;
240			uint32 ecode;
241			cli_dos_error(cli, &eclass, &ecode);
242			if (eclass != ERRSRV || ecode != ERRerror)
243				break;
244			smb_msleep(100);
245			continue;
246		}
247
248                if (cli_is_error(cli) || !rdata || !rparam)
249			break;
250
251		if (total_received == -1)
252			total_received = 0;
253
254		/* parse out some important return info */
255		p = rparam;
256		if (First) {
257			ff_dir_handle = SVAL(p,0);
258			ff_searchcount = SVAL(p,2);
259			ff_eos = SVAL(p,4);
260			ff_lastname = SVAL(p,8);
261		} else {
262			ff_searchcount = SVAL(p,0);
263			ff_eos = SVAL(p,2);
264			ff_lastname = SVAL(p,6);
265		}
266
267		if (ff_searchcount == 0)
268			break;
269
270		/* point to the data bytes */
271		p = rdata;
272
273		/* we might need the lastname for continuations */
274		for (p2=p,i=0;i<ff_searchcount;i++) {
275			if ((info_level == 260) && (i == ff_searchcount-1)) {
276				/* Last entry - fixup the last offset length. */
277				SIVAL(p2,0,PTR_DIFF((rdata + data_len),p2));
278			}
279			p2 += interpret_long_filename(cli,info_level,p2,&finfo);
280		}
281
282		if (ff_lastname > 0) {
283			pstrcpy(mask, finfo.name);
284		} else {
285			pstrcpy(mask,"");
286		}
287
288		/* grab the data for later use */
289		/* and add them to the dirlist pool */
290		tdl = SMB_REALLOC(dirlist,dirlist_len + data_len);
291
292		if (!tdl) {
293			DEBUG(0,("cli_list_new: Failed to expand dirlist\n"));
294			break;
295		} else {
296			dirlist = tdl;
297		}
298
299		memcpy(dirlist+dirlist_len,p,data_len);
300		dirlist_len += data_len;
301
302		total_received += ff_searchcount;
303
304		SAFE_FREE(rdata);
305		SAFE_FREE(rparam);
306
307		DEBUG(3,("received %d entries (eos=%d)\n",
308			 ff_searchcount,ff_eos));
309
310		if (ff_searchcount > 0)
311			loop_count = 0;
312
313		First = False;
314	}
315
316	mnt = cli_cm_get_mntpoint( cli );
317
318	for (p=dirlist,i=0;i<total_received;i++) {
319		p += interpret_long_filename(cli,info_level,p,&finfo);
320		fn( mnt,&finfo, Mask, state );
321	}
322
323	/* free up the dirlist buffer */
324	SAFE_FREE(dirlist);
325	return(total_received);
326}
327
328/****************************************************************************
329 Interpret a short filename structure.
330 The length of the structure is returned.
331****************************************************************************/
332
333static int interpret_short_filename(struct cli_state *cli, char *p,file_info *finfo)
334{
335	extern file_info def_finfo;
336
337	*finfo = def_finfo;
338
339	finfo->mode = CVAL(p,21);
340
341	/* this date is converted to GMT by make_unix_date */
342	finfo->ctime = make_unix_date(p+22);
343	finfo->mtime = finfo->atime = finfo->ctime;
344	finfo->size = IVAL(p,26);
345	clistr_pull(cli, finfo->name, p+30, sizeof(finfo->name), 12, STR_ASCII);
346	if (strcmp(finfo->name, "..") && strcmp(finfo->name, ".")) {
347		strncpy(finfo->short_name,finfo->name, sizeof(finfo->short_name)-1);
348		finfo->short_name[sizeof(finfo->short_name)-1] = '\0';
349	}
350
351	return(DIR_STRUCT_SIZE);
352}
353
354
355/****************************************************************************
356 Do a directory listing, calling fn on each file found.
357 this uses the old SMBsearch interface. It is needed for testing Samba,
358 but should otherwise not be used.
359****************************************************************************/
360
361int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
362		 void (*fn)(const char *, file_info *, const char *, void *), void *state)
363{
364	char *p;
365	int received = 0;
366	BOOL first = True;
367	char status[21];
368	int num_asked = (cli->max_xmit - 100)/DIR_STRUCT_SIZE;
369	int num_received = 0;
370	int i;
371	char *tdl, *dirlist = NULL;
372	pstring mask;
373
374	ZERO_ARRAY(status);
375
376	pstrcpy(mask,Mask);
377
378	while (1) {
379		memset(cli->outbuf,'\0',smb_size);
380		memset(cli->inbuf,'\0',smb_size);
381
382		set_message(cli->outbuf,2,0,True);
383
384		SCVAL(cli->outbuf,smb_com,SMBsearch);
385
386		SSVAL(cli->outbuf,smb_tid,cli->cnum);
387		cli_setup_packet(cli);
388
389		SSVAL(cli->outbuf,smb_vwv0,num_asked);
390		SSVAL(cli->outbuf,smb_vwv1,attribute);
391
392		p = smb_buf(cli->outbuf);
393		*p++ = 4;
394
395		p += clistr_push(cli, p, first?mask:"", -1, STR_TERMINATE);
396		*p++ = 5;
397		if (first) {
398			SSVAL(p,0,0);
399			p += 2;
400		} else {
401			SSVAL(p,0,21);
402			p += 2;
403			memcpy(p,status,21);
404			p += 21;
405		}
406
407		cli_setup_bcc(cli, p);
408		cli_send_smb(cli);
409		if (!cli_receive_smb(cli)) break;
410
411		received = SVAL(cli->inbuf,smb_vwv0);
412		if (received <= 0) break;
413
414		first = False;
415
416		tdl = SMB_REALLOC(dirlist,(num_received + received)*DIR_STRUCT_SIZE);
417
418		if (!tdl) {
419			DEBUG(0,("cli_list_old: failed to expand dirlist"));
420			SAFE_FREE(dirlist);
421			return 0;
422		}
423		else dirlist = tdl;
424
425		p = smb_buf(cli->inbuf) + 3;
426
427		memcpy(dirlist+num_received*DIR_STRUCT_SIZE,
428		       p,received*DIR_STRUCT_SIZE);
429
430		memcpy(status,p + ((received-1)*DIR_STRUCT_SIZE),21);
431
432		num_received += received;
433
434		if (cli_is_error(cli)) break;
435	}
436
437	if (!first) {
438		memset(cli->outbuf,'\0',smb_size);
439		memset(cli->inbuf,'\0',smb_size);
440
441		set_message(cli->outbuf,2,0,True);
442		SCVAL(cli->outbuf,smb_com,SMBfclose);
443		SSVAL(cli->outbuf,smb_tid,cli->cnum);
444		cli_setup_packet(cli);
445
446		SSVAL(cli->outbuf, smb_vwv0, 0); /* find count? */
447		SSVAL(cli->outbuf, smb_vwv1, attribute);
448
449		p = smb_buf(cli->outbuf);
450		*p++ = 4;
451		fstrcpy(p, "");
452		p += strlen(p) + 1;
453		*p++ = 5;
454		SSVAL(p, 0, 21);
455		p += 2;
456		memcpy(p,status,21);
457		p += 21;
458
459		cli_setup_bcc(cli, p);
460		cli_send_smb(cli);
461		if (!cli_receive_smb(cli)) {
462			DEBUG(0,("Error closing search: %s\n",cli_errstr(cli)));
463		}
464	}
465
466	for (p=dirlist,i=0;i<num_received;i++) {
467		file_info finfo;
468		p += interpret_short_filename(cli, p,&finfo);
469		fn("\\", &finfo, Mask, state);
470	}
471
472	SAFE_FREE(dirlist);
473	return(num_received);
474}
475
476/****************************************************************************
477 Do a directory listing, calling fn on each file found.
478 This auto-switches between old and new style.
479****************************************************************************/
480
481int cli_list(struct cli_state *cli,const char *Mask,uint16 attribute,
482	     void (*fn)(const char *, file_info *, const char *, void *), void *state)
483{
484	if (cli->protocol <= PROTOCOL_LANMAN1)
485		return cli_list_old(cli, Mask, attribute, fn, state);
486	return cli_list_new(cli, Mask, attribute, fn, state);
487}
488