]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/cifs/dfs.c
dc: fix off by one in stack overflow check (thanks BurnZeZ)
[plan9front.git] / sys / src / cmd / cifs / dfs.c
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