/* * Copyright 1999-2010 University of Chicago * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ package org.globus.gsi.gssapi; import java.net.InetAddress; import java.net.UnknownHostException; import org.globus.common.CoGProperties; import org.globus.gsi.util.CertificateUtil; import org.ietf.jgss.GSSName; import org.ietf.jgss.GSSException; import org.ietf.jgss.Oid; import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.Serializable; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.*; import java.util.regex.Pattern; /** * An implementation of <code>GSSName</code>. */ public class GlobusGSSName implements GSSName, Serializable { static class ReverseDNSCache { static class MapEntry { final Future<String> hostName; Long inserted; public MapEntry(Future<String> hostName, Long inserted) { this.hostName = hostName; this.inserted = inserted; } } // Use TreeMap to avoid clustering in any case final protected Map<String, MapEntry> cache = new TreeMap<String, MapEntry>(); final long duration; final ExecutorService threads = Executors.newCachedThreadPool(new ThreadFactory() { public Thread newThread(Runnable runnable) { Thread t = new Thread(runnable); t.setName("Reverse DNS request"); t.setDaemon(true); return t; } }); long oldest = System.currentTimeMillis(); public ReverseDNSCache(long duration) { this.duration = duration; } protected void enforceConstraints() { if(oldest + duration < System.currentTimeMillis()) { long newOldest = System.currentTimeMillis(); List<String> toClear = new LinkedList<String>(); for(Map.Entry<String, MapEntry> e: cache.entrySet()) { if(e.getValue().inserted + duration < System.currentTimeMillis()) toClear.add(e.getKey()); else if(e.getValue().inserted < newOldest) newOldest = e.getValue().inserted; } for(String k: toClear) cache.remove(k); oldest = newOldest; } } protected synchronized Future<String> getCached(final String ip) { MapEntry inCache = cache.get(ip); if(inCache == null) { Future<String> name = threads.submit(new Callable<String>() { public String call() throws Exception { return queryHost(ip); } }); inCache = new MapEntry(name, System.currentTimeMillis()); cache.put(ip, inCache); } else { inCache.inserted = System.currentTimeMillis(); } enforceConstraints(); return inCache.hostName; } public String resolve(String ip) throws UnknownHostException { try { return getCached(ip).get(); } catch(InterruptedException e) { throw new UnknownHostException(e.getMessage()); } catch(ExecutionException e) { throw new UnknownHostException(e.getMessage()); } } } static String queryHost(String name) throws UnknownHostException { InetAddress i = InetAddress.getByName(name); return InetAddress.getByName(i.getHostAddress()).getHostName(); } final static ReverseDNSCache reverseDNSCache = new ReverseDNSCache(CoGProperties.getDefault().getReveseDNSCacheLifetime()); protected Oid nameType; protected X500Principal name; // set toString called protected String globusID; // set when constructing with GSSName.NT_HOSTBASED_SERVICE as name type // or in the getter protected String hostBasedServiceCN; public GlobusGSSName() { this.nameType = GSSName.NT_ANONYMOUS; this.name = null; } public GlobusGSSName(X500Principal name) { if (name == null) { this.nameType = GSSName.NT_ANONYMOUS; } this.name = name; } public GlobusGSSName(byte[] name) { if (name == null) { this.nameType = GSSName.NT_ANONYMOUS; this.name = null; } else { this.name = new X500Principal(name); } } /** * Creates name from Globus DN * * @param name Globus DN (e.g. /C=US/O=Globus/..) If null * it is considered set as <code>GSSName.ANONYMOUS</code> name type. */ public GlobusGSSName(String name) throws GSSException { if (name == null) { this.nameType = GSSName.NT_ANONYMOUS; this.name = null; } else { try { this.name = CertificateUtil.toPrincipal(name); } catch (Exception e) { throw new GlobusGSSException(GSSException.BAD_NAME, e); } } } /** * Creates name from X509 name of specified type. * * @param name * Globus DN (e.g. /C=US/O=Globus/..) or service@host name. If null * it is considered set as <code>GSSName.ANONYMOUS</code> name type. * @param nameType name type. Only <code>GSSName.NT_ANONYMOUS</code> * or <code>GSSName.NT_HOSTBASED_SERVICE</code> is supported. * Maybe be null. */ public GlobusGSSName(String name, Oid nameType) throws GSSException { if (name == null) { if (nameType != null && !nameType.equals(GSSName.NT_ANONYMOUS)) { throw new GSSException(GSSException.BAD_NAMETYPE); } this.name = null; this.nameType = GSSName.NT_ANONYMOUS; } else { if (nameType != null) { if (nameType.equals(GSSName.NT_HOSTBASED_SERVICE)) { int atPos = name.indexOf('@'); if (atPos == -1 || (atPos+1 >= name.length())) { throw new GlobusGSSException(GSSException.FAILURE, GlobusGSSException.BAD_NAME, "badName00"); } // performs reverse DNS lookup String host = name.substring(atPos+1); try { if (CoGProperties.getDefault().getReverseDNSCacheType().equals(CoGProperties.THREADED_CACHE)) { host = reverseDNSCache.resolve(host); } else { host = queryHost(host); } } catch (UnknownHostException e) { throw new GlobusGSSException(GSSException.FAILURE, e); } hostBasedServiceCN = name.substring(0, atPos) + "/" + host; this.name = new X500Principal("CN=" + hostBasedServiceCN); } else { throw new GSSException(GSSException.BAD_NAMETYPE); } } else { try { this.name = CertificateUtil.toPrincipal(name); } catch (Exception e) { throw new GlobusGSSException(GSSException.BAD_NAME, e); } } this.nameType = nameType; } // both subject & nameType might be null } public boolean isAnonymous() { return (this.name == null); } public boolean isMN() { return true; } public boolean equals(GSSName another) throws GSSException { if (another == null) { return false; } if (isAnonymous()) { return another.isAnonymous(); } if (another.isAnonymous()) { return false; } if (!(another instanceof GlobusGSSName)) { throw new GSSException(GSSException.FAILURE); } GlobusGSSName other = (GlobusGSSName)another; // both are not anonymous // both have non-null subjects // nametypes might be different! (null) if ((nameType != null && nameType.equals(GSSName.NT_HOSTBASED_SERVICE)) || (other.nameType != null && other.nameType.equals(GSSName.NT_HOSTBASED_SERVICE))) { // perform host based comparison String hp1 = this.getHostBasedServiceCN(true); String hp2 = other.getHostBasedServiceCN(true); if (hp1 == null || hp2 == null) { // something is really wrong return false; } String service1 = getService(hp1); String service2 = getService(hp2); // service types do not match if (!service1.equalsIgnoreCase(service2)) { return false; } String host1 = getHost(hp1); String host2 = getHost(hp2); int i1=0; int i2=0; int s1 = host1.length(); int s2 = host2.length(); char h1; char h2; while (i1 < s1 && i2 < s2) { h1 = Character.toUpperCase(host1.charAt(i1)); h2 = Character.toUpperCase(host2.charAt(i2)); if (h1 == h2) { if (h1 == '.') { return host1.equalsIgnoreCase(host2); } i1++; i2++; } else if (h1 == '.' && h2 == '-') { return compareHost(host2, i2, host1, i1); } else if (h1 == '-' && h2 == '.') { return compareHost(host1, i1, host2, i2); } else { return false; } } return (i1 == i2); } else { // perform regular comparison // cross-check getStringNameType() // that's not implemented right now return toString().equalsIgnoreCase(another.toString()); } } /** * Returns globus ID string representation of the name. * If name represents is an anonymous name string * "<anonymous>" is returned. */ public String toString() { if (this.name == null) { return "<anonymous>"; } else { if (this.globusID == null) { this.globusID = CertificateUtil.toGlobusID(name); } return this.globusID; } } /** * Returns the CN corresponding to the host part of the DN * @param last true if the CN is assumed to be the last CN attribute * in the RFC 2253 formatted DN, else false to assume it is the first DN * attribute * @return the CN of the host based service */ protected String getHostBasedServiceCN(boolean last) { if (hostBasedServiceCN == null) { String dn = name.getName(); int cnStart; if (last) { // use the last instance of CN in the DN cnStart = dn.lastIndexOf("CN=") + 3; } else { // use the first instance of CN in the DN cnStart = dn.indexOf("CN=") + 3; } if (cnStart == -1) { return null; } int cnEnd = dn.indexOf(",", cnStart); if (cnEnd == -1) { int nextAtt = dn.indexOf("=", cnStart); if (nextAtt == -1) { // CN is the last attribute in the DN cnEnd = dn.length(); } else { // unexpected DN format (attributes not comma delimited) return null; } } hostBasedServiceCN = name.getName().substring(cnStart, cnEnd); } return hostBasedServiceCN; } private static String getService(String name) { int pos = name.indexOf('/'); return (pos == -1) ? "host" : name.substring(0, pos); } private static String getHost(String name) { int pos = name.indexOf('/'); return (pos == -1) ? name : name.substring(pos+1); } private static boolean compareHost(String host1, int i, String host2, int j) { if (host1.charAt(i) != '-') { throw new IllegalArgumentException(); } int size = host1.length(); while (i < size ) { if (host1.charAt(i) == '.') { break; } else { i++; } } if (size - i == host2.length() - j) { return host1.regionMatches(i, host2, j, size - i); } else { return false; } } // ---------------------------------- /** * Currently not implemented. */ public Oid getStringNameType() throws GSSException { throw new GSSException(GSSException.UNAVAILABLE); } /** * Currently not implemented. */ public byte[] export() throws GSSException { throw new GSSException(GSSException.UNAVAILABLE); } /** * Currently not implemented. */ public GSSName canonicalize(Oid mech) throws GSSException { throw new GSSException(GSSException.UNAVAILABLE); } private void writeObject(ObjectOutputStream oos) throws IOException { oos.writeObject(this.nameType); oos.writeObject(name.getName()); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { this.nameType = (Oid)ois.readObject(); this.name = new X500Principal((String)ois.readObject()); } }