/***********************************************************************
 * 
 *  Copyright (C) 2005-2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 ***********************************************************************/


using System;
using System.Collections;
using System.Threading;
using System.IO;
using sscs.cache;
using sscs.common;
using sscs.constants;
using System.Diagnostics;

namespace sscs.common
{

	class SessionManager
	{
		private static readonly SessionManager sessionManager = new SessionManager();
		private static Mutex mutex = new Mutex();
		private static Hashtable sessionTable = new Hashtable();
		private static Thread tJanitor = null;
		private static int JANITOR_SLEEP_TIME = 1000*60*5;  // 5 minutes
    
		private SessionManager()
		{
#if LINUX
			if (tJanitor == null)
			{
				tJanitor = new Thread(new ThreadStart(CleanUpSessionsThread));
				tJanitor.Start();
			}		
#endif
		}
		~SessionManager()
		{
                    if (tJanitor != null)
                    {
                        tJanitor.Abort();
                        tJanitor.Join();
                    }
                    mutex.Close();
		}
		internal static SessionManager GetSessionManager
		{
			get
			{
				return sessionManager;
			}
		}

		internal static SecretStore CreateUserSession(UserIdentifier userId)
		{
			CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
			SecretStore ss;
			userId.PrintIdentifier();
			try
			{
				ss = GetUserSecretStore(userId);
				ss.IncrRefCount();
                                ss.CreateTime = DateTime.Now;
				return ss;
			}
			catch(UserNotInSessionException e)
			{
				// Would create either windows/unix user 
                                // depending on the platform. 
				User user = User.CreateUser(userId);
				mutex.WaitOne();
				sessionTable.Add(userId,user);
				mutex.ReleaseMutex();
				ss = user.GetSecretStore();
				ss.IncrRefCount();
                                ss.CreateTime = DateTime.Now;
				return ss;
			}
		}

		internal static bool RemoveUserSession(UserIdentifier userId, bool destroySession)
		{
		    CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                    try
                    {
			mutex.WaitOne();
			SecretStore ss = GetUserSecretStore(userId);
			ss.DecrRefCount();
		
			// We must keep the cache alive, and destroy it on 
                        // a logout event

			//if( 0 == ss.refCount )
			if (destroySession)
			{
                            CSSSLogger.DbgLog("Removing the user session of " + userId.GetUID());
                            ss.CommitStore();
  			    sessionTable.Remove(userId);
			}
		
			mutex.ReleaseMutex();
			return true;
                    }
                    catch(Exception e)
                    {
                        CSSSLogger.ExpLog(e.ToString());
                    }
                    return false; 
		}

		internal static bool CheckIfUserSessionExists(UserIdentifier userId)
		{
			CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
			mutex.WaitOne();

			if( sessionTable.ContainsKey(userId) )        
			{
				mutex.ReleaseMutex();
				return true;
			}
			else
			{
				mutex.ReleaseMutex();
				return false;
			}
		}


		internal static SecretStore GetUserSecretStore(UserIdentifier userId)
		{
			CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
			userId.PrintIdentifier();
			ListActiveUserSessions();
			mutex.WaitOne();
			if( sessionTable.ContainsKey(userId) )
			{
				User user = (User)sessionTable[userId];
				SecretStore ss = user.GetSecretStore();
				// start persistent if not going yet
				if (!ss.IsStorePersistent())
				{	
					string sDesktopPWD = ss.GetDesktopPasswd();
					if (sDesktopPWD != null)
						ss.StartPersistenceByDesktopPasswd(sDesktopPWD);
				}

				mutex.ReleaseMutex();
				return ss;
			}
			else
			{
				mutex.ReleaseMutex();
				throw new UserNotInSessionException(userId);
            	            
			}
		}

                internal static DateTime GetSessionCreateTime(UserIdentifier userId)
                {
                    CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                    mutex.WaitOne();
                    if( sessionTable.ContainsKey(userId) )
                    {
                        User user = (User)sessionTable[userId];
                        SecretStore ss = user.GetSecretStore();
                        mutex.ReleaseMutex();
                        return ss.CreateTime;
                    }
                    else
                    {
                        mutex.ReleaseMutex();
                        throw new UserNotInSessionException(userId);
                    }
                }

		internal static void ListActiveUserSessions()
		{
			CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
			mutex.WaitOne();
			IDictionaryEnumerator etor = sessionTable.GetEnumerator();
			int i = 0;
        
			while(etor.MoveNext())
			{
				i++;
/*
                                CSSSLogger.DbgLog("Listing Active User Sessions");
				Console.WriteLine(etor.Key);
				Console.WriteLine((((SecretStore)(etor.Value)).secretStoreName + ":" + ((SecretStore)(etor.Value)).refCount);
*/
			}
                        mutex.ReleaseMutex();
                } 


