using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using Bitvise;
using Bitvise.FlowSshNet;


namespace FlowSshNet_Exec
{

    // ExitCodes

    public enum ExitCodes
    {
        Success,		// no exit code received from remote
        UsageError,
        SessionError,	// including connection error 
        FatalError,
    };


    // ExitCode

    public class ExitCode
    {
        public ExitCode() { m_exitCode = ExitCodes.Success; }
        public volatile ExitCodes m_exitCode;
    }


    // CmdLineParams

    public class CmdLineParams
    {
        public bool   m_bOk;
        public string m_sUserName;
        public string m_sKeyFile;
        public string m_sKeyPass;
        public string m_sPassword;
        public uint   m_nPort;
        public string m_sHost;

        public CmdLineParams(string[] args)
        {
            bool syntaxError = false;
            for (int i = 0; i < args.Length && !syntaxError; ++i)
	        {
                if (args[i].StartsWith("-user="))         m_sUserName = args[i].Substring(6);
                else if (args[i].StartsWith("-keyFile=")) m_sKeyFile = args[i].Substring(9);
                else if (args[i].StartsWith("-keyPass=")) m_sKeyPass = args[i].Substring(9);
                else if (args[i].StartsWith("-pw="))      m_sPassword = args[i].Substring(4);
                else if (args[i].StartsWith("-host="))    m_sHost = args[i].Substring(6);
                else if (args[i].StartsWith("-port="))    m_nPort = uint.Parse(args[i].Substring(6));
                else syntaxError = true;
	        }

            if (syntaxError)
            {
                Console.WriteLine("FlowSshNet sample Exec client.");
                Console.WriteLine("");
                Console.WriteLine("Parameters:");
                Console.WriteLine(" -host=...  (default localhost)");
                Console.WriteLine(" -port=...  (default 22)");
                Console.WriteLine(" -user=...");
                Console.WriteLine(" -keyFile=...");
                Console.WriteLine(" -keyPass=...");
                Console.WriteLine(" -pw=...");
            }
            m_bOk = !syntaxError;
        }
    }


    // ConsoleHelper

    static class ConsoleHelper
    {   
        public static void DisableEchoInput()
        {
            m_stdIn = GetStdHandle(STD_INPUT_HANDLE);

            m_restoreMode = false;
            if (GetFileType(m_stdIn) == FILE_TYPE_CHAR)
                if (m_restoreMode = GetConsoleMode(m_stdIn, ref m_originalMode))
                    m_restoreMode = SetConsoleMode(m_stdIn, m_originalMode & ~(ENABLE_ECHO_INPUT));
        }

        public static void RestoreEchoInput()
        {
            if (m_restoreMode) SetConsoleMode(m_stdIn, m_originalMode);
        }

        private static IntPtr m_stdIn;
        private static uint m_originalMode;
        private static bool m_restoreMode;

        private const uint STD_INPUT_HANDLE = 0xfffffff6;
        private const uint FILE_TYPE_CHAR = 2;
        private const uint ENABLE_ECHO_INPUT = 4;

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern IntPtr GetStdHandle(uint nStdHandle);

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern uint GetFileType(IntPtr hFile);

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern bool GetConsoleMode(IntPtr hConsoleHandle, ref uint dwMode);
    };


    // MyClient

    public class MyClient : ClientBase
    {
        public MyClient(ExitCode exitCode)
        {
            m_exitCode = exitCode;
            this.OnSshVersion += new SshVersionEventHandler(this.OnMySshVersion);
            this.OnHostKey += new HostKeyEventHandler(this.OnMyHostKey);
            this.OnFurtherAuth += new FurtherAuthEventHandler(this.OnMyFurtherAuth);
            this.OnPasswordChange += new PasswordChangeEventHandler(this.OnMyPasswordChange);
            this.OnBanner += new BannerEventHandler(this.OnMyBanner);
            this.OnDisconnect += new DisconnectEventHandler(this.OnMyDisconnect);
        }

        public void SetClientSettings(CmdLineParams cmdpar)
        {
            if (cmdpar.m_sUserName != null) SetUserName(cmdpar.m_sUserName);
            if (cmdpar.m_sKeyFile != null)  SetKeypair(new Keypair(File.ReadAllBytes(cmdpar.m_sKeyFile), cmdpar.m_sKeyPass));
            if (cmdpar.m_sPassword != null) SetPassword(cmdpar.m_sPassword);
            if (cmdpar.m_nPort > 0)         SetPort(cmdpar.m_nPort);
            if (cmdpar.m_sHost != null)     SetHost(cmdpar.m_sHost);
        }

        private void OnMySshVersion(object sender, string version)
        {
            Console.WriteLine("Server version: {0}", version);
        }

