//***************************************************************************
// (c) Copyright IBM Corp. 2007 All rights reserved.
// 
// The following sample of source code ("Sample") is owned by International 
// Business Machines Corporation or one of its subsidiaries ("IBM") and is 
// copyrighted and licensed, not sold. You may use, copy, modify, and 
// distribute the Sample in any form without payment to IBM, for the purpose of 
// assisting you in the development of your applications.
// 
// The Sample code is provided to you on an "AS IS" basis, without warranty of 
// any kind. IBM HEREBY EXPRESSLY DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR 
// IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Some jurisdictions do 
// not allow for the exclusion or limitation of implied warranties, so the above 
// limitations or exclusions may not apply to you. IBM shall not be liable for 
// any damages you suffer as a result of using, copying, modifying or 
// distributing the Sample, even if IBM has been advised of the possibility of 
// such damages.
//***************************************************************************
//
// SOURCE FILE NAME: JCCKerberosPlugin.java
//
// This set of sample shows:
//
// 1. How to implement a JCC plugin which does Kerberos authentication
// 2. How to use this sample plugin to get a Connection.
//
// In order to implement a JCC plugin, user needs to extend com.ibm.db2.jcc.DB2JCCPlugin
// and implement the following method:
// public abstract byte[] getTicket (String username, String password, 
//                                   byte[] returnedToken) throws org.ietf.jgss.GSSException;
//
// Plugin users also need to implement some JGSS APIs. The following is a list of JGSS APIs
// required for Java Security Plugin interface.
//
// GSSContext.requestMutualAuth(boolean state)
// GSSContext.getMutualAuthState()
// GSSContext.requestCredDeleg(boolean state)
// GSSContext.getCredDelegState()
// GSSContext.initSecContext (byte[] inputBuf, int offset, int len)
// GSSContext.dispose()
// GSSCredential.dispose()
//
// The APIs should follow the Generic Security Service Application Program Interface, 
// Version 2 (IETF RFC2743) and Generic Security Service 
// API Version 2: Java-Bindings (IETF RFC2853) specifications.
// For Kerberos, the implementations are already available through the default instance
// of the GSSManager class.
//
// This set of sample implements a plugin that does kerberos authentication.
// It uses Kerberos implementation in jgss package
// It corresponds to the c sample plugin IBMkrb5 in sqllib\samples\securtiy\plugins\
//
// This set of sample contains the following 3 files:
//
// JCCKerberosPluginTest.java
// This file uses sample plugin JCCKerberosPlugin to get a Connection from DB2 server
//
// JCCKerberosPlugin.java
// This file implements the sample JCCKerberosPlugin.
//
// JCCSimpleGSSException.java
// This file is used by JCCKerberosPlugin for Exception handling
//
// How to run this JCCKerberosPlugin sample:
//
//   Compile the above 3 files using: javac *.java
//   Run JCCKerberosPluginTest using
//   java JCCKerberosPluginTest server port dbname userid password serverPrincipalName
//
// Note: To run this sample, server side plugin IBMkrb5 needs to be installed in
//       the server plug-in directory on the  server. Database manager configuration
//       parameters SRVCON_GSS_PLUGIN_LIST and SRVCON_AUTH need to set correctly
//
//               None
//***************************************************************************
//
// For more information on the sample programs, see the README file.
//
// For information on developing Java applications see the Developing Java Applications book.
//
// For information on using SQL statements, see the SQL Reference.
//
// For the latest information on programming, compiling, and running DB2
// applications, visit the DB2 Information Center at
//     http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp
//**************************************************************************/

import org.ietf.jgss.*;

public class JCCKerberosPlugin  extends com.ibm.db2.jcc.DB2JCCPlugin implements java.security.PrivilegedExceptionAction
{
  private org.ietf.jgss.GSSManager manager_ = org.ietf.jgss.GSSManager.getInstance();
  private org.ietf.jgss.GSSName serverGSSName_ ;
  private byte[] ticket = null;
  private byte[] returnedToken_ = null;

  public JCCKerberosPlugin()
  {}

  public JCCKerberosPlugin ( String serverPrincipalName)
  {
    serverPrincipalName_ = serverPrincipalName;
  }


