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
dfscacheinfo(Fmt * f)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 *
trimshare(char * s)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 *
lookup(char * path,int * match)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 *
mapfile(char * opath)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
mapshare(char * path,Share ** osp)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
remap(Dfscache * cp,Refer * re)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
redir1(Session * s,char * path,Dfscache * cp,int level)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
redirect(Session * s,Share * sp,char * path)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
408