/* jcifs smb client library in Java * Copyright (C) 2008 "Michael B. Allen" <jcifs at samba dot org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package jcifs.smb; import java.util.*; import java.io.*; import jcifs.UniAddress; import jcifs.util.*; import jcifs.Config; public class Dfs { static class CacheEntry { long expiration; HashMap map; CacheEntry(long ttl) { if (ttl == 0) ttl = Dfs.TTL; expiration = System.currentTimeMillis() + ttl * 1000L; map = new HashMap(); } } static LogStream log = LogStream.getInstance(); static final boolean strictView = Config.getBoolean("jcifs.smb.client.dfs.strictView", false); static final long TTL = Config.getLong("jcifs.smb.client.dfs.ttl", 300); static final boolean DISABLED = Config.getBoolean("jcifs.smb.client.dfs.disabled", false); protected static CacheEntry FALSE_ENTRY = new Dfs.CacheEntry(0L); protected CacheEntry _domains = null; /* aka trusted domains cache */ protected CacheEntry referrals = null; public HashMap getTrustedDomains(NtlmPasswordAuthentication auth) throws SmbAuthException { if (DISABLED || auth.domain == "?") return null; if (_domains != null && System.currentTimeMillis() > _domains.expiration) { _domains = null; } if (_domains != null) return _domains.map; try { UniAddress addr = UniAddress.getByName(auth.domain, true); SmbTransport trans = SmbTransport.getSmbTransport(addr, 0); CacheEntry entry = new CacheEntry(Dfs.TTL * 10L); DfsReferral dr = trans.getDfsReferrals(auth, "", 0); if (dr != null) { DfsReferral start = dr; do { String domain = dr.server.toLowerCase(); entry.map.put(domain, new HashMap()); dr = dr.next; } while (dr != start); _domains = entry; return _domains.map; } } catch (IOException ioe) { if (log.level >= 3) ioe.printStackTrace(log); if (strictView && ioe instanceof SmbAuthException) { throw (SmbAuthException)ioe; } } return null; } public boolean isTrustedDomain(String domain, NtlmPasswordAuthentication auth) throws SmbAuthException { HashMap domains = getTrustedDomains(auth); if (domains == null) return false; domain = domain.toLowerCase(); return domains.get(domain) != null; } public SmbTransport getDc(String domain, NtlmPasswordAuthentication auth) throws SmbAuthException { if (DISABLED) return null; try { UniAddress addr = UniAddress.getByName(domain, true); SmbTransport trans = SmbTransport.getSmbTransport(addr, 0); DfsReferral dr = trans.getDfsReferrals(auth, "\\" + domain, 1); if (dr != null) { DfsReferral start = dr; IOException e = null; do { try { addr = UniAddress.getByName(dr.server); return SmbTransport.getSmbTransport(addr, 0); } catch (IOException ioe) { e = ioe; } dr = dr.next; } while (dr != start); throw e; } } catch (IOException ioe) { if (log.level >= 3) ioe.printStackTrace(log); if (strictView && ioe instanceof SmbAuthException) { throw (SmbAuthException)ioe; } } return null; } public DfsReferral getReferral(SmbTransport trans, String domain, String root, String path, NtlmPasswordAuthentication auth) throws SmbAuthException { if (DISABLED) return null; try { String p = "\\" + domain + "\\" + root; if (path != null) p += path; DfsReferral dr = trans.getDfsReferrals(auth, p, 0); if (dr != null) return dr; } catch (IOException ioe) { if (log.level >= 4) ioe.printStackTrace(log); if (strictView && ioe instanceof SmbAuthException) { throw (SmbAuthException)ioe; } } return null; } public synchronized DfsReferral resolve(String domain, String root, String path, NtlmPasswordAuthentication auth) throws SmbAuthException { DfsReferral dr = null; long now = System.currentTimeMillis(); if (DISABLED || root.equals("IPC$")) { return null; } /* domains that can contain DFS points to maps of roots for each */ HashMap domains = getTrustedDomains(auth); if (domains != null) { domain = domain.toLowerCase(); /* domain-based DFS root shares to links for each */ HashMap roots = (HashMap)domains.get(domain); if (roots != null) { SmbTransport trans = null; root = root.toLowerCase(); /* The link entries contain maps of referrals by path representing DFS links. * Note that paths are relative to the root like "\" and not "\example.com\root". */ CacheEntry links = (CacheEntry)roots.get(root); if (links != null && now > links.expiration) { roots.remove(root); links = null; } if (links == null) { if ((trans = getDc(domain, auth)) == null) return null; dr = getReferral(trans, domain, root, path, auth); if (dr != null) { int len = 1 + domain.length() + 1 + root.length(); links = new CacheEntry(0L); DfsReferral tmp = dr; do { if (path == null) { /* Store references to the map and key so that * SmbFile.resolveDfs can re-insert the dr list with * the dr that was successful so that subsequent * attempts to resolve DFS use the last successful * referral first. */ tmp.map = links.map; tmp.key = "\\"; } tmp.pathConsumed -= len; tmp = tmp.next; } while (tmp != dr); if (dr.key != null) links.map.put(dr.key, dr); roots.put(root, links); } else if (path == null) { roots.put(root, Dfs.FALSE_ENTRY); } } else if (links == Dfs.FALSE_ENTRY) { links = null; } if (links != null) { String link = "\\"; /* Lookup the domain based DFS root target referral. Note the * path is just "\" and not "\example.com\root". */ dr = (DfsReferral)links.map.get(link); if (dr != null && now > dr.expiration) { links.map.remove(link); dr = null; } if (dr == null) { if (trans == null) if ((trans = getDc(domain, auth)) == null) return null; dr = getReferral(trans, domain, root, path, auth); if (dr != null) { dr.pathConsumed -= 1 + domain.length() + 1 + root.length(); dr.link = link; links.map.put(link, dr); } } } } } if (dr == null && path != null) { /* We did not match a domain based root. Now try to match the * longest path in the list of stand-alone referrals. */ if (referrals != null && now > referrals.expiration) { referrals = null; } if (referrals == null) { referrals = new CacheEntry(0); } String key = "\\" + domain + "\\" + root; if (path.equals("\\") == false) key += path; key = key.toLowerCase(); Iterator iter = referrals.map.keySet().iterator(); while (iter.hasNext()) { String _key = (String)iter.next(); int _klen = _key.length(); boolean match = false; if (_klen == key.length()) { match = _key.equals(key); } else if (_klen < key.length()) { match = _key.regionMatches(0, key, 0, _klen) && key.charAt(_klen) == '\\'; } if (match) dr = (DfsReferral)referrals.map.get(_key); } } return dr; } synchronized void insert(String path, DfsReferral dr) { int s1, s2; String server, share, key; if (DISABLED) return; s1 = path.indexOf('\\', 1); s2 = path.indexOf('\\', s1 + 1); server = path.substring(1, s1); share = path.substring(s1 + 1, s2); key = path.substring(0, dr.pathConsumed).toLowerCase(); /* Samba has a tendency to return referral paths and pathConsumed values * in such a way that there can be a slash at the end of the path. This * causes problems matching keys in resolve() where an extra slash causes * a mismatch. This strips trailing slashes from all keys to eliminate * this problem. */ int ki = key.length(); while (ki > 1 && key.charAt(ki - 1) == '\\') { ki--; } if (ki < key.length()) { key = key.substring(0, ki); } /* Subtract the server and share from the pathConsumed so that * it refects the part of the relative path consumed and not * the entire path. */ dr.pathConsumed -= 1 + server.length() + 1 + share.length(); if (referrals != null && (System.currentTimeMillis() + 10000) > referrals.expiration) { referrals = null; } if (referrals == null) { referrals = new CacheEntry(0); } referrals.map.put(key, dr); } }