  /**
   * Convert text service principal name into the GSS-API internal format for use with the other APIs
   * @parm  serverPrincipalName String
   * @return  GSSName  server principal name in GSSName format
   * @throws GSSException  if an error occurs.
   */
  public org.ietf.jgss.GSSName processServerPrincipalName(String serverPrincipalName) throws org.ietf.jgss.GSSException
  {
    org.ietf.jgss.Oid krb5Oid = new org.ietf.jgss.Oid("1.2.840.113554.1.2.2");
    if (serverPrincipalName != null)
      return manager_.createName (serverPrincipalName_,
                                  null,
                                  krb5Oid);

    else
      throw new JCCSimpleGSSException(0,"plugin bad principal name.");
  }


  /**
   *  This method will generate the security context information for the username/password pair.
   *  The security context information will be used to get the connection
   * @param username String
   * @param password String
   * @param returnedByte byte[]  the token returned by DB2 server
   * @throws SQLException if an error occurs.
   * @return byte[]  the security context information for this username/password pair
   */

  public byte[] getTicket(String username, String password, byte[] returnedByte) throws java.sql.SQLException

  {
       returnedToken_ = returnedByte;

       if (username == null) {
         setUseSubjectCredsOnly(false);
         try{
           getTicketX();
         }
         catch(org.ietf.jgss.GSSException e)
         {
           throw new java.sql.SQLException(e.getMessage());
         }
       }
       else {
         setUseSubjectCredsOnly (true);
         try {
           com.ibm.db2.jcc.am.Krb5JAASCallbackHandler handler =
             new com.ibm.db2.jcc.am.Krb5JAASCallbackHandler();

           handler.setUser(username);
           handler.setPassword(password);

           javax.security.auth.login.LoginContext loginCtxt =
             new javax.security.auth.login.LoginContext("JaasClient", handler);

           loginCtxt.login();
           javax.security.auth.Subject subject = loginCtxt.getSubject();

           javax.security.auth.Subject.doAsPrivileged(subject, this, null);
         }

         catch (javax.security.auth.login.LoginException e) {
           throw new java.sql.SQLException("javax.security.auth.login.LoginException happened");
         }
         catch (java.security.PrivilegedActionException e) {
           throw new java.sql.SQLException("java.security.PrivilegedActionException happened.");
         }

       }
      return ticket;
  }

  /**
   * This method will generate the security context information.
   * It is called by getTicket(String username, String password, byte[] returnedByte)
   * @throws GSSException if an error occurs
   */
  public void getTicketX() throws org.ietf.jgss.GSSException
  {
      if(returnedToken_ == null) {
        /*
         * Create a GSSName out of the server's name.
         */
         serverGSSName_ = processServerPrincipalName(serverPrincipalName_);

        /*
         * Create a GSSContext for mutual authentication with the
         * server.
         */
        org.ietf.jgss.Oid defaultMech = null;
        context_ = manager_.createContext(serverGSSName_,
            defaultMech,
            gssCredential_,
            org.ietf.jgss.GSSContext.INDEFINITE_LIFETIME);

        context_.requestMutualAuth(true); // Mutual authentication

        returnedToken_ = new byte[0];
      }

     int tokenLength = 0;
     if (returnedToken_ != null)
       tokenLength = returnedToken_.length;

     ticket = context_.initSecContext(returnedToken_, 0, tokenLength);
  }


  /**
   * It sets the JAVA variable javax.security.auth.useSubjectCredsOnly
   * to useSubjectCredsOnly
   * If useSubjectCredsOnly is false, JGSS will not acquire credentials
   * through JAAS and Kinit will be used to get the initial credentials
   * If useSubjectCredsOnly is true, JGSS will acquire credentials
   * through JAAS
   * @param useSubjectCredsOnly boolean
   */
  public void setUseSubjectCredsOnly (boolean useSubjectCredsOnly)
  {
    final String subjectOnly = useSubjectCredsOnly ? "true" : "false";
    final String property = "javax.security.auth.useSubjectCredsOnly";

    String temp = (String) java.security.AccessController.doPrivileged (
      new sun.security.action.GetPropertyAction (property));

    // Property not set. Set it to the specified value.
    if(temp == null)
      java.security.AccessController.doPrivileged (
        new java.security.PrivilegedAction() {
          public Object run()
          {
            System.setProperty (property, subjectOnly);
            return null;
          }
        }
      );
  }

  public Object run () throws org.ietf.jgss.GSSException
  {
    getTicketX();
    return null;
  }

}