View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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   * Override the native SPNegoScheme to handle Kerberos.
38   * Try to get Kerberos ticket from the session, if this fails, use callbacks to get credentials interactively.
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          // Load Jaas configuration from class
48          Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
49          // Kerberos callback handler singleton
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              // check cached TGT
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              // create client login context
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              // try to renew almost expired tickets
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 }