package fnal.vox.security;
import org.globus.gsi.gssapi.GlobusGSSCredentialImpl;
import org.globus.gsi.gssapi.SSLUtil;
import org.globus.gsi.gssapi.GSSConstants;
import org.globus.gsi.gssapi.GlobusGSSManagerImpl;
import org.globus.gsi.GSIConstants;
import org.globus.gsi.TrustedCertificates;
import org.globus.gsi.GlobusCredential;
import org.globus.gsi.GlobusCredentialException;
import org.globus.gsi.gssapi.net.GssSocketFactory;
import org.globus.gsi.gssapi.net.GssSocket;
import org.globus.gsi.gssapi.auth.NoAuthorization;


import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid;

import org.gridforum.jgss.ExtendedGSSContext;
import org.gridforum.jgss.ExtendedGSSManager;
import org.gridforum.jgss.ExtendedGSSCredential;

import java.io.OutputStream;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.net.InetAddress;
import java.security.cert.X509Certificate;
import fnal.vox.security.ReadWriteSocket;
import fnal.vox.log.Log;

public abstract class ANAM{
	protected String[] dn=null;
	protected String[] ca=null;
	protected String prin=null;
	protected int lifeTime=0;


	/**
	* Perform a gsi handshake and authenticate the two way connection. This is used at server side.
	* @param clientSocket The socket on which the handshake will be performed.
	* @param context The context which is initiated between the client and this server.
	* @return true - means the authentication is successful. false - means authentication failed.
	*/
	public boolean gsiAuthenticate(Socket clientSocket,ExtendedGSSContext context)  throws Exception{
		byte [] inToken=null;
		byte [] outToken=null;
		boolean toReturn=false;
		InputStream in=null;
		
		try{
			in = clientSocket.getInputStream();
			ReadWriteSocket rwSocket=new ReadWriteSocket(clientSocket);
	
			while (!context.isEstablished()){
				//inToken=SSLUtil.readSslMessage(in);
				inToken=rwSocket.readToken();
				outToken=context.acceptSecContext(inToken, 0, inToken.length);
				if (outToken != null){
					rwSocket.writeToken(outToken);
				}
			}
			Log.write("Context established.");
			//dn=context.getSrcName().toString();
			X509Certificate[] chain=(X509Certificate[])context.inquireByOid(GSSConstants.X509_CERT_CHAIN);
			//X509Certificate[] chain=(X509Certificate[])context.getCertificateChain();
			if(chain!=null){
				int chainLength=chain.length;
				dn=new String[chainLength];
				ca=new String[chainLength];
				for(int i=0;i<chainLength;i++){
					if(chain[i]!=null){
						dn[i]=chain[i].getSubjectDN().getName();
						ca[i]=chain[i].getIssuerDN().getName();
						Log.write("Initiator DN["+i+"]-->"+dn[i]);
						Log.write("Initiator CA["+i+"]-->"+ca[i]);
					}
				}
			}
			lifeTime=context.getLifetime();
			//Log.write("Initiator DN : " + dn[0]);
			//Log.write("Initiator CA : " + ca[0]);
			Log.write("Acceptor  : " + context.getTargName());
			Log.write("Lifetime  : " + lifeTime);
			Log.write("Privacy   : " + context.getConfState());
			GlobusGSSCredentialImpl cred =(GlobusGSSCredentialImpl)context.getDelegCred();
			Log.write("Delegated credential :");
			if (cred != null){
				Log.write(cred.getGlobusCredential().toString());
			}else{
				Log.write("None");
			}
			toReturn=true;
		}finally{
			try{
				context.dispose();
			}catch(Exception e1){
				e1.printStackTrace();
			}
		}		

		return(toReturn);

	}


	/**
	* User interface for defining the authorize method<br>
	* The subclass can access lifeTime and dn for autherization.
	*/
	public abstract boolean authorize();
	

	/**
	* Sends a request to the server to perform gsi authentication and autherization (if enabled on server) on the given socket. This method is used at client side to initiate handshake.
	* @param clientSocket the socket on which the mutual authentication will be performed
	* @param context The context which is initiated between this client and the server.
	* @return true - means the auhentication (and autherization) is successful. false - means authentication (and/or autherization) failed.
	*/
	public boolean sendGsiRequest(Socket clientSocket,ExtendedGSSContext context) throws Exception{
		InputStream in=null;
		byte [] inToken=null;
		byte [] outToken=null;
		try{
			in = clientSocket.getInputStream();
			ReadWriteSocket rwSocket=new ReadWriteSocket(clientSocket);
	    		inToken = new byte[0];
			outToken = null;
			while (!context.isEstablished()){
				outToken=context.initSecContext(inToken, 0, inToken.length);
				if (outToken != null) {
					rwSocket.writeToken(outToken);
				}
				if (!context.isEstablished()) {
					//inToken = SSLUtil.readSslMessage(in);
					inToken=rwSocket.readToken();
				}
			}
			return(true);
		}finally{
			try{
				context.dispose();
			}catch(Exception e1){
				e1.printStackTrace();
			}
		}	
	}

