summaryrefslogtreecommitdiff
path: root/sys/src/cmd/cifs/dfs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/cifs/dfs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/cifs/dfs.c')
-rwxr-xr-xsys/src/cmd/cifs/dfs.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/sys/src/cmd/cifs/dfs.c b/sys/src/cmd/cifs/dfs.c
new file mode 100755
index 000000000..73c9a3e01
--- /dev/null
+++ b/sys/src/cmd/cifs/dfs.c
@@ -0,0 +1,407 @@
+/*
+ * DNS referrals give two main fields: the path to connect to in
+ * /Netbios-host-name/share-name/path... form and a network
+ * address of how to find this path of the form domain.dom.
+ *
+ * The domain.dom is resolved in XP/Win2k etc using AD to do
+ * a lookup (this is a consensus view, I don't think anyone
+ * has proved it). I cannot do this as AD needs Kerberos and
+ * LDAP which I don't have.
+ *
+ * Instead I just use the NetBios names passed in the paths
+ * and assume that the servers are in the same DNS domain as me
+ * and have their DNS hostname set the same as their netbios
+ * called-name; thankfully this always seems to be the case (so far).
+ *
+ * I have not added support for starting another instance of
+ * cifs to connect to other servers referenced in DFS links,
+ * this is not a problem for me and I think it hides a load
+ * of problems of its own wrt plan9's private namespaces.
+ *
+ * The proximity of my test server (AD enabled) is always 0 but some
+ * systems may report more meaningful values. The expiry time is
+ * similarly zero, so I guess at 5 mins.
+ *
+ * If the redirection points to a "hidden" share (i.e., its name
+ * ends in a $) then the type of the redirection is 0 (unknown) even
+ * though it is a CIFS share.
+ *
+ * It would be nice to add a check for which subnet a server is on
+ * so our first choice is always the the server on the same subnet
+ * as us which replies to a ping (i.e., is up). This could short-
+ * circuit the tests as the a server on the same subnet will always
+ * be the fastest to get to.
+ *
+ * If I set Flags2_DFS then I don't see DFS links, I just get
+ * path not found (?!).
+ *
+ * If I do a QueryFileInfo of a DFS link point (IE when I'am doing a walk)
+ * Then I just see a directory, its not until I try to walk another level
+ * That I get "IO reparse tag not handled" error rather than
+ * "Path not covered".
+ *
+ * If I check the extended attributes of the QueryFileInfo in walk() then I can
+ * see this is a reparse point and so I can get the referral. The only
+ * problem here is that samba and the like may not support this.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <libsec.h>
+#include <ctype.h>
+#include <9p.h>
+#include "cifs.h"
+
+enum {
+ Nomatch, /* not found in cache */
+ Exactmatch, /* perfect match found */
+ Badmatch /* matched but wrong case */
+};
+
+#define SINT_MAX 0x7fffffff
+
+typedef struct Dfscache Dfscache;
+struct Dfscache {
+ Dfscache*next; /* next entry */
+ char *src;
+ char *host;
+ char *share;
+ char *path;
+ long expiry; /* expiry time in sec */
+ long rtt; /* round trip time, nsec */
+ int prox; /* proximity, lower = closer */
+};
+
+Dfscache *Cache;
+
+int
+dfscacheinfo(Fmt *f)
+{
+ long ex;
+ Dfscache *cp;
+
+ for(cp = Cache; cp; cp = cp->next){
+ ex = cp->expiry - time(nil);
+ if(ex < 0)
+ ex = -1;
+ fmtprint(f, "%-42s %6ld %8.1f %4d %-16s %-24s %s\n",
+ cp->src, ex, (double)cp->rtt/1000.0L, cp->prox,
+ cp->host, cp->share, cp->path);
+ }
+ return 0;
+}
+
+char *
+trimshare(char *s)
+{
+ char *p;
+ static char name[128];
+
+ strncpy(name, s, sizeof(name));
+ name[sizeof(name)-1] = 0;
+ if((p = strrchr(name, '$')) != nil && p[1] == 0)
+ *p = 0;
+ return name;
+}
+
+static Dfscache *
+lookup(char *path, int *match)
+{
+ int len, n, m;
+ Dfscache *cp, *best;
+
+ if(match)
+ *match = Nomatch;
+
+ len = 0;
+ best = nil;
+ m = strlen(path);
+ for(cp = Cache; cp; cp = cp->next){
+ n = strlen(cp->src);
+ if(n < len)
+ continue;
+ if(strncmp(path, cp->src, n) != 0)
+ continue;
+ if(path[n] != 0 && path[n] != '/')
+ continue;
+ best = cp;
+ len = n;
+ if(n == m){
+ if(match)
+ *match = Exactmatch;
+ break;
+ }
+ }
+ return best;
+}
+
+char *
+mapfile(char *opath)
+{
+ int exact;
+ Dfscache *cp;
+ char *p, *path;
+ static char npath[MAX_DFS_PATH];
+
+ path = opath;
+ if((cp = lookup(path, &exact)) != nil){
+ snprint(npath, sizeof npath, "/%s%s%s%s", cp->share,
+ *cp->path? "/": "", cp->path, path + strlen(cp->src));
+ path = npath;
+ }
+
+ if((p = strchr(path+1, '/')) == nil)
+ p = "/";
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("mapfile src=%q => dst=%q\n", opath, p);
+ return p;
+}
+
+int
+mapshare(char *path, Share **osp)
+{
+ int i;
+ Share *sp;
+ Dfscache *cp;
+ char *s, *try;
+ char *tail[] = { "", "$" };
+
+ if((cp = lookup(path, nil)) == nil)
+ return 0;
+
+ for(sp = Shares; sp < Shares+Nshares; sp++){
+ s = trimshare(sp->name);
+ if(cistrcmp(cp->share, s) != 0)
+ continue;
+ if(Checkcase && strcmp(cp->share, s) != 0)
+ continue;
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("mapshare, already connected, src=%q => dst=%q\n", path, sp->name);
+ *osp = sp;
+ return 0;
+ }
+ /*
+ * Try to autoconnect to share if it is not known. Note even if you
+ * didn't specify any shares and let the system autoconnect you may
+ * not already have the share you need as RAP (which we use) throws
+ * away names > 12 chars long. If we where to use RPC then this block
+ * of code would be less important, though it would still be useful
+ * to catch Shares added since cifs(1) was started.
+ */
+ sp = Shares + Nshares;
+ for(i = 0; i < 2; i++){
+ try = smprint("%s%s", cp->share, tail[i]);
+ if(CIFStreeconnect(Sess, Sess->cname, try, sp) == 0){
+ sp->name = try;
+ *osp = sp;
+ Nshares++;
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("mapshare connected, src=%q dst=%q\n",
+ path, cp->share);
+ return 0;
+ }
+ free(try);
+ }
+
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("mapshare failed src=%s\n", path);
+ werrstr("not found");
+ return -1;
+}
+
+/*
+ * Rtt_tol is the fractional tollerance for RTT comparisons.
+ * If a later (further down the list) host's RTT is less than
+ * 1/Rtt_tol better than my current best then I don't bother
+ * with it. This biases me towards entries at the top of the list
+ * which Active Directory has already chosen for me and prevents
+ * noise in RTTs from pushing me to more distant machines.
+ */
+static int
+remap(Dfscache *cp, Refer *re)
+{
+ int n;
+ long rtt;
+ char *p, *a[4];
+ enum {
+ Hostname = 1,
+ Sharename = 2,
+ Pathname = 3,
+
+ Rtt_tol = 10
+ };
+
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap %s\n", re->addr);
+
+ for(p = re->addr; *p; p++)
+ if(*p == '\\')
+ *p = '/';
+
+ if(cp->prox < re->prox){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap %d < %d\n", cp->prox, re->prox);
+ return -1;
+ }
+ if((n = getfields(re->addr, a, sizeof(a), 0, "/")) < 3){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap nfields=%d\n", n);
+ return -1;
+ }
+ if((rtt = ping(a[Hostname], Dfstout)) == -1){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap ping failed\n");
+ return -1;
+ }
+ if(cp->rtt < rtt && (rtt/labs(rtt-cp->rtt)) < Rtt_tol){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap bad ping %ld < %ld && %ld < %d\n",
+ cp->rtt, rtt, (rtt/labs(rtt-cp->rtt)), Rtt_tol);
+ return -1;
+ }
+
+ if(n < 4)
+ a[Pathname] = "";
+ if(re->ttl == 0)
+ re->ttl = 60*5;
+
+ free(cp->host);
+ free(cp->share);
+ free(cp->path);
+ cp->rtt = rtt;
+ cp->prox = re->prox;
+ cp->expiry = time(nil)+re->ttl;
+ cp->host = estrdup9p(a[Hostname]);
+ cp->share = estrdup9p(trimshare(a[Sharename]));
+ cp->path = estrdup9p(a[Pathname]);
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print(" remap ping OK prox=%d host=%s share=%s path=%s\n",
+ cp->prox, cp->host, cp->share, cp->path);
+ return 0;
+}
+
+static int
+redir1(Session *s, char *path, Dfscache *cp, int level)
+{
+ Refer retab[16], *re;
+ int n, gflags, used, found;
+
+ if(level > 8)
+ return -1;
+
+ if((n = T2getdfsreferral(s, &Ipc, path, &gflags, &used, retab,
+ nelem(retab))) == -1)
+ return -1;
+
+ if(! (gflags & DFS_HEADER_ROOT))
+ used = SINT_MAX;
+
+ found = 0;
+ for(re = retab; re < retab+n; re++){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("referal level=%d prox=%d path=%q addr=%q\n",
+ level, re->prox, re->path, re->addr);
+
+ if(gflags & DFS_HEADER_STORAGE){
+ if(remap(cp, re) == 0)
+ found = 1;
+ } else{
+ if(redir1(s, re->addr, cp, level+1) != -1) /* ???? */
+ found = 1;
+ }
+ free(re->addr);
+ free(re->path);
+ }
+
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("referal level=%d path=%q found=%d used=%d\n",
+ level, path, found, used);
+ if(!found)
+ return -1;
+ return used;
+}
+
+/*
+ * We can afford to ignore the used count returned by redir
+ * because of the semantics of 9p - we always walk to directories
+ * ome and we a time and we always walk before any other file operations
+ */
+int
+redirect(Session *s, Share *sp, char *path)
+{
+ int match;
+ char *unc;
+ Dfscache *cp;
+
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect name=%q path=%q\n", sp->name, path);
+
+ cp = lookup(path, &match);
+ if(match == Badmatch)
+ return -1;
+
+ if(cp && match == Exactmatch){
+ if(cp->expiry >= time(nil)){ /* cache hit */
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect cache=hit src=%q => share=%q path=%q\n",
+ cp->src, cp->share, cp->path);
+ return 0;
+
+ } else{ /* cache hit, but entry stale */
+ cp->rtt = SINT_MAX;
+ cp->prox = SINT_MAX;
+
+ unc = smprint("//%s/%s/%s%s%s", s->auth->windom,
+ cp->share, cp->path, *cp->path? "/": "",
+ path + strlen(cp->src) + 1);
+ if(unc == nil)
+ sysfatal("no memory: %r");
+ if(redir1(s, unc, cp, 1) == -1){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect refresh failed unc=%q\n",
+ unc);
+ free(unc);
+ return -1;
+ }
+ free(unc);
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect refresh cache=stale src=%q => share=%q path=%q\n",
+ cp->src, cp->share, cp->path);
+ return 0;
+ }
+ }
+
+
+ /* in-exact match or complete miss */
+ if(cp)
+ unc = smprint("//%s/%s/%s%s%s", s->auth->windom, cp->share,
+ cp->path, *cp->path? "/": "", path + strlen(cp->src) + 1);
+ else
+ unc = smprint("//%s%s", s->auth->windom, path);
+ if(unc == nil)
+ sysfatal("no memory: %r");
+
+ cp = emalloc9p(sizeof(Dfscache));
+ memset(cp, 0, sizeof(Dfscache));
+ cp->rtt = SINT_MAX;
+ cp->prox = SINT_MAX;
+
+ if(redir1(s, unc, cp, 1) == -1){
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect new failed unc=%q\n", unc);
+ free(unc);
+ free(cp);
+ return -1;
+ }
+ free(unc);
+
+ cp->src = estrdup9p(path);
+ cp->next = Cache;
+ Cache = cp;
+ if(Debug && strstr(Debug, "dfs") != nil)
+ print("redirect cache=miss src=%q => share=%q path=%q\n",
+ cp->src, cp->share, cp->path);
+ return 0;
+}
+