/***********************************************************************
 * 
 *  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;

					// determine if user has more than 1 process still running
					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;
		}
	}
}