1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.http;
21
22 import org.apache.http.auth.Credentials;
23 import org.apache.http.impl.auth.SPNegoScheme;
24 import org.apache.log4j.Logger;
25 import org.ietf.jgss.GSSException;
26 import org.ietf.jgss.Oid;
27
28 import javax.security.auth.RefreshFailedException;
29 import javax.security.auth.Subject;
30 import javax.security.auth.kerberos.KerberosTicket;
31 import javax.security.auth.login.LoginContext;
32 import javax.security.auth.login.LoginException;
33 import java.security.PrivilegedAction;
34 import java.security.Security;
35
36
37
38
39
40 public class DavMailSPNegoScheme extends SPNegoScheme {
41 protected static final Logger LOGGER = Logger.getLogger(DavMailSPNegoScheme.class);
42 protected static final Object LOCK = new Object();
43 protected static final KerberosHelper.KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER;
44 private static LoginContext clientLoginContext;
45
46 static {
47
48 Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
49
50 KERBEROS_CALLBACK_HANDLER = new KerberosHelper.KerberosCallbackHandler();
51 }
52
53 public DavMailSPNegoScheme(final boolean stripPort, final boolean useCanonicalHostname) {
54 super(stripPort, useCanonicalHostname);
55 }
56
57 public DavMailSPNegoScheme(final boolean stripPort) {
58 super(stripPort);
59 }
60
61 public DavMailSPNegoScheme() {
62 super();
63 }
64
65 @Override
66 protected byte[] generateGSSToken(final byte[] input, final Oid oid, final String authServer, final Credentials credentials) throws GSSException {
67 String protocol = "HTTP";
68
69 LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + '@' + authServer + ' ' + input.length + " bytes token");
70
71 synchronized (LOCK) {
72
73 if (clientLoginContext != null) {
74 for (KerberosTicket kerberosTicket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
75 if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
76 LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
77 clientLoginContext = null;
78 }
79 }
80 }
81
82 if (clientLoginContext == null) {
83 final LoginContext localLoginContext;
84 try {
85 localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
86 localLoginContext.login();
87 clientLoginContext = localLoginContext;
88 } catch (LoginException e) {
89 LOGGER.error(e.getMessage(), e);
90 throw new GSSException(GSSException.FAILURE);
91 }
92 }
93
94 for (KerberosTicket kerberosTicket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
95 LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
96 if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
97 if (kerberosTicket.isRenewable()) {
98 try {
99 kerberosTicket.refresh();
100 } catch (RefreshFailedException e) {
101 LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket);
102 }
103 } else {
104 LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
105 }
106 }
107 }
108
109 Object result = internalGenerateGSSToken(input, oid, authServer, credentials);
110
111 if (result instanceof GSSException) {
112 LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
113 throw (GSSException) result;
114 }
115
116 LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
117 return (byte[]) result;
118 }
119 }
120
121 @SuppressWarnings("removal")
122 protected Object internalGenerateGSSToken(final byte[] input, final Oid oid, final String authServer, final Credentials credentials) {
123 return Subject.doAs(clientLoginContext.getSubject(), (PrivilegedAction<Object>) () -> {
124 Object result;
125 try {
126 result = super.generateGSSToken(input, oid, authServer, credentials);
127 } catch (GSSException e) {
128 result = e;
129 }
130 return result;
131 });
132 }
133
134 }