
// package
package org.sakaiproject.component.grid;

// imports

import org.sakaiproject.service.grid.GridService;

import java.io.*;
import java.util.*;
import java.net.*;
import java.lang.Math;

import org.sakaiproject.service.framework.log.Logger;

import org.globus.myproxy.*;
import org.gridforum.jgss.ExtendedGSSManager;
import org.gridforum.jgss.ExtendedGSSCredential;
import org.ietf.jgss.GSSCredential;

import org.sakaiproject.service.legacy.user.User;
import org.sakaiproject.service.legacy.user.UserEdit;
import org.sakaiproject.service.legacy.resource.ResourceProperties;
import org.sakaiproject.service.legacy.resource.ResourcePropertiesEdit;
import org.sakaiproject.util.storage.StorageUser;

import org.sakaiproject.service.framework.session.cover.UsageSessionService;
import org.sakaiproject.service.legacy.user.cover.UserDirectoryService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.InUseException;

import java.util.Hashtable;
import java.util.LinkedList;

/**
 * <p>GridService is currently just a place where some definitions pertaining to
 * portal access references can be centralized.</p>
 *
 * @author University of Michigan, CHEF Software Development Team
 * @version $Revision: 1.8 $
 */
public class GridServiceImpl
	implements GridService
{
	/** A Hash Table to store the actual proxies */
        private Hashtable m_proxies = null;

	/** A linked list to store the IDs in a "FIFO" queue */
	private LinkedList m_entries;	

	/** The host to contact for myproxy */
	private String m_hostName = "myproxy.ncsa.uiuc.edu";

	/** The port to contact for myproxy */
	private int m_hostPort = 7512;

	/** The time to cache the myproxy return information (seconds) */
	private int m_cacheTime = 300;

        /** The name of the guest credential */
        private String m_guest_name = null;

        /** The password of the guest credential */
        private String m_guest_password = null;

        /** The prefix for the kx_509 credential location */
        private String m_kx509Prefix = null;

        /** The static x509 credential location (for testing) */
        private String m_staticCert = null;

	/** Service Dependency - Logger */
	private Logger logger = null;

	/**
	*/
	public void init()
	{
/*
		// read the table names

		m_hostName = getProperties().getProperty("host", "");
		String sPort = getProperties().getProperty("port", "7512");
		String sCache = getProperties().getProperty("cachetime", "300");
		m_kx509Prefix = getProperties().getProperty("kx509prefix", "");
		m_staticCert  = getProperties().getProperty("static_cert", "");
 
		try {
			int m_hostPort = Integer.parseInt(sPort);
		} catch (NumberFormatException e) {
			System.out.println("BaseGridService.init() - Parse error port="+sPort);
			m_hostPort = 7512;
		}
		try {
			int m_cacheTime = Integer.parseInt(sCache);
		} catch (NumberFormatException e) {
			System.out.println("BaseGridService.init() - Parse error port="+sCache);
			m_cacheTime = 300;
		}
		System.out.println("BaseGridService.init() host = " + m_hostName + " port = " 
				   + m_hostPort+" cachetime="+m_cacheTime);

                m_guest_name = getProperties().getProperty("guest_name", "");
                m_guest_password = getProperties().getProperty("guest_password", "");
                if ( m_guest_name.length() <= 0 || m_guest_password.length() <= 0  ) {
                        m_guest_name = null;
                        m_guest_password = null;
                }
                System.out.println(" guest_name="+m_guest_name+" guest_password="+m_guest_password);

		// Create the data structures
            	m_proxies = new Hashtable();
            	m_entries = new LinkedList();

		super.init();
*/
	}	// init

	/** 
	* Convert a Credential to a string for printing
	*/
	public String toString(GSSCredential proxy)
	{
		String retval;
		if ( proxy != null ) {
			retval = "Cert = " + getName(proxy) + "\n";
		} else {
			retval = "Proxy was null";
		}
		return retval;
	}

	public String toStringDetailed(GSSCredential proxy)
	{
		String retval;
		if ( proxy != null ) {
			retval = toString(proxy);
		} else {
			retval = "Proxy was null";
		}
		return retval;
	}

	/**
	 * Return the GSS Credential from the CHEF user Context
	 */
	public GSSCredential getCurrentUserGSSCredential()
	{
	
           	GSSCredential proxy = null; 

		User curUser = UsageSessionService.getSessionUser();

		logger.info(" getCurrentUserGSSCredential user = " + curUser.getId());

	    	ChefProxyInfo proxyInfo = getProxyInfo(curUser.getId());

		if ( proxyInfo == null ) return null;

		return proxyInfo.getProxy();
	}

	/**
	 * Check to see if the oldest entries in the Hash Table need to be 
	 * expired.
	 */
	private void expireCache() 
	{
		// System.out.println("BaseGridService.expireCache");
                if (m_proxies.isEmpty()== true) return;
   		if (m_entries.isEmpty()== true) return;

		// Check the first entry in the FIFO list
		String topEntry = (String) m_entries.getFirst();
		// System.out.println("Found id="+topEntry);

		// Retrieve it from the cache
		ChefProxyInfo proxyInfo = (ChefProxyInfo)getProxyInfo(topEntry);
		if ( proxyInfo == null ) {
			System.out.println("BaseGridService.expireCache proxy entry not found FIFO entry removed:"+topEntry);
			topEntry = (String) m_entries.removeFirst();
			return;
		}

		// Check to see if it is old
            	long starttime = (System.currentTimeMillis()/1000);
		long timeDifference = Math.abs(proxyInfo.getTime() - starttime);

		// System.out.println("Found entry in table id="+proxyInfo.getId()+
		//   " time="+proxyInfo.getTime()+
		//   " starttime="+starttime+" diff="+timeDifference);

		if ( timeDifference > m_cacheTime)  {
			System.out.println("BaseGridService.expireCache expired:"+topEntry);
			topEntry = (String) m_entries.removeFirst();
			proxyInfo = (ChefProxyInfo) m_proxies.remove(topEntry);
			return;
		}
		// System.out.println("BaseGridService.expireCache top entry still valid");
	}
	
	/** 
	* Get the GSSCredential Proxy from the CHEF user context - may return null
	*/
	private ChefProxyInfo getProxyInfo(String id)
	{
                if (m_proxies.isEmpty()== true) return null;
            
                // System.out.println("Retrieving Cached Proxy Information for "+id);
                
                if (m_proxies.containsKey(id)== false)
		{
			return null;
		}
                else
                {
			ChefProxyInfo proxyInfo = (ChefProxyInfo)m_proxies.get(id);
			// System.out.println("Found proxyInfo time="+proxyInfo.getTime());
			return proxyInfo;
		}
	}

        /**
         ** Get Grid Proxy Credential from X509 certificate and return
         **/
        private GSSCredential getX509GSSCredential( String x509path, String username, String password, long starttime )
        {
	    GSSCredential proxy = null;
            System.out.println("Loading the kx509 from "+x509path);
                
            try 
            {
                FileInputStream in = new FileInputStream(x509path);
                int length = in.available();

                if ( length > 10000 ) length = 10000;
                byte [] data = new byte[length];

                int read = in.read(data);

                ExtendedGSSManager manager = (ExtendedGSSManager)ExtendedGSSManager.getInstance();

                GSSCredential gssCred  = manager.createCredential(data,
                                         ExtendedGSSCredential.IMPEXP_OPAQUE,
                                         GSSCredential.DEFAULT_LIFETIME,
                                         null, // use default mechanism - GSI
                                         GSSCredential.ACCEPT_ONLY);

                addProxyToCache(username,password,gssCred,starttime);
                return proxy;
            }
            catch(FileNotFoundException e) 
            {
                System.out.println("X509 proxy not found "+x509path);
                // Fall Through to check myproxy - no need to traceback
            }
            catch(Throwable t) 
            {
                System.out.println("Unable to load proxy from "+x509path);
                System.out.println("Error:"+t.getMessage());
                t.printStackTrace();
                // Fall through to check to see if we are doing myproxy
            }
            
            return null;
        }

	/**
          * Retrueve a credential using a password
	  */
	public GSSCredential getGSSCredential(String password)
	{
		GSSCredential proxy = null;

                String m_hostName = "myproxy.ncsa.uiuc.edu";

                User curUser = UsageSessionService.getSessionUser();
		String id = curUser.getId();
                logger.debug(" getCurrentUserGSSCredential user = " + id);

		return getGSSCredential(id,password,m_hostName);
	}

	/**
	  * Get Credential from a id/pw/host
	  */
        public GSSCredential getGSSCredential(String id, String pw, String host)
	{
		GSSCredential proxy = null;

                int m_hostPort = 7512;

                logger.debug("Initializing myproxy h="+host+" p="+m_hostPort);
        
                MyProxy myproxy = new MyProxy(host,m_hostPort);
        
		try {   
			logger.debug("Retrieving credential...");
			proxy = myproxy.get(id,pw,3600);
        		logger.info(this+" Subject = " + proxy.getName());
		} catch (Throwable t)
		{       
			proxy = null;
        		logger.info(this+"Threw exception on myproxy.get");
		}       
		return proxy;
	}
        
	/**
	* Get a Grid Proxy Credential for the user and return
	*/
	public GSSCredential loginGlobusUser(UserEdit edit, String pw)
	{
            long starttime = (System.currentTimeMillis()/1000);                               
            boolean isGuest = false;

	    String id = edit.getId();
            if ( pw == null ) 
               pw = "";

 	    System.out.println("BaseGridService.loginGlobusUser("+id+",pw)");

	    // First check to see if the top entry needs to be expired
	    expireCache();

	    GSSCredential proxy = null;

	    ChefProxyInfo proxyInfo = getProxyInfo(id);

	    if ( proxyInfo != null )
	    {
		long timeDifference = Math.abs(proxyInfo.getTime() - starttime) ;
		// System.out.println("Found entry in table id="+proxyInfo.getId()+
		//   " pw="+proxyInfo.checkPassword(pw)+" time="+proxyInfo.getTime()+
		//   " starttime="+starttime+" diff="+timeDifference);

		if ( id.equals(proxyInfo.getId()) && proxyInfo.checkPassword(pw) && 
		     timeDifference < m_cacheTime)  {
			System.out.println(" ******* Found in the hash table ");
			proxy = proxyInfo.getProxy();
			if ( proxy != null ) return proxy;
		} else {
			System.out.println(" ******* Password Mismatch or Entry Expired");
			proxyInfo = (ChefProxyInfo) m_proxies.remove(id);
		}
		proxyInfo = null;
            }

	    String username = id;
	    String password = pw;
	    int lifetime    = 10 * 3600;

            // First check if static certificate is defined and present
            if ( m_staticCert != "" )
            {
               proxy = getX509GSSCredential( m_staticCert, username, password, starttime );
               if ( proxy != null )
                  return proxy;
            }
            
            // Next, try to find the KX509 credential for this person
            if ( m_kx509Prefix != "" ) 
            {
               String kx509_path = m_kx509Prefix + username;
               proxy = getX509GSSCredential( kx509_path, username, password, starttime );
               if ( proxy != null )
                  return proxy;
            }
            
            // Finally, look for myproxy credentials
	    try {

		System.out.println("Initializing myproxy h="+m_hostName+" p="+m_hostPort);

		MyProxy myproxy = new MyProxy(m_hostName,m_hostPort);

		System.out.println("Retreiving credentials...");
		proxy = myproxy.get(username,password,lifetime);

		System.out.println("convert proxy to string"+toStringDetailed(proxy));
            } catch (Exception e) {
                System.err.println("Error: Failed to obtained the delegated credentials!");
                if ( m_guest_name == null ) return null;

                System.out.println("Retreiving guest credentials name="+m_guest_name+" pw="+m_guest_password);
                try {

                        MyProxy myproxy = new MyProxy(m_hostName,m_hostPort);
                        proxy = myproxy.get(m_guest_name,m_guest_password,lifetime);
                        System.out.println("Guest proxy:"+toStringDetailed(proxy));
                        isGuest = true;
                } catch (Exception e2) { return null; }
            }
	    catch( Throwable t ) {
		System.out.println("Low level error (probably a missing jar): "+t.getMessage());
		return null;
	    }

	    // See if this user exists - If so, grab the GRID:Subject property
	    // and compare to this.

	    // Note may want to truncate ,CN=proxy from the 
	    // subject - but that may not matter - csev@umich.edu

	    if ( ! isGuest )
	    {
	    	ResourceProperties props = null;
	        props = edit.getProperties();
	        String subjectDN = props.getProperty("GRID:Subject");

	        if ( subjectDN != null )
	        {

			String pSubject = getName(proxy) + "";

			// Find second ,CN= and truncate
			int ipos = pSubject.indexOf(",CN=");
			if ( ipos > 2 ) ipos = pSubject.indexOf(",CN=",ipos+1);  // Skip to second
			if ( ipos > 2 ) pSubject = pSubject.substring(0,ipos);

			String cSubject = subjectDN + "";
			ipos = cSubject.indexOf(",CN=");
			if ( ipos > 2 ) ipos = cSubject.indexOf(",CN=",ipos+1);  // Skip to second
			if ( ipos > 2 ) cSubject = cSubject.substring(0,ipos);

			System.out.println("CS="+cSubject);
			System.out.println("PS="+pSubject);

			if ( cSubject.equals(pSubject) )
			{
				System.out.println("Retrieved subject matches with stored version...");
			} else {
				System.out.println("Subject mismatch...");
	        		System.out.println("Found property "+cSubject);
	        		System.out.println("Subject "+pSubject);

				if (! edit.checkPassword(password)) {
					System.out.println("Mismatch between certificate and internal password");
					return null;
				}
				System.out.println("Certificate and Internal Password Matched, GRID:Subject changed id="+id);
			}
	        }
		// Update the grid-related user information

		ResourcePropertiesEdit propsEdit = edit.getPropertiesEdit();
        	propsEdit.addProperty("GRID:Subject",getName(proxy));
		edit.setPassword(password);
		System.out.println("Stored GRID:Subject and internal password");
		UserDirectoryService.commitEdit(edit);
	    }

	    // Store the proxy in the hash table
	    if ( proxy == null ) return null;

	    addProxyToCache(id,password,proxy,starttime);
            
	    return proxy;
	}

	/*
	 * Extract a name from a proxy, eating the unlikely exception
	 */

	private String getName(GSSCredential proxy)
	{
		String retval = null;
		try {
			retval = proxy.getName().toString();
		} catch (Exception e) {
			// Not too likely
			retval = null;
		}
		return retval;
	}

        /**
         ** Place the proxy information into the hash table and the id into the FIFO queue
         **/
	private void addProxyToCache(String id,String password,GSSCredential proxy,long starttime)
	{

	    // System.out.println("Storing information in cache...");
	    ChefProxyInfo proxyInfo  = new ChefProxyInfo();
    
	    proxyInfo.setdata(id,password,proxy,starttime);
    
	    m_proxies.put(id,proxyInfo);
	    m_entries.add(id);
	}
        
	/*
	 * Class which is stored in the has table
	 */

	public class ChefProxyInfo
	{
		GSSCredential p_proxy;
		String p_id;
		String p_Password;
        	long m_start;
		String p_EncodedPassword;

		public ChefProxyInfo()
		{
			p_id = "";
			p_Password = "";
			m_start = 0;
		}
	
		public void setdata(String id, String password, GSSCredential proxy, long time)
		{
			System.out.println("id is "+id+" pw is (pw)");
			p_id = id;
			setPassword(password);
			p_proxy = proxy;
			m_start = time;
		}

		public void setPassword(String password)
		{
			p_Password= password;
			p_EncodedPassword = OneWayHash.encode(password);
			System.out.println("Encoded password "+p_EncodedPassword);
		}
		
		public String getId()
		{
			return p_id;
		}

		public long getTime()
		{
    			return m_start;
		}

		public boolean checkPassword(String testPassword)
		{
			boolean retval = false;

			if ( testPassword == null ) return false;
			/*
			if ( p_Password.equals(testPassword) )
			{
				retval = true;
				System.out.println("Plain text passwords matched");
			}
			*/

			System.out.println("Checking digest passwords...");
			// encode this password
			String encoded = OneWayHash.encode(testPassword);

			System.out.println("stored "+p_EncodedPassword);
			System.out.println("encoding "+encoded);

			retval = encoded.equals(p_EncodedPassword);
			if ( retval ) System.out.println("Digested passwords match...");
			return retval;
		}

		public GSSCredential getProxy()
		{
			return p_proxy;
		}

	} // ChefProxyInfo

        /**
         * Inject the Logger dependency
         * @param logger - The logger Instance
         */
        public void setLogger(Logger logger)
        {
                this.logger = logger;
		// System.out.println("Injected logger="+logger);
        }

        /**
         * Return the logger service
         */
        public Logger getLogger()
        {
                return logger;
        }


}	// GridService

/**********************************************************************************
 *
 * $Header: /CVS/neesgrid-chef/chef/modules/umich/grid/src/java/org/chefproject/service/component/BaseGridService.java,v 1.8 2004/07/13 01:34:43 crs Exp $
 *
 **********************************************************************************/
