package org.dcache.chimera.nfsv41.door; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.net.InternetDomainName; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.CommunicationException; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.Principal; import java.util.Hashtable; import java.util.Set; import diskCacheV111.util.CacheException; import org.dcache.auth.GidPrincipal; import org.dcache.auth.GroupNamePrincipal; import org.dcache.auth.LoginStrategy; import org.dcache.auth.Origin; import org.dcache.auth.Subjects; import org.dcache.auth.UidPrincipal; import org.dcache.auth.UserNamePrincipal; import org.dcache.nfs.v4.NfsIdMapping; import org.dcache.xdr.RpcLoginService; import org.dcache.xdr.XdrTransport; public class StrategyIdMapper implements NfsIdMapping, RpcLoginService { private static final Logger LOGGER = LoggerFactory.getLogger(StrategyIdMapper.class); /** * DNS TXT record attribute name */ private final static String NFS4_DNS_TXT_REC = "_nfsv4idmapdomain"; /** * nfs4domain to use if all other methods to auto-discover it have failed. */ private final static String DEFAULT_NFS4_DOMAIN = "localdomain"; private final String NOBODY = "nobody"; private final int NODOBY_ID = -1; private final LoginStrategy _remoteLoginStrategy; private final String _domain; private boolean _fallbackToNumeric = false; public StrategyIdMapper(LoginStrategy remoteLoginStrategy, String domain) throws NamingException, UnknownHostException { _remoteLoginStrategy = remoteLoginStrategy; _domain = discoverNFS4Domain(domain); } public void setFallBackToNumeric(boolean fallBack) { _fallbackToNumeric = fallBack; } public boolean getFallBackToNumeric() { return _fallbackToNumeric; } @Override public String gidToPrincipal(int id) { // shortcut.... if (id < 0) { return NOBODY; } try { Set<Principal> principals = _remoteLoginStrategy.reverseMap(new GidPrincipal(id, false)); for (Principal principal : principals) { if (principal instanceof GroupNamePrincipal) { return addDomain(principal.getName()); } } } catch (CacheException e) { LOGGER.debug("Failed to reverseMap for gid {} : {}", id, e); } return numericStringIfAllowed(id); } @Override public int principalToGid(String name) { try { String principal = stripDomain(name); Principal gidPrincipal = _remoteLoginStrategy.map(new GroupNamePrincipal(principal)); if (gidPrincipal instanceof GidPrincipal) { return (int) ((GidPrincipal) gidPrincipal).getGid(); } } catch (CacheException e) { LOGGER.debug("Failed to map principal {} : {}", name, e); } return tryNumericIfAllowed(name); } @Override public int principalToUid(String name) { try { String principal = stripDomain(name); Principal uidPrincipal = _remoteLoginStrategy.map(new UserNamePrincipal(principal)); if (uidPrincipal instanceof UidPrincipal) { return (int) ((UidPrincipal) uidPrincipal).getUid(); } } catch (CacheException e) { LOGGER.debug("Failed to map principal {} : {}", name, e); } return tryNumericIfAllowed(name); } @Override public String uidToPrincipal(int id) { // shortcut.... if (id < 0) { return NOBODY; } try { Set<Principal> principals = _remoteLoginStrategy.reverseMap(new UidPrincipal(id)); for (Principal principal : principals) { if (principal instanceof UserNamePrincipal) { return addDomain(principal.getName()); } } } catch (CacheException e) { LOGGER.debug("Failed to reverseMap for uid {} : {}", id, e); } return numericStringIfAllowed(id); } private String stripDomain(String s) { int n = s.indexOf('@'); if (n != -1) { return s.substring(0, n); } return s; } private String addDomain(String s) { return s + "@" + _domain; } private int tryNumericIfAllowed(String id) { if ( !_fallbackToNumeric ) { return NODOBY_ID; } else { try { return Integer.parseInt(id); } catch (NumberFormatException e) { return NODOBY_ID; } } } private String numericStringIfAllowed(int id) { return _fallbackToNumeric ? String.valueOf(id) :NOBODY; } @Override public Subject login(XdrTransport xt, GSSContext gssc) { try { KerberosPrincipal principal = new KerberosPrincipal(gssc.getSrcName().toString()); Subject in = new Subject(); in.getPrincipals().add(principal); in.getPrincipals().add(new Origin(xt.getRemoteSocketAddress().getAddress())); in.setReadOnly(); return _remoteLoginStrategy.login(in).getSubject(); }catch(GSSException | CacheException e) { LOGGER.debug("Failed to login for : {} : {}", gssc, e.toString()); } return Subjects.NOBODY; } /** * Auto-discovers NFSv4 domain from DNS server. if provided {@code * configuredDomain} is null or an empty string, a local DNS server will * be queried for the {@code _nfsv4idmapdomain} text record. If the record exists * that will be used as the domain. When the record does not exist, the domain * part of the DNS domain will used. * * @see <a href="http://docs.oracle.com/cd/E19253-01/816-4555/epubp/index.html">nfsmapid and DNS TXT Records</a> * * @param configuredDomain nfs4domain to be used. * @return NFSv4 domain */ private String discoverNFS4Domain(String configuredDomain) throws NamingException, UnknownHostException { if (!Strings.isNullOrEmpty(configuredDomain)) { LOGGER.info("Using config provided nfs4domain: {}", configuredDomain); return configuredDomain; } // Java doesn't provide a way to discover local domain..... String fqdn = InetAddress.getLocalHost().getCanonicalHostName(); InternetDomainName domainName = InternetDomainName.from(fqdn); if (!domainName.hasParent()) { // DNS is not configured, or we got something like localhost LOGGER.warn("The FQDN {} has no parent, using default nfs4domain:", fqdn, DEFAULT_NFS4_DOMAIN); return DEFAULT_NFS4_DOMAIN; } // try to get TXT record from DNS a server Hashtable<String, String> env = new Hashtable<>(); env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); DirContext dirContext = new InitialDirContext(env); InternetDomainName domain = domainName.parent(); // we can't use InternetDomainName#child as leading underscore is not allowed by domain names String idmapDomainRecord = NFS4_DNS_TXT_REC + "." +domain.toString(); try { Attributes attrs = dirContext.getAttributes(idmapDomainRecord, new String[]{"TXT"}); Attribute txtAttr = attrs.get("TXT"); if (txtAttr != null) { NamingEnumeration e = txtAttr.getAll(); String txtRecord = e.next().toString(); LOGGER.info("Using nfs4domain from DNS TXT record: {}", txtRecord); return txtRecord; } } catch (CommunicationException e) { LOGGER.warn("DNS query to discover NFS domain name failed: {}", Throwables.getRootCause(e).getMessage()); } catch (NameNotFoundException e) { // nfsv4idmapdomain record doesn't exists } // The DNS hasn't corresponding TXT record. Use domain name. LOGGER.info("Using DNS domain as nfs4domain: {}", domain); return domain.toString(); } }