        private bool OnMyHostKey(object sender, PublicKey publicKey)
        {
            Console.WriteLine("Received the following host key:");
            Console.WriteLine("  MD5 Fingerprint: {0}", publicKey.GetMd5());
            Console.WriteLine("  Bubble-Babble: {0}", publicKey.GetBubbleBabble());
            Console.WriteLine("  SHA-256: {0}", publicKey.GetSha256());
            Console.Write("Type 'yes' to accept the key, any other input to exit: ");
            return Console.ReadLine() == "yes";
        }

        private bool OnMyFurtherAuth(object sender, FurtherAuth furtherAuth)
        {
            ConsoleHelper.DisableEchoInput();
            try
            {
                bool success = false;
                if (furtherAuth.IsPasswordRemaining())
                {	// user must provide a password
                    Console.Write("Password: ");

                    string password = Console.ReadLine();
                    if (password.Length > 0)
                    {
                        furtherAuth.SetPassword(password);
                        success = true;
                    }
                    Console.WriteLine("");
                }                
                return success;
            }
            finally { ConsoleHelper.RestoreEchoInput(); }
        }

        private bool OnMyPasswordChange(object sender, PasswordChange passwordChange)
        {
            ConsoleHelper.DisableEchoInput();
            try
            {
	            // Production code should remove volatile characters from prompt first.
	            Console.WriteLine(passwordChange.GetPrompt());
	            Console.WriteLine("New password: ");

                bool success = false;
	            string newPassword = Console.ReadLine();

	            if (newPassword.Length > 0)
	            {
		            Console.WriteLine("\nRepeat password: ");

                    string repeatPassword = Console.ReadLine();
		            if (newPassword == repeatPassword)
		            {
			            passwordChange.SetNewPassword(newPassword);
			            success = true;
		            }
		            else Console.WriteLine("\nPasswords do not match!");
	            }
	            Console.WriteLine("");
	            return success;
            }
            finally { ConsoleHelper.RestoreEchoInput(); }
        }

        void OnMyBanner(object sender, string banner)
	    {	// Production code should remove volatile characters from banner first.
		    Console.WriteLine("\n\nUser authentication banner: {0}", banner);
	    }

        void OnMyDisconnect(object sender, DisconnectReason reason, string desc)
	    {			
		    if (reason != DisconnectReason.ByClient)
		    {
			    Console.WriteLine("\n\nClient disconnecting: {0}", desc);
                if (m_exitCode.m_exitCode == ExitCodes.Success)
                    m_exitCode.m_exitCode = ExitCodes.SessionError;
		    }
	    }

        private ExitCode m_exitCode;
    }


    // Program

    class Program
    {
        static void OnUncaughtExceptionInEvent(object sender, bool fatal, System.Exception e)
        {
            Console.WriteLine("Error: " + e.ToString());
            System.Environment.Exit((int)ExitCodes.FatalError);
        }

        static int Main(string[] args)
        {
            CmdLineParams cmdpar = new CmdLineParams(args);
            if (!cmdpar.m_bOk)
                return (int)ExitCodes.UsageError;

            ExitCode exitCode = new ExitCode();
            try
            {
                // Register delegate for uncaught exceptions in user-defined events.
                // Example: If there is an uncaught exception in MyClient.OnMyHostKey, 
                //          then this is reported in OnUncaughtExceptionInEvent.
                SshNet.OnExceptionInEvent += new ExceptionInEventEventHandler(OnUncaughtExceptionInEvent);
                
                // For use in deployed applications where Bitvise SSH Client might not be installed, 
                // provide an activation code using SetActCode to ensure that FlowSsh does not  
                // display an evaluation dialog. On computers with Bitvise SSH Client, use of 
                // FlowSsh is permitted under the same terms as the Client; in this case, there 
                // will be no evaluation dialog, and SetActCode does not have to be called.
                //
                //SshNet.SetActCode("Enter Your Activation Code Here");

                // create client

                MyClient client = new MyClient(exitCode);
                // CHANGE APPLICATION NAME IN PRODUCTION CODE!
                client.SetAppName("FlowSshNet_PubKeyAuth 1.0");
                client.SetClientSettings(cmdpar);

                // connect client

                ProgressHandler progress = new ProgressHandler();
                client.Connect(progress);
                progress.WaitDone();

                if (!progress.Success)
                {	// Alternatively we could use a ProgressHandler::OnError
                    // event handler and do the logging there.
                    uint step = progress.GetTaskSpecificStep();
                    string auxInfo = progress.GetAuxInfo();
                    Console.WriteLine("{0}", ProgressHandler.DescribeConnectError(step, auxInfo));
                    return (int)ExitCodes.SessionError;
                }

                // Connected successfully
                Console.WriteLine("Connected successfully.");
                Console.WriteLine("This sample illustrates authentication only, so we exit here.");
            }
            catch (System.Exception e) // includes Bitvise.FlowSshNet.Exception
            {
                Console.WriteLine("{0}", e.Message);
                SshNet.Shutdown();  // drop connection
                return (int)ExitCodes.FatalError;
            }
            finally
            {
                SshNet.Shutdown();
            }
            return (int)exitCode.m_exitCode;
        }
    } // Program
}
