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