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 408