416 lines · 10.4 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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; intdfscacheinfo(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;} intmapshare(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 intremap(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 intredir1(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 */intredirect(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;}