/***********************************************************************
 * 
 *  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.IO;
using System.Text;
using System.Collections;
using System.Threading;
using System.Security.Cryptography;
using System.Xml;
#if LINUX
using Mono.Unix.Native;
#endif
using sscs.cache;
using sscs.crypto;
using sscs.common;
using sscs.constants;
using Novell.CASA.MiCasa.Common;

namespace sscs.lss
{
    /// <summary>
    /* 
     * This class is a service to store data persistently.
     * How it does this is determined by implementation within the 
     * private methods (File system using file(s), database, etc)
     * The MasterPasscode can be used to generate the key for 
     * encyption and decryption.    
     * If encrpytion is used, the private methods will also manage 
     * how the encyption key is to be stored and retrieved.
     * Each piece of data is located by a DataID.  
     * This might be an individual credentail or
     * a complete store.  
     */

     /* We might not need this as a separate class. 
      * Depending on the db changes, we can change this later.
      */

    /// </summary>
    public class LocalStorage
    {        
		private byte[] m_baGeneratedKey = null;
		private SecretStore userStore = null;

        private int persistThreadSleepTime = 1000 * 60 * 5; //1000 * 30; 
        private Thread persistThread = null;       

#if LINUX
		Mono.Unix.UnixFileSystemInfo sockFileInfo;
		Mono.Unix.UnixUserInfo sockFileOwner;
#endif

        private static string LINUXID = "Unix";         
        
		internal LocalStorage(SecretStore store,byte[] baMasterPasscode)
        {
            userStore = store;  
			m_baGeneratedKey = baMasterPasscode;            
            LoadPersistentStore();
            userStore.DumpSecretstore();
        }
        ~LocalStorage()
        {
            if(persistThread != null) 
            {
                persistThread.Abort();
                persistThread.Join();
            }
        }

        // allowing a user to choose the storage location is not approved yet
		private LocalStorage(SecretStore store, 
			byte[] baMasterPasscode, string sStorageDirectory)
        {
            userStore = store;
			m_baGeneratedKey = baMasterPasscode;
            LoadPersistentStore();
            userStore.DumpSecretstore();
        }

        private void StorePersistentData(string sDataID, byte[] baData)
        {

        }

        private byte[] RetrievePersistentData(string sDataID)
        {


            return null;
        }

		public void PersistStoreWithDelay()
		{
			if (persistThread == null)
			{
				persistThread = new Thread(new ThreadStart(PersistStoreDelayThreadFn));
				persistThread.Start();
			}
		}
		
		public bool StopPersistence()
		{
			if(persistThread != null) 
			{
				persistThread.Abort();
				persistThread.Join();
			}
			return true;
		}

		public bool IsOwnedByRoot(string fileName)		
		{
#if LINUX
			sockFileInfo = new Mono.Unix.UnixFileInfo(fileName);
		    sockFileOwner = sockFileInfo.OwnerUser;
			if(0==sockFileOwner.UserId)
			    return true;
		    else
		        return false;
#else
			return true;
#endif
		}


        private string GetDecryptedXml()
        {
            try
            {
				string fileName = userStore.GetPersistenceFilePath();
				string tempFile = fileName;
				int count = 0;
				if(!File.Exists(fileName))
				{
					while(true)
					{
						// check for tmp file
						if (File.Exists(tempFile+".tmp"))
						{
							if(IsOwnedByRoot(tempFile+".tmp"))
							{
								File.Move(tempFile+".tmp", fileName);
								break;
							}
							else
							{
								count++;
								tempFile = fileName + count.ToString();
							}
						}
						else
							return null;
                                
					}

					// delete tmp file if there
					if (File.Exists(tempFile+".tmp"))
					{
						if(IsOwnedByRoot(tempFile+".tmp"))
							File.Delete(tempFile+".tmp");
					}
				}

				byte[] baPasscode = null;
				if (null != m_baGeneratedKey)
					baPasscode =  m_baGeneratedKey;
				else
					baPasscode = CASACrypto.GetMasterPasscode(userStore.GetDesktopPasswd(),userStore.GetPasscodeByDesktopFilePath());

				if( null == baPasscode )
					return null;

				byte[] key = CASACrypto.GetKeySetFromFile(baPasscode,userStore.GetKeyFilePath());
                if( null == key )
                    return null;

                byte[] decryptedBuffer = CASACrypto.ReadFileAndDecryptData(key,fileName);
 
                if( null == decryptedBuffer )
                    return null; 
      
				string temp = Encoding.UTF8.GetString(decryptedBuffer, 0, decryptedBuffer.Length);

				return temp;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Unable to get persistent store");
            }
            return null;
        }
        /* This method, uses the key to decrypt the persistent store
         * and populates userStore with the persistent data.
         */
        private bool LoadPersistentStore()
        {
            try
            {
                string xpath = "";
                XmlDocument doc = new XmlDocument();

                string xmlToLoad = GetDecryptedXml();
                if(xmlToLoad != null)
                {
                    doc.LoadXml(xmlToLoad);

#if false
                    XmlTextWriter writer = new XmlTextWriter("/home/poorna/.miCASA.xml",null);
                    writer.Formatting = Formatting.Indented;
                    doc.Save(writer);
                    writer.Close();
#endif
                }
                else
                {
                    return false;
                }
                xpath = "//" + XmlConsts.miCASANode;
                XmlNode miCASANode = doc.SelectSingleNode(xpath);
                if(miCASANode != null)
                {
                    xpath = "descendant::" + XmlConsts.keyChainNode;
                    XmlNodeList keyChainNodeList = miCASANode.SelectNodes(xpath);
                    foreach(XmlNode node in keyChainNodeList)
                    {
                        XmlAttributeCollection attrColl = node.Attributes;
                        string keyChainId = (attrColl[XmlConsts.idAttr]).Value + "\0";
                        KeyChain keyChain = null;
     
                        if( userStore.CheckIfKeyChainExists(keyChainId) == false )
                        {
                            keyChain = new KeyChain(keyChainId);
                            userStore.AddKeyChain(keyChain);
                        }
                        else
                        {
                            keyChain = userStore.GetKeyChain(keyChainId);
                        }
                            xpath = "descendant::" + XmlConsts.secretNode;
                            XmlNodeList secretNodeList = node.SelectNodes(xpath);
                            foreach(XmlNode secretNode in secretNodeList)
                            {
                                attrColl = secretNode.Attributes;
                                string secretId = (attrColl[XmlConsts.idAttr]).Value + "\0";
                                xpath = "descendant::" + XmlConsts.valueNode;
                                Secret secret = new Secret(secretId);
                                if( keyChain.CheckIfSecretExists(secretId) == false)
                                {
                                    keyChain.AddSecret(secret);
                                    XmlNode secretValNode = (secretNode.SelectSingleNode(xpath));
                                    xpath = "descendant::" + XmlConsts.keyNode;

                                    XmlNodeList keyNodeList = secretValNode.SelectNodes(xpath);
								
                                    secret = keyChain.GetSecret(secretId);
                                    foreach(XmlNode keyNode in keyNodeList)
                                    {
                                        attrColl = keyNode.Attributes;
										string key;
										try 
										{
											key = (attrColl[XmlConsts.idAttr]).Value;
										}
										catch (Exception)
										{
											// LinkedKey node, continue
											continue;
										}
                                        xpath = "descendant::" + XmlConsts.keyValueNode;
                                        XmlNode keyValNode = keyNode.SelectSingleNode(xpath);
                                        string keyValue = keyValNode.InnerText;
                                        secret.SetKeyValue(key,keyValue);
										
										
										// add linked keys
										xpath = "descendant::" + XmlConsts.linkedKeyNode;
										XmlNodeList linkNodeList = keyNode.SelectNodes(xpath);
										foreach(XmlNode linkNode in linkNodeList)
										{
											// get TargetSecretID
											xpath = "descendant::" + XmlConsts.linkedTargetSecretNode;
											XmlNode targetSecretNode = linkNode.SelectSingleNode(xpath);
											string sSecretID = targetSecretNode.InnerText + "\0";

											// get TargetSecretKey
											xpath = "descendant::" + XmlConsts.linkedTargetKeyNode;
											XmlNode targetKeyNode = linkNode.SelectSingleNode(xpath);
											string sKeyID = targetKeyNode.InnerText;

											LinkedKeyInfo lki = new LinkedKeyInfo(sSecretID, sKeyID, true);
											KeyValue kv = secret.GetKeyValue(key);
											kv.AddLink(lki);											
										}
										
                                    }
                                }//if ends
                            }
 
                    }//end of traversing keyChainNodeList
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }

			// collect now to remove old data from memory
			GC.Collect();

            return true;
        }

		private void PersistStoreDelayThreadFn()
		{														
			Thread.Sleep(15000);	
			PersistStore();			
			persistThread = null;
		}

        private void PersistStoreThreadFn()
        {
            while(true)
            {								
				Thread.Sleep(persistThreadSleepTime);				
				PersistStore();
            }
        }
       
        /* Persists the store to an xml file.
         * TBD : Would we require any form of encoding?
         */

        internal void PersistStore()
        {
//            userStore.DumpSecretstore();
            try
            {

                MemoryStream ms1 = new MemoryStream();
                XmlTextWriter writer = new XmlTextWriter(ms1,null);
                writer.Formatting = Formatting.Indented;

                writer.WriteStartDocument();
                writer.WriteStartElement(XmlConsts.miCASANode);
                writer.WriteAttributeString(XmlConsts.versionAttr,"1.5");

                {
                    IDictionaryEnumerator iter = (IDictionaryEnumerator)userStore.GetKeyChainEnumerator();
                    char [] tmpId;
                    string sTmpId;
                    while( iter.MoveNext() )
                    {
                        KeyChain kc = (KeyChain)iter.Value;
                        writer.WriteStartElement(XmlConsts.keyChainNode);
                        string kcId = kc.GetKey();
                        tmpId = new char[kcId.Length-1];
                        for(int i = 0; i < kcId.Length-1; i++ )
                            tmpId[i] = kcId[i];
                        sTmpId = new string(tmpId);

                        writer.WriteAttributeString(XmlConsts.idAttr,sTmpId);
/* If we need to store time
                        writer.WriteStartElement(XmlConsts.timeNode);
                        writer.WriteAttributeString(XmlConsts.createdTimeNode,kc.CreatedTime.ToString());
                        writer.WriteAttributeString(XmlConsts.modifiedTimeNode,kc.ModifiedTime.ToString());
                        writer.WriteEndElement();
*/

                        IDictionaryEnumerator secIter = (IDictionaryEnumerator)(kc.GetAllSecrets());
                        while(secIter.MoveNext())
                        {
                            Secret secret = (Secret)secIter.Value;
                            writer.WriteStartElement(XmlConsts.secretNode);
                            string secretId = secret.GetKey();
                            tmpId = new char[secretId.Length-1];
                            for(int i = 0; i < secretId.Length-1; i++ )
                                tmpId[i] = secretId[i];
                            sTmpId = new string(tmpId);

                            writer.WriteAttributeString(XmlConsts.idAttr,sTmpId);
/* If we need to store time
                            writer.WriteStartElement(XmlConsts.timeNode);
                            writer.WriteAttributeString(XmlConsts.createdTimeNode,secret.CreatedTime.ToString());
                            writer.WriteAttributeString(XmlConsts.modifiedTimeNode,secret.ModifiedTime.ToString());
                            writer.WriteEndElement();
*/

                            writer.WriteStartElement(XmlConsts.valueNode); 
//                            byte[] byteArr = secret.GetValue();

                            IDictionaryEnumerator etor = (IDictionaryEnumerator)secret.GetKeyValueEnumerator();
                            while(etor.MoveNext())
                            {
                                string sKey = (string)etor.Key;
                                string value = secret.GetKeyValue(sKey).GetValue();
                                writer.WriteStartElement(XmlConsts.keyNode);
                                writer.WriteAttributeString(XmlConsts.idAttr, sKey);
                                writer.WriteStartElement(XmlConsts.keyValueNode);
                                writer.WriteString(value);
                                writer.WriteEndElement();
/* If we need to store time
                                writer.WriteStartElement(XmlConsts.timeNode);
                                writer.WriteAttributeString(XmlConsts.createdTimeNode,(secret.GetKeyValueCreatedTime(sKey)).ToString());
                                writer.WriteAttributeString(XmlConsts.modifiedTimeNode,(secret.GetKeyValueModifiedTime(sKey)).ToString());
                                writer.WriteEndElement();
*/
								// write all LinkKeys
								Hashtable htLinkedKeys = secret.GetLinkedKeys(sKey);
								if (htLinkedKeys != null)
								{
									IDictionaryEnumerator etorLinked = (IDictionaryEnumerator)htLinkedKeys.GetEnumerator();
									while(etorLinked.MoveNext())
									{
										LinkedKeyInfo lki = (LinkedKeyInfo)etorLinked.Value;
										writer.WriteStartElement(XmlConsts.linkedKeyNode);
										
										writer.WriteStartElement(XmlConsts.linkedTargetSecretNode);
										writer.WriteString(lki.GetLinkedSecretID().Substring(0, lki.GetLinkedSecretID().Length-1));
										writer.WriteEndElement();

										writer.WriteStartElement(XmlConsts.linkedTargetKeyNode);
										writer.WriteString(lki.GetLinkedKeyID());
										writer.WriteEndElement();
																				
										writer.WriteEndElement();
									}
								}

                                writer.WriteEndElement();
                            }

/*
                            char[] chArr = new char[byteArr.Length];
                            for(int z = 0; z < byteArr.Length; z++)
                                chArr[z] = (char)byteArr[z]; 

                            string stringToStore = new string(chArr);
                            writer.WriteString(stringToStore);
*/

                            writer.WriteEndElement(); //end of value node
                            writer.WriteEndElement();
                        }
                        writer.WriteEndElement(); //keychain
                    }
                } 
                writer.WriteEndElement(); //miCASA node
                writer.WriteEndDocument();
                writer.Flush();
                writer.Close();										

                //byte[] key = CASACrypto.GetKeySetFromFile(CASACrypto.GetMasterPasscode(userStore.GetDesktopPasswd(),userStore.GetPasscodeByDesktopFilePath()),userStore.GetKeyFilePath());
				byte[] key = CASACrypto.GetKeySetFromFile(m_baGeneratedKey, userStore.GetKeyFilePath());

				string fileName = userStore.GetPersistenceFilePath();
				string tempFile = fileName;
				int count=0;

				// rename existing file
				if(File.Exists(fileName))
				{
					while(true)
					{
						if (File.Exists(tempFile+".tmp"))
						{
							if(IsOwnedByRoot(tempFile+".tmp"))
							{
								File.Delete(tempFile+".tmp");
								break;
							}
							else
							{
								count++;
								tempFile = fileName + count.ToString();
							}
						}
						else
							break;
					}
					File.Move(fileName, tempFile+".tmp");
				}

				CASACrypto.EncryptDataAndWriteToFile(ms1.ToArray(),key,fileName);

				//remove temp
				if(File.Exists(tempFile+".tmp"))
				{
					if(IsOwnedByRoot(tempFile+".tmp"))
						File.Delete(tempFile+".tmp");
				}
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
			}
        }
    }
}