mirror of
https://github.com/moparisthebest/davmail
synced 2025-01-09 12:48:02 -05:00
Kerberos: synchronize access to clientLoginContext
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@2275 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
d4efba3599
commit
31377bfb02
@ -29,7 +29,7 @@ import javax.security.auth.callback.*;
|
|||||||
import javax.security.auth.kerberos.KerberosTicket;
|
import javax.security.auth.kerberos.KerberosTicket;
|
||||||
import javax.security.auth.login.LoginContext;
|
import javax.security.auth.login.LoginContext;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import java.awt.GraphicsEnvironment;
|
import java.awt.*;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@ -44,7 +44,7 @@ public class KerberosHelper {
|
|||||||
protected static final Logger LOGGER = Logger.getLogger(KerberosHelper.class);
|
protected static final Logger LOGGER = Logger.getLogger(KerberosHelper.class);
|
||||||
protected static final Object LOCK = new Object();
|
protected static final Object LOCK = new Object();
|
||||||
protected static final KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER;
|
protected static final KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER;
|
||||||
protected static LoginContext clientLoginContext;
|
private static LoginContext clientLoginContext;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Load Jaas configuration from class
|
// Load Jaas configuration from class
|
||||||
@ -53,25 +53,26 @@ public class KerberosHelper {
|
|||||||
KERBEROS_CALLBACK_HANDLER = new KerberosCallbackHandler();
|
KERBEROS_CALLBACK_HANDLER = new KerberosCallbackHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KerberosHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||||
protected static class KerberosCallbackHandler implements CallbackHandler {
|
protected static class KerberosCallbackHandler implements CallbackHandler {
|
||||||
String principal;
|
String principal;
|
||||||
String password;
|
String password;
|
||||||
|
|
||||||
protected KerberosCallbackHandler() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
for (int i = 0; i < callbacks.length; i++) {
|
for (Callback callback : callbacks) {
|
||||||
if (callbacks[i] instanceof NameCallback) {
|
if (callback instanceof NameCallback) {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
// if we get there kerberos token is missing or invalid
|
// if we get there kerberos token is missing or invalid
|
||||||
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
|
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
|
||||||
// headless or server mode
|
// headless or server mode
|
||||||
System.out.print(((NameCallback) callbacks[i]).getPrompt());
|
System.out.print(((NameCallback) callback).getPrompt());
|
||||||
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
principal = inReader.readLine();
|
principal = inReader.readLine();
|
||||||
} else {
|
} else {
|
||||||
CredentialPromptDialog credentialPromptDialog = new CredentialPromptDialog(((NameCallback) callbacks[i]).getPrompt());
|
CredentialPromptDialog credentialPromptDialog = new CredentialPromptDialog(((NameCallback) callback).getPrompt());
|
||||||
principal = credentialPromptDialog.getPrincipal();
|
principal = credentialPromptDialog.getPrincipal();
|
||||||
password = String.valueOf(credentialPromptDialog.getPassword());
|
password = String.valueOf(credentialPromptDialog.getPassword());
|
||||||
}
|
}
|
||||||
@ -79,14 +80,14 @@ public class KerberosHelper {
|
|||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
throw new IOException("KerberosCallbackHandler: failed to retrieve principal");
|
throw new IOException("KerberosCallbackHandler: failed to retrieve principal");
|
||||||
}
|
}
|
||||||
((NameCallback) callbacks[i]).setName(principal);
|
((NameCallback) callback).setName(principal);
|
||||||
|
|
||||||
} else if (callbacks[i] instanceof PasswordCallback) {
|
} else if (callback instanceof PasswordCallback) {
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
// if we get there kerberos token is missing or invalid
|
// if we get there kerberos token is missing or invalid
|
||||||
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
|
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
|
||||||
// headless or server mode
|
// headless or server mode
|
||||||
System.out.print(((PasswordCallback) callbacks[i]).getPrompt());
|
System.out.print(((PasswordCallback) callback).getPrompt());
|
||||||
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
password = inReader.readLine();
|
password = inReader.readLine();
|
||||||
}
|
}
|
||||||
@ -94,20 +95,30 @@ public class KerberosHelper {
|
|||||||
if (password == null) {
|
if (password == null) {
|
||||||
throw new IOException("KerberosCallbackHandler: failed to retrieve password");
|
throw new IOException("KerberosCallbackHandler: failed to retrieve password");
|
||||||
}
|
}
|
||||||
((PasswordCallback) callbacks[i]).setPassword(password.toCharArray());
|
((PasswordCallback) callback).setPassword(password.toCharArray());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedCallbackException(callbacks[i]);
|
throw new UnsupportedCallbackException(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setPrincipal(String principal) {
|
/**
|
||||||
|
* Force client principal in callback handler
|
||||||
|
*
|
||||||
|
* @param principal client principal
|
||||||
|
*/
|
||||||
|
public static void setClientPrincipal(String principal) {
|
||||||
KERBEROS_CALLBACK_HANDLER.principal = principal;
|
KERBEROS_CALLBACK_HANDLER.principal = principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setPassword(String password) {
|
/**
|
||||||
|
* Force client password in callback handler
|
||||||
|
*
|
||||||
|
* @param password client password
|
||||||
|
*/
|
||||||
|
public static void setClientPassword(String password) {
|
||||||
KERBEROS_CALLBACK_HANDLER.password = password;
|
KERBEROS_CALLBACK_HANDLER.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,17 +152,19 @@ public class KerberosHelper {
|
|||||||
public static byte[] initSecurityContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) throws GSSException, LoginException {
|
public static byte[] initSecurityContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) throws GSSException, LoginException {
|
||||||
LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + "/" + host + " " + token.length + " bytes token");
|
LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + "/" + host + " " + token.length + " bytes token");
|
||||||
|
|
||||||
// create client login context
|
synchronized (LOCK) {
|
||||||
clientLogin();
|
// create client login context
|
||||||
|
clientLogin();
|
||||||
|
|
||||||
Object result = internalInitSecContext(protocol, host, delegatedCredentials, token);
|
Object result = internalInitSecContext(protocol, host, delegatedCredentials, token);
|
||||||
if (result instanceof GSSException) {
|
if (result instanceof GSSException) {
|
||||||
LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((GSSException) result).getMessage());
|
LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
|
||||||
throw (GSSException) result;
|
throw (GSSException) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
|
||||||
|
return (byte[]) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
|
|
||||||
return (byte[]) result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) {
|
protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) {
|
||||||
@ -180,7 +193,7 @@ public class KerberosHelper {
|
|||||||
try {
|
try {
|
||||||
context.dispose();
|
context.dispose();
|
||||||
} catch (GSSException e) {
|
} catch (GSSException e) {
|
||||||
LOGGER.debug("KerberosHelper.internalInitSecContext " + e + " " + e.getMessage());
|
LOGGER.debug("KerberosHelper.internalInitSecContext " + e + ' ' + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,37 +207,35 @@ public class KerberosHelper {
|
|||||||
*
|
*
|
||||||
* @throws LoginException on error
|
* @throws LoginException on error
|
||||||
*/
|
*/
|
||||||
public static void clientLogin() throws LoginException {
|
protected static void clientLogin() throws LoginException {
|
||||||
synchronized (LOCK) {
|
if (clientLoginContext != null) {
|
||||||
if (clientLoginContext != null) {
|
// check cached TGT
|
||||||
// check cached TGT
|
|
||||||
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
|
|
||||||
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
|
|
||||||
if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
|
|
||||||
LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
|
|
||||||
clientLoginContext = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (clientLoginContext == null) {
|
|
||||||
final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
|
|
||||||
localLoginContext.login();
|
|
||||||
clientLoginContext = localLoginContext;
|
|
||||||
}
|
|
||||||
// try to renew almost expired tickets
|
|
||||||
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
|
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
|
||||||
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
|
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
|
||||||
LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
|
if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
|
||||||
if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
|
LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
|
||||||
if (kerberosTicket.isRenewable()) {
|
clientLoginContext = null;
|
||||||
try {
|
}
|
||||||
kerberosTicket.refresh();
|
}
|
||||||
} catch (RefreshFailedException e) {
|
}
|
||||||
LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket.toString());
|
if (clientLoginContext == null) {
|
||||||
}
|
final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
|
||||||
} else {
|
localLoginContext.login();
|
||||||
LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
|
clientLoginContext = localLoginContext;
|
||||||
|
}
|
||||||
|
// try to renew almost expired tickets
|
||||||
|
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
|
||||||
|
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
|
||||||
|
LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
|
||||||
|
if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
|
||||||
|
if (kerberosTicket.isRenewable()) {
|
||||||
|
try {
|
||||||
|
kerberosTicket.refresh();
|
||||||
|
} catch (RefreshFailedException e) {
|
||||||
|
LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket.toString());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,24 +244,24 @@ public class KerberosHelper {
|
|||||||
/**
|
/**
|
||||||
* Create server side Kerberos login context for provided credentials.
|
* Create server side Kerberos login context for provided credentials.
|
||||||
*
|
*
|
||||||
* @param principal server principal
|
* @param serverPrincipal server principal
|
||||||
* @param password server passsword
|
* @param serverPassword server passsword
|
||||||
* @return LoginContext server login context
|
* @return LoginContext server login context
|
||||||
* @throws LoginException on error
|
* @throws LoginException on error
|
||||||
*/
|
*/
|
||||||
public static LoginContext serverLogin(final String principal, final String password) throws LoginException {
|
public static LoginContext serverLogin(final String serverPrincipal, final String serverPassword) throws LoginException {
|
||||||
LoginContext serverLoginContext = new LoginContext("spnego-server", new CallbackHandler() {
|
LoginContext serverLoginContext = new LoginContext("spnego-server", new CallbackHandler() {
|
||||||
|
|
||||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
for (int i = 0; i < callbacks.length; i++) {
|
for (Callback callback : callbacks) {
|
||||||
if (callbacks[i] instanceof NameCallback) {
|
if (callback instanceof NameCallback) {
|
||||||
final NameCallback nameCallback = (NameCallback) callbacks[i];
|
final NameCallback nameCallback = (NameCallback) callback;
|
||||||
nameCallback.setName(principal);
|
nameCallback.setName(serverPrincipal);
|
||||||
} else if (callbacks[i] instanceof PasswordCallback) {
|
} else if (callback instanceof PasswordCallback) {
|
||||||
final PasswordCallback passCallback = (PasswordCallback) callbacks[i];
|
final PasswordCallback passCallback = (PasswordCallback) callback;
|
||||||
passCallback.setPassword(password.toCharArray());
|
passCallback.setPassword(serverPassword.toCharArray());
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedCallbackException(callbacks[i]);
|
throw new UnsupportedCallbackException(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +301,7 @@ public class KerberosHelper {
|
|||||||
Object result = Subject.doAs(serverLoginContext.getSubject(), new PrivilegedAction() {
|
Object result = Subject.doAs(serverLoginContext.getSubject(), new PrivilegedAction() {
|
||||||
|
|
||||||
public Object run() {
|
public Object run() {
|
||||||
Object result;
|
Object innerResult;
|
||||||
SecurityContext securityContext = new SecurityContext();
|
SecurityContext securityContext = new SecurityContext();
|
||||||
GSSContext context = null;
|
GSSContext context = null;
|
||||||
try {
|
try {
|
||||||
@ -315,23 +326,23 @@ public class KerberosHelper {
|
|||||||
securityContext.clientCredential = context.getDelegCred();
|
securityContext.clientCredential = context.getDelegCred();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = securityContext;
|
innerResult = securityContext;
|
||||||
} catch (GSSException e) {
|
} catch (GSSException e) {
|
||||||
result = e;
|
innerResult = e;
|
||||||
} finally {
|
} finally {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
try {
|
try {
|
||||||
context.dispose();
|
context.dispose();
|
||||||
} catch (GSSException e) {
|
} catch (GSSException e) {
|
||||||
LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + " " + e.getMessage());
|
LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + ' ' + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return innerResult;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (result instanceof GSSException) {
|
if (result instanceof GSSException) {
|
||||||
LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((GSSException) result).getMessage());
|
LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
|
||||||
throw (GSSException) result;
|
throw (GSSException) result;
|
||||||
}
|
}
|
||||||
return (SecurityContext) result;
|
return (SecurityContext) result;
|
||||||
|
@ -56,7 +56,10 @@ public class KerberosLoginConfiguration extends Configuration {
|
|||||||
//clientLoginModuleOptions.put("ticketCache", FileCredentialsCache.getDefaultCacheName());
|
//clientLoginModuleOptions.put("ticketCache", FileCredentialsCache.getDefaultCacheName());
|
||||||
//clientLoginModuleOptions.put("refreshKrb5Config", "true");
|
//clientLoginModuleOptions.put("refreshKrb5Config", "true");
|
||||||
//clientLoginModuleOptions.put("storeKey", "true");
|
//clientLoginModuleOptions.put("storeKey", "true");
|
||||||
CLIENT_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, clientLoginModuleOptions)};
|
CLIENT_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry(
|
||||||
|
"com.sun.security.auth.module.Krb5LoginModule",
|
||||||
|
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
|
||||||
|
clientLoginModuleOptions)};
|
||||||
|
|
||||||
HashMap<String, String> serverLoginModuleOptions = new HashMap<String, String>();
|
HashMap<String, String> serverLoginModuleOptions = new HashMap<String, String>();
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
@ -66,7 +69,10 @@ public class KerberosLoginConfiguration extends Configuration {
|
|||||||
serverLoginModuleOptions.put("isInitiator", "false"); // acceptor (server) mode
|
serverLoginModuleOptions.put("isInitiator", "false"); // acceptor (server) mode
|
||||||
serverLoginModuleOptions.put("useKeyTab", "false"); // do not use credentials stored in keytab file
|
serverLoginModuleOptions.put("useKeyTab", "false"); // do not use credentials stored in keytab file
|
||||||
serverLoginModuleOptions.put("storeKey", "true"); // store credentials in subject
|
serverLoginModuleOptions.put("storeKey", "true"); // store credentials in subject
|
||||||
SERVER_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, serverLoginModuleOptions)};
|
SERVER_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry(
|
||||||
|
"com.sun.security.auth.module.Krb5LoginModule",
|
||||||
|
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
|
||||||
|
serverLoginModuleOptions)};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -80,6 +86,7 @@ public class KerberosLoginConfiguration extends Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user