Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 * DNS referrals give two main fields: the path to connect to in
3
 * /Netbios-host-name/share-name/path... form and a network
4
 * address of how to find this path of the form domain.dom.
5
 *
6
 * The domain.dom is resolved in XP/Win2k etc using AD to do
7
 * a lookup (this is a consensus view, I don't think anyone
8
 * has proved it).  I cannot do this as AD needs Kerberos and
9
 * LDAP which I don't have.
10
 *
11
 * Instead I just use the NetBios names passed in the paths
12
 * and assume that the servers are in the same DNS domain as me
13
 * and have their DNS hostname set the same as their netbios
14
 * called-name; thankfully this always seems to be the case (so far).
15
 *
16
 * I have not added support for starting another instance of
17
 * cifs to connect to other servers referenced in DFS links,
18
 * this is not a problem for me and I think it hides a load
19
 * of problems of its own wrt plan9's private namespaces.
20
 *
21
 * The proximity of my test server (AD enabled) is always 0 but some
22
 * systems may report more meaningful values.  The expiry time is
23
 * similarly zero, so I guess at 5 mins.
24
 *
25
 * If the redirection points to a "hidden" share (i.e., its name
26
 * ends in a $) then the type of the redirection is 0 (unknown) even
27
 * though it is a CIFS share.
28
 *
29
 * It would be nice to add a check for which subnet a server is on
30
 * so our first choice is always the the server on the same subnet
31
 * as us which replies to a ping (i.e., is up).  This could short-
32
 * circuit the tests as the a server on the same subnet will always
33
 * be the fastest to get to.
34
 *
35
 * If I set Flags2_DFS then I don't see DFS links, I just get
36
 * path not found (?!).
37
 *
