1 /*
   2  * Copyright (c) 2019, Red Hat, Inc.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.krb5.internal;
  27 
  28 import java.util.Date;
  29 import java.util.HashMap;
  30 import java.util.LinkedList;
  31 import java.util.List;
  32 import java.util.Map;
  33 import java.util.Map.Entry;
  34 
  35 import sun.security.krb5.Credentials;
  36 import sun.security.krb5.PrincipalName;
  37 
  38 /*
  39  * ReferralsCache class implements a cache scheme for referral TGTs as
  40  * described in RFC 6806 - 10. Caching Information. The goal is to optimize
  41  * resources (such as network traffic) when a client requests credentials for a
  42  * service principal to a given KDC. If a referral TGT was previously received,
  43  * cached information is used instead of issuing a new query. Once a referral
  44  * TGT expires, the corresponding referral entry in the cache is removed.
  45  */
  46 final class ReferralsCache {
  47 
  48     private static Map<PrincipalName, Map<String, ReferralCacheEntry>> referralsMap =
  49             new HashMap<>();
  50 
  51     static final class ReferralCacheEntry {
  52         private final Credentials creds;
  53         private final String toRealm;
  54         ReferralCacheEntry(Credentials creds, String toRealm) {
  55             this.creds = creds;
  56             this.toRealm = toRealm;
  57         }
  58         Credentials getCreds() {
  59             return creds;
  60         }
  61         String getToRealm() {
  62             return toRealm;
  63         }
  64     }
  65 
  66     /*
  67      * Add a new referral entry to the cache, including: service principal,
  68      * source KDC realm, destination KDC realm and referral TGT.
  69      *
  70      * If a loop is generated when adding the new referral, the first hop is
  71      * automatically removed. For example, let's assume that adding a
  72      * REALM-3.COM -> REALM-1.COM referral generates the following loop:
  73      * REALM-1.COM -> REALM-2.COM -> REALM-3.COM -> REALM-1.COM. Then,
  74      * REALM-1.COM -> REALM-2.COM referral entry is removed from the cache.
  75      */
  76     static synchronized void put(PrincipalName service,
  77             String fromRealm, String toRealm, Credentials creds) {
  78         pruneExpired(service);
  79         if (creds.getEndTime().before(new Date())) {
  80             return;
  81         }
  82         Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
  83         if (entries == null) {
  84             entries = new HashMap<String, ReferralCacheEntry>();
  85             referralsMap.put(service, entries);
  86         }
  87         entries.remove(fromRealm);
  88         ReferralCacheEntry newEntry = new ReferralCacheEntry(creds, toRealm);
  89         entries.put(fromRealm, newEntry);
  90 
  91         // Remove loops within the cache
  92         ReferralCacheEntry current = newEntry;
  93         List<ReferralCacheEntry> seen = new LinkedList<>();
  94         while (current != null) {
  95             if (seen.contains(current)) {
  96                 // Loop found. Remove the first referral to cut the loop.
  97                 entries.remove(newEntry.getToRealm());
  98                 break;
  99             }
 100             seen.add(current);
 101             current = entries.get(current.getToRealm());
 102         }
 103     }
 104 
 105     /*
 106      * Obtain a referral entry from the cache given a service principal and a
 107      * source KDC realm.
 108      */
 109     static synchronized ReferralCacheEntry get(PrincipalName service,
 110             String fromRealm) {
 111         pruneExpired(service);
 112         Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
 113         if (entries != null) {
 114             ReferralCacheEntry toRef = entries.get(fromRealm);
 115             if (toRef != null) {
 116                 return toRef;
 117             }
 118         }
 119         return null;
 120     }
 121 
 122     /*
 123      * Remove referral entries from the cache when referral TGTs expire.
 124      */
 125     private static void pruneExpired(PrincipalName service) {
 126         Date now = new Date();
 127         Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
 128         if (entries != null) {
 129             for (Entry<String, ReferralCacheEntry> mapEntry :
 130                     entries.entrySet()) {
 131                 if (mapEntry.getValue().getCreds().getEndTime().before(now)) {
 132                     entries.remove(mapEntry.getKey());
 133                 }
 134             }
 135         }
 136     }
 137 }