29 * Copyright 1997 The Open Group Research Institute. All rights reserved. |
29 * Copyright 1997 The Open Group Research Institute. All rights reserved. |
30 */ |
30 */ |
31 |
31 |
32 package sun.security.krb5; |
32 package sun.security.krb5; |
33 |
33 |
|
34 import java.security.AccessController; |
|
35 import java.security.PrivilegedAction; |
|
36 import java.security.Security; |
|
37 import java.util.Locale; |
34 import sun.security.krb5.internal.Krb5; |
38 import sun.security.krb5.internal.Krb5; |
35 import sun.security.krb5.internal.UDPClient; |
39 import sun.security.krb5.internal.UDPClient; |
36 import sun.security.krb5.internal.TCPClient; |
40 import sun.security.krb5.internal.TCPClient; |
37 import java.io.IOException; |
41 import java.io.IOException; |
38 import java.io.InterruptedIOException; |
|
39 import java.net.SocketTimeoutException; |
42 import java.net.SocketTimeoutException; |
40 import java.net.UnknownHostException; |
|
41 import java.util.StringTokenizer; |
43 import java.util.StringTokenizer; |
42 import java.security.AccessController; |
44 import java.security.AccessController; |
43 import java.security.PrivilegedExceptionAction; |
45 import java.security.PrivilegedExceptionAction; |
44 import java.security.PrivilegedActionException; |
46 import java.security.PrivilegedActionException; |
|
47 import java.util.ArrayList; |
|
48 import java.util.List; |
|
49 import java.util.Set; |
|
50 import java.util.HashSet; |
45 |
51 |
46 public abstract class KrbKdcReq { |
52 public abstract class KrbKdcReq { |
47 |
|
48 /** |
|
49 * Default port for a KDC. |
|
50 */ |
|
51 private static final int DEFAULT_KDC_PORT = Krb5.KDC_INET_DEFAULT_PORT; |
|
52 |
53 |
53 // Currently there is no option to specify retries |
54 // Currently there is no option to specify retries |
54 // in the kerberos configuration file |
55 // in the kerberos configuration file |
55 |
56 |
56 private static final int DEFAULT_KDC_RETRY_LIMIT = Krb5.KDC_RETRY_LIMIT; |
57 private static final int DEFAULT_KDC_RETRY_LIMIT = Krb5.KDC_RETRY_LIMIT; |
64 |
65 |
65 private static final boolean DEBUG = Krb5.DEBUG; |
66 private static final boolean DEBUG = Krb5.DEBUG; |
66 |
67 |
67 private static int udpPrefLimit = -1; |
68 private static int udpPrefLimit = -1; |
68 |
69 |
|
70 private static final String BAD_POLICY_KEY = "krb5.kdc.bad.policy"; |
|
71 |
|
72 /** |
|
73 * What to do when a KDC is unavailable, specified in the |
|
74 * java.security file with key krb5.kdc.bad.policy. |
|
75 * Possible values can be TRY_LAST or TRY_LESS |
|
76 */ |
|
77 private enum BpType { |
|
78 NONE, TRY_LAST, TRY_LESS |
|
79 } |
|
80 private static int tryLessMaxRetries = 1; |
|
81 private static int tryLessTimeout = 5000; |
|
82 |
|
83 private static final BpType badPolicy; |
|
84 |
69 static { |
85 static { |
|
86 String value = AccessController.doPrivileged( |
|
87 new PrivilegedAction<String>() { |
|
88 public String run() { |
|
89 return Security.getProperty(BAD_POLICY_KEY); |
|
90 } |
|
91 }); |
|
92 if (value != null) { |
|
93 value = value.toLowerCase(Locale.ENGLISH); |
|
94 String[] ss = value.split(":"); |
|
95 if ("tryless".equals(ss[0])) { |
|
96 if (ss.length > 1) { |
|
97 String[] params = ss[1].split(","); |
|
98 tryLessMaxRetries = Integer.parseInt(params[0]); |
|
99 if (params.length > 1) { |
|
100 tryLessTimeout = Integer.parseInt(params[1]); |
|
101 } |
|
102 } |
|
103 badPolicy = BpType.TRY_LESS; |
|
104 } else if ("trylast".equals(ss[0])) { |
|
105 badPolicy = BpType.TRY_LAST; |
|
106 } else { |
|
107 badPolicy = BpType.NONE; |
|
108 } |
|
109 } else { |
|
110 badPolicy = BpType.NONE; |
|
111 } |
70 |
112 |
71 /* |
113 /* |
72 * Get default timeout. |
114 * Get default timeout. |
73 */ |
115 */ |
74 |
116 |
129 throw new KrbException(Krb5.KRB_ERR_GENERIC, |
171 throw new KrbException(Krb5.KRB_ERR_GENERIC, |
130 "Cannot find default realm"); |
172 "Cannot find default realm"); |
131 } |
173 } |
132 } |
174 } |
133 |
175 |
134 /* |
|
135 * Get timeout. |
|
136 */ |
|
137 |
|
138 int timeout = getKdcTimeout(realm); |
|
139 |
|
140 String kdcList = cfg.getKDCList(realm); |
176 String kdcList = cfg.getKDCList(realm); |
141 if (kdcList == null) { |
177 if (kdcList == null) { |
142 throw new KrbException("Cannot get kdc for realm " + realm); |
178 throw new KrbException("Cannot get kdc for realm " + realm); |
143 } |
179 } |
144 String tempKdc = null; // may include the port number also |
180 String tempKdc = null; // may include the port number also |
145 StringTokenizer st = new StringTokenizer(kdcList); |
181 for (String tmp: KdcAccessibility.list(kdcList)) { |
146 while (st.hasMoreTokens()) { |
182 tempKdc = tmp; |
147 tempKdc = st.nextToken(); |
|
148 try { |
183 try { |
149 send(realm,tempKdc,useTCP); |
184 send(realm,tempKdc,useTCP); |
|
185 KdcAccessibility.removeBad(tempKdc); |
150 break; |
186 break; |
151 } catch (Exception e) { |
187 } catch (Exception e) { |
152 if (DEBUG) { |
188 if (DEBUG) { |
153 System.out.println(">>> KrbKdcReq send: error trying " + |
189 System.out.println(">>> KrbKdcReq send: error trying " + |
154 tempKdc); |
190 tempKdc); |
155 e.printStackTrace(System.out); |
191 e.printStackTrace(System.out); |
156 } |
192 } |
|
193 KdcAccessibility.addBad(tempKdc); |
157 savedException = e; |
194 savedException = e; |
158 } |
195 } |
159 } |
196 } |
160 if (ibuf == null && savedException != null) { |
197 if (ibuf == null && savedException != null) { |
161 if (savedException instanceof IOException) { |
198 if (savedException instanceof IOException) { |
172 public void send(String realm, String tempKdc, boolean useTCP) |
209 public void send(String realm, String tempKdc, boolean useTCP) |
173 throws IOException, KrbException { |
210 throws IOException, KrbException { |
174 |
211 |
175 if (obuf == null) |
212 if (obuf == null) |
176 return; |
213 return; |
177 PrivilegedActionException savedException = null; |
214 |
178 int port = Krb5.KDC_INET_DEFAULT_PORT; |
215 int port = Krb5.KDC_INET_DEFAULT_PORT; |
179 |
216 int retries = DEFAULT_KDC_RETRY_LIMIT; |
180 /* |
|
181 * Get timeout. |
|
182 */ |
|
183 int timeout = getKdcTimeout(realm); |
217 int timeout = getKdcTimeout(realm); |
184 /* |
218 |
185 * Get port number for this KDC. |
219 if (badPolicy == BpType.TRY_LESS && |
186 */ |
220 KdcAccessibility.isBad(tempKdc)) { |
|
221 if (retries > tryLessMaxRetries) { |
|
222 retries = tryLessMaxRetries; // less retries |
|
223 } |
|
224 if (timeout > tryLessTimeout) { |
|
225 timeout = tryLessTimeout; // less time |
|
226 } |
|
227 } |
|
228 |
187 String kdc = null; |
229 String kdc = null; |
188 String portStr = null; |
230 String portStr = null; |
189 |
231 |
190 if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in [] |
232 if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in [] |
191 int pos = tempKdc.indexOf(']', 1); |
233 int pos = tempKdc.indexOf(']', 1); |
223 System.out.println(">>> KrbKdcReq send: kdc=" + kdc |
265 System.out.println(">>> KrbKdcReq send: kdc=" + kdc |
224 + (useTCP ? " TCP:":" UDP:") |
266 + (useTCP ? " TCP:":" UDP:") |
225 + port + ", timeout=" |
267 + port + ", timeout=" |
226 + timeout |
268 + timeout |
227 + ", number of retries =" |
269 + ", number of retries =" |
228 + DEFAULT_KDC_RETRY_LIMIT |
270 + retries |
229 + ", #bytes=" + obuf.length); |
271 + ", #bytes=" + obuf.length); |
230 } |
272 } |
231 |
273 |
232 KdcCommunication kdcCommunication = |
274 KdcCommunication kdcCommunication = |
233 new KdcCommunication(kdc, port, useTCP, timeout, obuf); |
275 new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf); |
234 try { |
276 try { |
235 ibuf = AccessController.doPrivileged(kdcCommunication); |
277 ibuf = AccessController.doPrivileged(kdcCommunication); |
236 if (DEBUG) { |
278 if (DEBUG) { |
237 System.out.println(">>> KrbKdcReq send: #bytes read=" |
279 System.out.println(">>> KrbKdcReq send: #bytes read=" |
238 + (ibuf != null ? ibuf.length : 0)); |
280 + (ibuf != null ? ibuf.length : 0)); |
256 |
298 |
257 private String kdc; |
299 private String kdc; |
258 private int port; |
300 private int port; |
259 private boolean useTCP; |
301 private boolean useTCP; |
260 private int timeout; |
302 private int timeout; |
|
303 private int retries; |
261 private byte[] obuf; |
304 private byte[] obuf; |
262 |
305 |
263 public KdcCommunication(String kdc, int port, boolean useTCP, |
306 public KdcCommunication(String kdc, int port, boolean useTCP, |
264 int timeout, byte[] obuf) { |
307 int timeout, int retries, byte[] obuf) { |
265 this.kdc = kdc; |
308 this.kdc = kdc; |
266 this.port = port; |
309 this.port = port; |
267 this.useTCP = useTCP; |
310 this.useTCP = useTCP; |
268 this.timeout = timeout; |
311 this.timeout = timeout; |
|
312 this.retries = retries; |
269 this.obuf = obuf; |
313 this.obuf = obuf; |
270 } |
314 } |
271 |
315 |
272 // The caller only casts IOException and KrbException so don't |
316 // The caller only casts IOException and KrbException so don't |
273 // add any new ones! |
317 // add any new ones! |
292 } |
336 } |
293 |
337 |
294 } else { |
338 } else { |
295 // For each KDC we try DEFAULT_KDC_RETRY_LIMIT (3) times to |
339 // For each KDC we try DEFAULT_KDC_RETRY_LIMIT (3) times to |
296 // get the response |
340 // get the response |
297 for (int i=1; i <= DEFAULT_KDC_RETRY_LIMIT; i++) { |
341 for (int i=1; i <= retries; i++) { |
298 UDPClient kdcClient = new UDPClient(kdc, port, timeout); |
342 UDPClient kdcClient = new UDPClient(kdc, port, timeout); |
299 |
343 |
300 if (DEBUG) { |
344 if (DEBUG) { |
301 System.out.println(">>> KDCCommunication: kdc=" + kdc |
345 System.out.println(">>> KDCCommunication: kdc=" + kdc |
302 + (useTCP ? " TCP:":" UDP:") |
346 + (useTCP ? " TCP:":" UDP:") |
383 if (ret >= 0) |
427 if (ret >= 0) |
384 return ret; |
428 return ret; |
385 |
429 |
386 return -1; |
430 return -1; |
387 } |
431 } |
|
432 |
|
433 /** |
|
434 * Maintains a KDC accessible list. Unavailable KDCs are put into a |
|
435 * blacklist, when a KDC in the blacklist is available, it's removed |
|
436 * from there. No insertion order in the blacklist. |
|
437 * |
|
438 * There are two methods to deal with KDCs in the blacklist. 1. Only try |
|
439 * them when there's no KDC not on the blacklist. 2. Still try them, but |
|
440 * with lesser number of retries and smaller timeout value. |
|
441 */ |
|
442 static class KdcAccessibility { |
|
443 // Known bad KDCs |
|
444 private static Set<String> bads = new HashSet<String>(); |
|
445 |
|
446 private static synchronized void addBad(String kdc) { |
|
447 if (DEBUG) { |
|
448 System.out.println(">>> KdcAccessibility: add " + kdc); |
|
449 } |
|
450 bads.add(kdc); |
|
451 } |
|
452 |
|
453 private static synchronized void removeBad(String kdc) { |
|
454 if (DEBUG) { |
|
455 System.out.println(">>> KdcAccessibility: remove " + kdc); |
|
456 } |
|
457 bads.remove(kdc); |
|
458 } |
|
459 |
|
460 private static synchronized boolean isBad(String kdc) { |
|
461 return bads.contains(kdc); |
|
462 } |
|
463 |
|
464 public static synchronized void reset() { |
|
465 if (DEBUG) { |
|
466 System.out.println(">>> KdcAccessibility: reset"); |
|
467 } |
|
468 bads.clear(); |
|
469 } |
|
470 |
|
471 // Returns a preferred KDC list by putting the bad ones at the end |
|
472 private static synchronized String[] list(String kdcList) { |
|
473 StringTokenizer st = new StringTokenizer(kdcList); |
|
474 List<String> list = new ArrayList<String>(); |
|
475 if (badPolicy == BpType.TRY_LAST) { |
|
476 List<String> badkdcs = new ArrayList<String>(); |
|
477 while (st.hasMoreTokens()) { |
|
478 String t = st.nextToken(); |
|
479 if (bads.contains(t)) badkdcs.add(t); |
|
480 else list.add(t); |
|
481 } |
|
482 // Bad KDCs are put at last |
|
483 list.addAll(badkdcs); |
|
484 } else { |
|
485 // All KDCs are returned in their original order, |
|
486 // This include TRY_LESS and NONE |
|
487 while (st.hasMoreTokens()) { |
|
488 list.add(st.nextToken()); |
|
489 } |
|
490 } |
|
491 return list.toArray(new String[list.size()]); |
|
492 } |
|
493 } |
388 } |
494 } |
|
495 |