38
 * If I do a QueryFileInfo of a DFS link point (IE when I'am doing a walk)
39
 * Then I just see a directory, its not until I try to walk another level
40
 * That I get  "IO reparse tag not handled" error rather than
41
 * "Path not covered".
42
 *
43
 * If I check the extended attributes of the QueryFileInfo in walk() then I can
44
 * see this is a reparse point and so I can get the referral.  The only
45
 * problem here is that samba and the like may not support this.
46
 */
47
#include <u.h>
48
#include <libc.h>
49
#include <fcall.h>
50
#include <thread.h>
51
#include <libsec.h>
52
#include <ctype.h>
53
#include <9p.h>
54
#include "cifs.h"
55
 
56
enum {
57
	Nomatch,	/* not found in cache */
58
	Exactmatch,	/* perfect match found */
59
	Badmatch	/* matched but wrong case */
60
};
61
 
62
#define SINT_MAX	0x7fffffff
63
 
64
typedef struct Dfscache Dfscache;
65
struct Dfscache {
66
	Dfscache*next;		/* next entry */
67
	char	*src;
68
	char	*host;
69
	char	*share;
70
	char	*path;
71
	long	expiry;		/* expiry time in sec */
72
	long	rtt;		/* round trip time, nsec */
73
	int	prox;		/* proximity, lower = closer */
74
};
75
 
76
Dfscache *Cache;
77
 
78
int
79
dfscacheinfo(Fmt *f)
80
{
81
	long ex;
82
	Dfscache *cp;
83
 
84
	for(cp = Cache; cp; cp = cp->next){
85
		ex = cp->expiry - time(nil);
86
		if(ex < 0)
87
			ex = -1;
88
		fmtprint(f, "%-42s %6ld %8.1f %4d %-16s %-24s %s\n",
89
			cp->src, ex, (double)cp->rtt/1000.0L, cp->prox,
90
			cp->host, cp->share, cp->path);
91
	}
92
	return 0;
93
}
94
 
95
char *
96
trimshare(char *s)
97
{
98
	char *p;
99
	static char name[128];
100
 
101
	strncpy(name, s, sizeof(name));
102
	name[sizeof(name)-1] = 0;
103
	if((p = strrchr(name, '$')) != nil && p[1] == 0)
104
		*p = 0;
105
	return name;
106
}
107
 
108
static Dfscache *
109
lookup(char *path, int *match)
110
{
111
	int len, n, m;
112
	Dfscache *cp, *best;
113
 
114
	if(match)
115
		*match = Nomatch;
116
 
117
	len = 0;
118
	best = nil;
119
	m = strlen(path);
120
	for(cp = Cache; cp; cp = cp->next){
121
		n = strlen(cp->src);
122
		if(n < len)
123
			continue;
124
		if(strncmp(path, cp->src, n) != 0)
125
			continue;
126
		if(path[n] != 0 && path[n] != '/')
127
			continue;
128
		best = cp;
129
		len = n;
130
		if(n == m){
131
			if(match)
132
				*match = Exactmatch;
133
			break;
134
		}
135
	}
136
	return best;
137
}
138
 
139
char *
140
mapfile(char *opath)
141
{
142
	int exact;
143
	Dfscache *cp;
144
	char *p, *path;
145
	static char npath[MAX_DFS_PATH];
146
 
147
	path = opath;
148
	if((cp = lookup(path, &exact)) != nil){
149
		snprint(npath, sizeof npath, "/%s%s%s%s", cp->share,
150
			*cp->path? "/": "", cp->path, path + strlen(cp->src));
151
		path = npath;
152
	}
153
 
154
	if((p = strchr(path+1, '/')) == nil)
155
		p = "/";
156
	if(Debug && strstr(Debug, "dfs") != nil)
157
		print("mapfile src=%q => dst=%q\n", opath, p);
158
	return p;
159
}
160
 
161
int
162
mapshare(char *path, Share **osp)
163
{
164
	int i;
165
	Share *sp;
166
	Dfscache *cp;
167
	char *s, *try;
168
	char *tail[] = { "", "$" };
169
 
170
	if((cp = lookup(path, nil)) == nil)
171
		return 0;
172
 
173
	for(sp = Shares; sp < Shares+Nshares; sp++){
174
		s = trimshare(sp->name);
175
		if(cistrcmp(cp->share, s) != 0)
176
			continue;
177
		if(Checkcase && strcmp(cp->share, s) != 0)
178
			continue;
179
		if(Debug && strstr(Debug, "dfs") != nil)
180
			print("mapshare, already connected, src=%q => dst=%q\n", path, sp->name);
181
		*osp = sp;
182
		return 0;
183
	}
184
	/*
185
	 * Try to autoconnect to share if it is not known.  Note even if you
186
	 * didn't specify any shares and let the system autoconnect you may
187
	 * not already have the share you need as RAP (which we use) throws
188
	 * away names > 12 chars long.  If we where to use RPC then this block
189
	 * of code would be less important, though it would still be useful
190
	 * to catch Shares added since cifs(1) was started.
191
	 */
192
	sp = Shares + Nshares;
193
	for(i = 0; i < 2; i++){
194
		try = smprint("%s%s", cp->share, tail[i]);
195
		if(CIFStreeconnect(Sess, Sess->cname, try, sp) == 0){
196
			sp->name = try;
197
			*osp = sp;
198
			Nshares++;
199
			if(Debug && strstr(Debug, "dfs") != nil)
200
				print("mapshare connected, src=%q dst=%q\n",
201
					path, cp->share);
202
			return 0;
203
		}
204
		free(try);
205
	}
206
 
207
	if(Debug && strstr(Debug, "dfs") != nil)
208
		print("mapshare failed src=%s\n", path);
209
	werrstr("not found");
210
	return -1;
211
}
212
 
213
/*
214
 * Rtt_tol is the fractional tollerance for RTT comparisons.
215
 * If a later (further down the list) host's RTT is less than
216
 * 1/Rtt_tol better than my current best then I don't bother
217
 * with it.  This biases me towards entries at the top of the list
218
 * which Active Directory has already chosen for me and prevents
219
 * noise in RTTs from pushing me to more distant machines.
220
 */
221
static int
222
remap(Dfscache *cp, Refer *re)
223
{
224
	int n;
225
	long rtt;
226
	char *p, *a[4];
227
	enum {
228
		Hostname = 1,
229
		Sharename = 2,
230
		Pathname = 3,
231
 
232
		Rtt_tol = 10
233
	};
234
 
235
	if(Debug && strstr(Debug, "dfs") != nil)
236
		print("	remap %s\n", re->addr);
237
 
238
	for(p = re->addr; *p; p++)
239
		if(*p == '\\')
240
			*p = '/';
241
 
242
	if(cp->prox < re->prox){
243
		if(Debug && strstr(Debug, "dfs") != nil)
244
			print("	remap %d < %d\n", cp->prox, re->prox);
245
		return -1;
246
	}
247
	if((n = getfields(re->addr, a, sizeof(a), 0, "/")) < 3){
248
		if(Debug && strstr(Debug, "dfs") != nil)
249
			print("	remap nfields=%d\n", n);
250
		return -1;
251
	}
252
	if((rtt = ping(a[Hostname], Dfstout)) == -1){
253
		if(Debug && strstr(Debug, "dfs") != nil)
254
			print("	remap ping failed\n");
255
		return -1;
256
	}
257
	if(cp->rtt < rtt && (rtt/labs(rtt-cp->rtt)) < Rtt_tol){
258
		if(Debug && strstr(Debug, "dfs") != nil)
259
			print("	remap bad ping %ld < %ld && %ld < %d\n",
260
				cp->rtt, rtt, (rtt/labs(rtt-cp->rtt)), Rtt_tol);
261
		return -1;
262
	}
263
 
264
	if(n < 4)
265
		a[Pathname] = "";
266
	if(re->ttl == 0)
267
		re->ttl = 60*5;
268
 
269
	free(cp->host);
270
	free(cp->share);
271
	free(cp->path);
272
	cp->rtt = rtt;
273
	cp->prox = re->prox;
274
	cp->expiry = time(nil)+re->ttl;
275
	cp->host = estrdup9p(a[Hostname]);
276
	cp->share = estrdup9p(trimshare(a[Sharename]));
277
	cp->path = estrdup9p(a[Pathname]);
278
	if(Debug && strstr(Debug, "dfs") != nil)
279
		print("	remap ping OK prox=%d host=%s share=%s path=%s\n",
280
			cp->prox, cp->host, cp->share, cp->path);
281
	return 0;
282
}
283
 
284
static int
285
redir1(Session *s, char *path, Dfscache *cp, int level)
286
{
287
	Refer retab[16], *re;
288
	int n, gflags, used, found;
289
 
290
	if(level > 8)
291
		return -1;
292
 
293
	if((n = T2getdfsreferral(s, &Ipc, path, &gflags, &used, retab,
294
	    nelem(retab))) == -1)
295
		return -1;
296
 
297
	if(! (gflags & DFS_HEADER_ROOT))
298
		used = SINT_MAX;
299
 
300
	found = 0;
301
	for(re = retab; re < retab+n; re++){
302
		if(Debug && strstr(Debug, "dfs") != nil)
303
			print("referal level=%d prox=%d path=%q addr=%q\n",
304
				level, re->prox, re->path, re->addr);
305
 
306
		if(gflags & DFS_HEADER_STORAGE){
307
			if(remap(cp, re) == 0)
308
				found = 1;
309
		} else{
310
			if(redir1(s, re->addr, cp, level+1) != -1)  /* ???? */
311
				found = 1;
312
		}
313
		free(re->addr);
314
		free(re->path);
315
	}
316
 
317
	if(Debug && strstr(Debug, "dfs") != nil)
318
		print("referal level=%d path=%q found=%d used=%d\n",
319
			level, path, found, used);
320
	if(!found)
321
		return -1;
322
	return used;
323
}
324
 
325
/*
326
 * We can afford to ignore the used count returned by redir
327
 * because of the semantics of 9p - we always walk to directories
328
 * ome and we a time and we always walk before any other file operations
329
 */
330
int
331
redirect(Session *s, Share *sp, char *path)
332
{
333
	int match;
334
	char *unc;
335
	Dfscache *cp;
336
 
337
	if(Debug && strstr(Debug, "dfs") != nil)
338
		print("redirect name=%q path=%q\n", sp->name, path);
339
 
340
	cp = lookup(path, &match);
341
	if(match == Badmatch)
342
		return -1;
343
 
344
	if(cp && match == Exactmatch){
345
		if(cp->expiry >= time(nil)){		/* cache hit */
346
			if(Debug && strstr(Debug, "dfs") != nil)
347
				print("redirect cache=hit src=%q => share=%q path=%q\n",
348
					cp->src, cp->share, cp->path);
349
			return 0;
350
 
351
		} else{				/* cache hit, but entry stale */
352
			cp->rtt = SINT_MAX;
353
			cp->prox = SINT_MAX;
354
 
355
			unc = smprint("//%s/%s/%s%s%s", s->auth->windom,
356
				cp->share, cp->path, *cp->path? "/": "",
357
				path + strlen(cp->src) + 1);
358
			if(unc == nil)
359
				sysfatal("no memory: %r");
360
			if(redir1(s, unc, cp, 1) == -1){
361
				if(Debug && strstr(Debug, "dfs") != nil)
362
					print("redirect refresh failed unc=%q\n",
363
						unc);
364
				free(unc);
365
				return -1;
366
			}
367
			free(unc);
368
			if(Debug && strstr(Debug, "dfs") != nil)
369
				print("redirect refresh cache=stale src=%q => share=%q path=%q\n",
370
					cp->src, cp->share, cp->path);
371
			return 0;
372
		}
373
	}
374
 
375
 
376
	/* in-exact match or complete miss */
377
	if(cp)
378
		unc = smprint("//%s/%s/%s%s%s", s->auth->windom, cp->share,
379
			cp->path, *cp->path? "/": "", path + strlen(cp->src) + 1);
380
	else
381
		unc = smprint("//%s%s", s->auth->windom, path);
382
	if(unc == nil)
383
		sysfatal("no memory: %r");
384
 
385
	cp = emalloc9p(sizeof(Dfscache));
386
	memset(cp, 0, sizeof(Dfscache));
387
	cp->rtt = SINT_MAX;
388
	cp->prox = SINT_MAX;
389
 
390
	if(redir1(s, unc, cp, 1) == -1){
391
		if(Debug && strstr(Debug, "dfs") != nil)
392
			print("redirect new failed unc=%q\n", unc);
393
		free(unc);
394
		free(cp);
395
		return -1;
396
	}
397
	free(unc);
398
 
399
	cp->src = estrdup9p(path);
400
	cp->next = Cache;
401
	Cache = cp;
402
	if(Debug && strstr(Debug, "dfs") != nil)
403
		print("redirect cache=miss src=%q => share=%q path=%q\n",
404
			cp->src, cp->share, cp->path);
405
	return 0;
406
}
407