		private static void CleanUpSessionsThread()
		{
                    CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                    try
                    {
                        while (true)
  		        {
		            // enumerate users in the session
                            IEnumerator etor; 
   			    ICollection keys = sessionTable.Keys;
			    if (keys != null)
			    {
			        etor = keys.GetEnumerator();			
			        while(etor.MoveNext())
			        {
			            UserIdentifier userIdentifier = (UserIdentifier)etor.Current;
			
				    // check if this user still has 
                                    // processes running
				    if(CheckAndDestroySession(userIdentifier,false))
                                    {
                                        /* If at least 1 session was removed, 
                                         * the etor must be 
                                         * re-initiated, else 
                                         * Invalidoperationexception will be 
                                         * thrown.
                                         */                                   
                                         keys = sessionTable.Keys;
                                         if( null == keys )
                                             break;
                                         else
                                         {
                                             etor = keys.GetEnumerator();
                                         }
                                    }
                                }//while etor.MoveNext ends here.    
                            }
			    Thread.Sleep(JANITOR_SLEEP_TIME);
                        } //while true ends here.   
                    }
                    catch(ThreadAbortException e)
                    {
                        CSSSLogger.DbgLog("Janitor thread is going down.");
                    }
                      
                }//Method ends here.

                /* As the pam module does a seteuid(), when is ps is
                 * execed it would appear as if the user owns the process.
                 * Hence, if this method is called from CloseSecretStore
                 * verb ( that would have been initiated from the pam 
                 * module with ssFlags = 1), then if number of processes
                 * is one, then delete the session.
                 */

		internal static bool CheckAndDestroySession(UserIdentifier userID, bool calledFromClose)
		{
                    CSSSLogger.ExecutionTrace(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                    int iUID = userID.GetUID();
                    bool retVal = false; 
                    Process myProcess = null;
                    StreamReader myStreamReader = null;
		    if (iUID != -1)
		    {
		        // make the 'ps h U UID' call
			try 
			{
		            myProcess = new Process();
			    ProcessStartInfo myProcessStartInfo = new ProcessStartInfo("ps" );
					
			    myProcessStartInfo.Arguments = "h U " + iUID.ToString();					
			    myProcessStartInfo.UseShellExecute = false;
			    myProcessStartInfo.RedirectStandardOutput = true;
			    myProcess.StartInfo = myProcessStartInfo;
			    myProcess.Start();
                            myProcess.WaitForExit();

			    myStreamReader = myProcess.StandardOutput;
				
			    // Read the standard output of the spawned process.
			    string myString = myStreamReader.ReadLine();
                            int numProcs = 0;
                            while( myString != null)
                            {
                                if(numProcs > 1)
                                    break;
                                numProcs++;

                                myString = myStreamReader.ReadLine();       
                            }


                            do
                            {
                                /* If this has been called from 
                                 * CloseSecretStore verb,
                                 * verb, the session must be deleted.
                                 */
                                if( calledFromClose )
                                {
                                    RemoveUserSession(userID, true);
                                    retVal = true;
                                    break;
                                }
                                /* If the session was created during login,
                                 * and the janitor thread starts processing 
                                 * before user login is completed, we need
                                 * maintain the user session (say for 5 mts).
                                 */
                                if( (numProcs == 0) && (CheckIfLoginTimeSession(userID)) )
                                {
                                    retVal = false;
                                    break;
                                } 
                            
                                /* If the user does not own any processes and  
                                 * if this method has not been called from 
                                 * CloseSecretStore verb, it implies that a user
                                 * background process, which existed during user
                                 * logout has died now. 
                                 * So, clean the user session.
                                 */
 
                                if ( (numProcs == 0) && (!calledFromClose) )
                                {
                                    RemoveUserSession(userID, true);
                                    retVal = true;
                                    break;
                                }
                            }while(false);
/*
                            myProcess.Close();
                            myStreamReader.Close();
*/
                        }
                        catch (Exception e)
			{
			    CSSSLogger.DbgLog(e.ToString());
 			}	
                        finally
                        {
                            if( myProcess != null )
                                myProcess.Close();
                            if( myStreamReader != null )
                                myStreamReader.Close();

                        }
                    }
                    return retVal;
		}
                internal static bool CheckIfLoginTimeSession(UserIdentifier userId)
                {
                    if( ((TimeSpan)(DateTime.Now - GetSessionCreateTime(userId))).TotalMinutes < 3 )
                    {
                        return true;
                    }
                    else
                        return false;
                }
	}
}