	/**
        * A method to instantiate a ExtendedGSSContext object This is used at the server side to before calling gsiAuthenticate
	* @param cert The host certificate to key. If NULL then by default it uses /etc/grid-security/hostcert.pem
	* @param key The host key to user. If NULL then by default it uses /etc/grid-security/hostkey.pem
	* @return An ExtendedGSSContext object that can used for handshake , wraping and unwrapping messages. 
	*/
	public ExtendedGSSContext getGsiServerContext(String cert,String key) throws GSSException{
		GlobusCredential serverCred=null;
		TrustedCertificates trustedCerts=null;
		ExtendedGSSContext context=null;
		if(cert==null){
			cert="/etc/grid-security/hostcert.pem";
		}
		if(key==null){
			key="/etc/grid-security/hostkey.pem";
		}
		if(cert.length()==0){
			cert="/etc/grid-security/hostcert.pem";
		}
		if(key.length()==0){
			key="/etc/grid-security/hostkey.pem";
		}

		try{
			serverCred =new GlobusCredential(cert,key);
			serverCred.verify();
		}catch(GlobusCredentialException gce){
			System.out.println(gce);
			Log.write(gce.getMessage());
			throw new GSSException(GSSException.NO_CRED,0,"could not load host globus credentials "+gce.toString());
		}
		try{
			GSSCredential cred = new GlobusGSSCredentialImpl(serverCred, GSSCredential.INITIATE_AND_ACCEPT);
			trustedCerts=TrustedCertificates.load("/etc/grid-security/certificates");
			GSSManager manager = ExtendedGSSManager.getInstance();
			context = (ExtendedGSSContext)manager.createContext(cred);
			context.setOption(GSSConstants.GSS_MODE,GSIConstants.MODE_GSI);
			context.setOption(GSSConstants.TRUSTED_CERTIFICATES,trustedCerts);
        		return(context);
		}finally{
			try{
				context.dispose();
			}catch(Exception e1){
				e1.printStackTrace();
			}

		}
		
	}


	/**
        * A method to instantiate a ExtendedGSSContext object This is used at the client side before calling sendGsiRequest
	* @return An ExtendedGSSContext object that can used for handshake , wraping and unwrapping messages. 
	*/
	public ExtendedGSSContext getGsiClientContext() throws GSSException{
		GSSManager manager = new GlobusGSSManagerImpl();
		ExtendedGSSContext context = (ExtendedGSSContext)manager.createContext(null,GSSConstants.MECH_OID,null,GSSContext.DEFAULT_LIFETIME);
		context.setOption(GSSConstants.GSS_MODE,GSIConstants.MODE_GSI);
		//context.requestCredDeleg(true);
		context.requestCredDeleg(false);
		return(context);

	}
	
	/**
        * A method to instantiate a GSSContext object This is used at the client side before calling sendKerberosRequest
	* @param host The fully qualified host name of the server.
	* @return An GSSContext object that can used for handshake , wraping and unwrapping messages. 
	*/
	public GSSContext getKerberosClientContext(String host) throws GSSException{
		Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
		GSSManager manager = GSSManager.getInstance();
		GSSName serverName = manager.createName("host/"+host,null);
		GSSContext context = manager.createContext(serverName,krb5Oid,null,GSSContext.DEFAULT_LIFETIME);
		return(context);
	}

	/**
        * A method to instantiate a GSSContext object This is used at the server side to before calling kerberosAuthenticate
	* @return An GSSContext object that can used for handshake , wraping and unwrapping messages. 
	*/
	public GSSContext getKerberosServerContext() throws Exception{
		Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
		GSSManager manager = GSSManager.getInstance();
		GSSContext context = manager.createContext((GSSCredential)null);
		return(context);

	}
	
	/**
	* Perform a kerberos handshake and authenticate the two way connection. This is used at server side.
	* @param clientSocket The socket on which the handshake will be performed.
	* @param context The context which is initiated between the client and this server.
	* @return true - means the authentication is successful. false - means authentication failed.
	*/
	public boolean kerberosAuthenticate(Socket clientSocket,GSSContext context) throws Exception{
		ReadWriteSocket rwSocket=new ReadWriteSocket(clientSocket);
		byte[] token = null;
		while (!context.isEstablished()){
			token=rwSocket.readKerberosToken();
			token = context.acceptSecContext(token, 0, token.length);
			if (token != null) {
				rwSocket.writeKerberosToken(token);
			}
		}
		prin=context.getSrcName().toString();
		Log.write("Client is " + prin);
		Log.write("Server is " + context.getTargName());
		return(true);
	}	

	/**
	* Sends a request to the server to perform kerberos authentication and autherization (if enabled on server) on the given socket. This method is used at client side to initiate handshake.
	* @param clientSocket the socket on which the mutual authentication will be performed
	* @param context The context which is initiated between this client and the server.
	* @return true - means the auhentication (and autherization) is successful. false - means authentication (and/or autherization) failed.
	*/
	public boolean sendKerberosRequest(Socket clientSocket,GSSContext context)throws Exception{
		ReadWriteSocket rwSocket=new ReadWriteSocket(clientSocket);
		context.requestMutualAuth(true);
		context.requestConf(true);
		context.requestInteg(true); 
		byte[] token = new byte[1];
		while (!context.isEstablished()) {
			token = context.initSecContext(token, 0, token.length);
			if (token != null) {
				rwSocket.writeKerberosToken(token);
    			}
			if (!context.isEstablished()) {
				token=rwSocket.readKerberosToken();
			}
		}
		return(true);
	}

	public Socket getGsiServerSocket(Socket clientSocket,ExtendedGSSContext context) throws Exception{
		clientSocket = GssSocketFactory.getDefault().createSocket(clientSocket, null, 0, context);
		((GssSocket)clientSocket).setUseClientMode(false);
		((GssSocket)clientSocket).setWrapMode(GssSocket.GSI_MODE);
		((GssSocket)clientSocket).setAuthorization(NoAuthorization.getInstance());
		return(clientSocket);
	}

	public Socket getGsiClientSocket(String host,int port,ExtendedGSSContext context) throws Exception{
		Socket clientSocket = GssSocketFactory.getDefault().createSocket(host, port, context);
		((GssSocket)clientSocket).setWrapMode(GssSocket.GSI_MODE);
		((GssSocket)clientSocket).setAuthorization(NoAuthorization.getInstance());
		return(clientSocket);
	}

}
