/*
 * Copyright (c) 2006-2018 ymnk, JCraft,Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jcraft.jsch.jgss;

import com.jcraft.jsch.JSchException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;

public class GSSContextKrb5 implements com.jcraft.jsch.GSSContext {

  private static final String pUseSubjectCredsOnly = "javax.security.auth.useSubjectCredsOnly";
  private static String useSubjectCredsOnly = getSystemProperty(pUseSubjectCredsOnly);

  private GSSContext context = null;

  @Override
  public void create(String user, String host) throws JSchException {
    try {
      // RFC 1964
      Oid krb5 = new Oid("1.2.840.113554.1.2.2");
      // Kerberos Principal Name Form
      Oid principalName = GSSName.NT_HOSTBASED_SERVICE;

      GSSManager mgr = GSSManager.getInstance();

      GSSCredential crd = null;
      /*
      try {
        GSSName _user = mgr.createName(user, principalName);
        crd = mgr.createCredential(_user,
                                   GSSCredential.DEFAULT_LIFETIME,
                                   krb5,
                                   GSSCredential.INITIATE_ONLY);
      } catch (GSSException crdex) {
      }
      */

      String cname = host;
      try {
        cname = InetAddress.getByName(cname).getCanonicalHostName();
      } catch (UnknownHostException e) {
      }
      GSSName _host = mgr.createName("host@" + cname, principalName);

      context = mgr.createContext(_host, krb5, crd, GSSContext.DEFAULT_LIFETIME);

      // RFC4462  3.4.  GSS-API Session
      //
      // When calling GSS_Init_sec_context(), the client MUST set
      // integ_req_flag to "true" to request that per-message integrity
      // protection be supported for this context.  In addition,
      // deleg_req_flag MAY be set to "true" to request access delegation, if
      // requested by the user.
      //
      // Since the user authentication process by its nature authenticates
      // only the client, the setting of mutual_req_flag is not needed for
      // this process.  This flag SHOULD be set to "false".

      // TODO: OpenSSH's sshd does accepts 'false' for mutual_req_flag
      // context.requestMutualAuth(false);
      context.requestMutualAuth(true);
      context.requestConf(true);
      context.requestInteg(true); // for MIC
      context.requestCredDeleg(true);
      context.requestAnonymity(false);

      return;
    } catch (GSSException ex) {
      throw new JSchException(ex.toString(), ex);
    }
  }

  @Override
  public boolean isEstablished() {
    return context.isEstablished();
  }

  @Override
  public byte[] init(byte[] token, int s, int l) throws JSchException {
    try {
      // Without setting "javax.security.auth.useSubjectCredsOnly" to "false",
      // Sun's JVM for Un*x will show messages to stderr in
      // processing context.initSecContext().
      // This hack is not thread safe ;-<.
      // If that property is explicitly given as "true" or "false",
      // this hack must not be invoked.
      if (useSubjectCredsOnly == null) {
        setSystemProperty(pUseSubjectCredsOnly, "false");
      }
      return context.initSecContext(token, 0, l);
    } catch (GSSException ex) {
      throw new JSchException(ex.toString(), ex);
    } catch (SecurityException ex) {
      throw new JSchException(ex.toString(), ex);
    } finally {
      if (useSubjectCredsOnly == null) {
        // By the default, it must be "true".
        setSystemProperty(pUseSubjectCredsOnly, "true");
      }
    }
  }

  @Override
  public byte[] getMIC(byte[] message, int s, int l) {
    try {
      MessageProp prop = new MessageProp(0, true);
      return context.getMIC(message, s, l, prop);
    } catch (GSSException ex) {
      return null;
    }
  }

  @Override
  public void dispose() {
    try {
      context.dispose();
    } catch (GSSException ex) {
    }
  }

  private static String getSystemProperty(String key) {
    try {
      return System.getProperty(key);
    } catch (Exception e) {
      // We are not allowed to get the System properties.
      return null;
    }
  }

  private static void setSystemProperty(String key, String value) {
    try {
      System.setProperty(key, value);
    } catch (Exception e) {
      // We are not allowed to set the System properties.
    }
  }
}
