diff --git a/readline/completers/command-arguments.go b/readline/completers/command-arguments.go deleted file mode 100644 index 912ac7e..0000000 --- a/readline/completers/command-arguments.go +++ /dev/null @@ -1,23 +0,0 @@ -package completers - -import ( - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// CompleteCommandArguments - Completes all values for arguments to a command. -// Arguments here are different from command options (--option). -// Many categories, from multiple sources in multiple contexts -func completeCommandArguments(cmd *flags.Command, arg string, lastWord string) (prefix string, completions []*readline.CompletionGroup) { - - // the prefix is the last word, by default - prefix = lastWord - - // SEE completeOptionArguments FOR A WAY TO ADD COMPLETIONS TO SPECIFIC ARGUMENTS ------------------------------ - - // found := argumentByName(cmd, arg) - // var comp *readline.CompletionGroup // This group is used as a buffer, to add groups to final completions - - return -} diff --git a/readline/completers/env.go b/readline/completers/env.go deleted file mode 100644 index ae77aab..0000000 --- a/readline/completers/env.go +++ /dev/null @@ -1,124 +0,0 @@ -package completers - -import ( - "os" - "strings" - - "github.com/maxlandon/readline" -) - -// completeEnvironmentVariables - Returns all environment variables as suggestions -func completeEnvironmentVariables(lastWord string) (last string, completions []*readline.CompletionGroup) { - - // Check if last input is made of several different variables - allVars := strings.Split(lastWord, "/") - lastVar := allVars[len(allVars)-1] - - var evaluated = map[string]string{} - - grp := &readline.CompletionGroup{ - Name: "console OS environment", - MaxLength: 5, // Should be plenty enough - DisplayType: readline.TabDisplayGrid, - TrimSlash: true, // Some variables can be paths - } - - for k, v := range clientEnv { - if strings.HasPrefix("$"+k, lastVar) { - grp.Suggestions = append(grp.Suggestions, "$"+k+"/") - evaluated[k] = v - } - } - - completions = append(completions, grp) - - return lastVar, completions -} - -// clientEnv - Contains all OS environment variables, client-side. -// This is used for things like downloading/uploading files from localhost, etc., -// therefore we need completion and parsing stuff, sometimes. -var clientEnv = map[string]string{} - -// ParseEnvironmentVariables - Parses a line of input and replace detected environment variables with their values. -func ParseEnvironmentVariables(args []string) (processed []string, err error) { - - for _, arg := range args { - - // Anywhere a $ is assigned means there is an env variable - if strings.Contains(arg, "$") || strings.Contains(arg, "~") { - - //Split in case env is embedded in path - envArgs := strings.Split(arg, "/") - - // If its not a path - if len(envArgs) == 1 { - processed = append(processed, handleCuratedVar(arg)) - } - - // If len of the env var split is > 1, its a path - if len(envArgs) > 1 { - processed = append(processed, handleEmbeddedVar(arg)) - } - } else if arg != "" && arg != " " { - // Else, if arg is not an environment variable, return it as is - processed = append(processed, arg) - } - - } - return -} - -// handleCuratedVar - Replace an environment variable alone and without any undesired characters attached -func handleCuratedVar(arg string) (value string) { - if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" { - envVar := strings.TrimPrefix(arg, "$") - val, ok := clientEnv[envVar] - if !ok { - return envVar - } - return val - } - if arg != "" && arg == "~" { - return clientEnv["HOME"] - } - - return arg -} - -// handleEmbeddedVar - Replace an environment variable that is in the middle of a path, or other one-string combination -func handleEmbeddedVar(arg string) (value string) { - - envArgs := strings.Split(arg, "/") - var path []string - - for _, arg := range envArgs { - if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" { - envVar := strings.TrimPrefix(arg, "$") - val, ok := clientEnv[envVar] - if !ok { - // Err will be caught when command is ran anyway, or completion will stop... - path = append(path, arg) - } - path = append(path, val) - } else if arg != "" && arg == "~" { - path = append(path, clientEnv["HOME"]) - } else if arg != " " && arg != "" { - path = append(path, arg) - } - } - - return strings.Join(path, "/") -} - -// loadClientEnv - Loads all user environment variables -func loadClientEnv() error { - env := os.Environ() - - for _, kv := range env { - key := strings.Split(kv, "=")[0] - value := strings.Split(kv, "=")[1] - clientEnv[key] = value - } - return nil -} diff --git a/readline/completers/hint-completer.go b/readline/completers/hint-completer.go deleted file mode 100644 index e838961..0000000 --- a/readline/completers/hint-completer.go +++ /dev/null @@ -1,180 +0,0 @@ -package completers - -import ( - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// HintCompleter - Entrypoint to all hints in the Wiregost console -func (c *CommandCompleter) HintCompleter(line []rune, pos int) (hint []rune) { - - // Format and sanitize input - // @args => All items of the input line - // @last => The last word detected in input line as []rune - // @lastWord => The last word detected in input as string - args, last, lastWord := formatInput(line) - - // Detect base command automatically - var command = c.detectedCommand(args) - - // Menu hints (command line is empty, or nothing recognized) - if noCommandOrEmpty(args, last, command) { - hint = MenuHint(args, last) - } - - // Check environment variables - if envVarAsked(args, lastWord) { - return envVarHint(args, last) - } - - // Command Hint - if commandFound(command) { - - // Command hint by default (no space between cursor and last command character) - hint = CommandHint(command) - - // Check environment variables - if envVarAsked(args, lastWord) { - return envVarHint(args, last) - } - - // If options are asked for root command, return commpletions. - if len(command.Groups()) > 0 { - for _, grp := range command.Groups() { - if opt, yes := optionArgRequired(args, last, grp); yes { - hint = OptionArgumentHint(args, last, opt) - } - } - } - - // If command has args, hint for args - if arg, yes := commandArgumentRequired(lastWord, args, command); yes { - hint = []rune(CommandArgumentHints(args, last, command, arg)) - } - - // Brief subcommand hint - if lastIsSubCommand(lastWord, command) { - hint = []rune(commandHint + command.Find(string(last)).ShortDescription) - } - - // Handle subcommand if found - if sub, ok := subCommandFound(lastWord, args, command); ok { - return HandleSubcommandHints(args, last, sub) - } - - } - - // Handle system binaries, shell commands, etc... - if commandFoundInPath(args[0]) { - // hint = []rune(exeHint + util.ParseSummary(util.GetManPages(args[0]))) - } - - return -} - -// CommandHint - Yields the hint of a Wiregost command -func CommandHint(command *flags.Command) (hint []rune) { - return []rune(commandHint + command.ShortDescription) -} - -// HandleSubcommandHints - Handles hints for a subcommand and its arguments, options, etc. -func HandleSubcommandHints(args []string, last []rune, command *flags.Command) (hint []rune) { - - // If command has args, hint for args - if arg, yes := commandArgumentRequired(string(last), args, command); yes { - hint = []rune(CommandArgumentHints(args, last, command, arg)) - return - } - - // Environment variables - if envVarAsked(args, string(last)) { - hint = envVarHint(args, last) - } - - // If the last word in input is an option --name, yield argument hint if needed - if len(command.Groups()) > 0 { - for _, grp := range command.Groups() { - if opt, yes := optionArgRequired(args, last, grp); yes { - hint = OptionArgumentHint(args, last, opt) - } - } - } - - // If user asks for completions with "-" or "--". - // (Note: This takes precedence on any argument hints, as it is evaluated after them) - if commandOptionsAsked(args, string(last), command) { - return OptionHints(args, last, command) - } - - return -} - -// CommandArgumentHints - Yields hints for arguments to commands if they have some -func CommandArgumentHints(args []string, last []rune, command *flags.Command, arg string) (hint []rune) { - - found := argumentByName(command, arg) - // Base Hint is just a description of the command argument - hint = []rune(argHint + found.Description) - - return -} - -// ModuleOptionHints - If the option being set has a description, show it -func ModuleOptionHints(opt string) (hint []rune) { - return -} - -// OptionHints - Yields hints for proposed options lists/groups -func OptionHints(args []string, last []rune, command *flags.Command) (hint []rune) { - return -} - -// OptionArgumentHint - Yields hints for arguments to an option (generally the last word in input) -func OptionArgumentHint(args []string, last []rune, opt *flags.Option) (hint []rune) { - return []rune(valueHint + opt.Description) -} - -// MenuHint - Returns the Hint for a given menu context -func MenuHint(args []string, current []rune) (hint []rune) { - return -} - -// SpecialCommandHint - Shows hints for Wiregost special commands -func SpecialCommandHint(args []string, current []rune) (hint []rune) { - return current -} - -// envVarHint - Yields hints for environment variables -func envVarHint(args []string, last []rune) (hint []rune) { - // Trim last in case its a path with multiple vars - allVars := strings.Split(string(last), "/") - lastVar := allVars[len(allVars)-1] - - // Base hint - hint = []rune(envHint + lastVar) - - envVar := strings.TrimPrefix(lastVar, "$") - - if v, ok := clientEnv[envVar]; ok { - if v != "" { - hintStr := string(hint) + " => " + clientEnv[envVar] - hint = []rune(hintStr) - } - } - return -} - -var ( - // Hint signs - menuHint = readline.RESET + readline.DIM + readline.BOLD + " menu " + readline.RESET // Dim - envHint = readline.RESET + readline.GREEN + readline.BOLD + " env " + readline.RESET + readline.DIM + readline.GREEN // Green - commandHint = readline.RESET + readline.DIM + readline.BOLD + " command " + readline.RESET + readline.DIM + "\033[38;5;244m" // Cream - exeHint = readline.RESET + readline.DIM + readline.BOLD + " shell " + readline.RESET + readline.DIM // Dim - optionHint = "\033[38;5;222m" + readline.BOLD + " options " + readline.RESET + readline.DIM + "\033[38;5;222m" // Cream-Yellow - valueHint = readline.RESET + readline.DIM + readline.BOLD + " value " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream - // valueHint = "\033[38;5;217m" + readline.BOLD + " Value " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream - argHint = readline.DIM + "\033[38;5;217m" + readline.BOLD + " arg " + readline.RESET + readline.DIM + "\033[38;5;244m" // Pink-Cream -) diff --git a/readline/completers/local-filesystem.go b/readline/completers/local-filesystem.go deleted file mode 100644 index fcec4c5..0000000 --- a/readline/completers/local-filesystem.go +++ /dev/null @@ -1,205 +0,0 @@ -package completers - -import ( - "io/ioutil" - "os" - "os/user" - "path/filepath" - "strings" - - "github.com/maxlandon/readline" -) - -func completeLocalPath(last string) (string, *readline.CompletionGroup) { - - // Completions - completion := &readline.CompletionGroup{ - Name: "(console) local path", - MaxLength: 10, // The grid system is not yet able to roll on comps if > MaxLength - DisplayType: readline.TabDisplayGrid, - TrimSlash: true, - } - var suggestions []string - - // Any parsing error is silently ignored, for not messing the prompt - processedPath, _ := ParseEnvironmentVariables([]string{last}) - - // Check if processed input is empty - var inputPath string - if len(processedPath) == 1 { - inputPath = processedPath[0] - } - - // Add a slash if the raw input has one but not the processed input - if len(last) > 0 && last[len(last)-1] == '/' { - inputPath += "/" - } - - var linePath string // curated version of the inputPath - var absPath string // absolute path (excluding suffix) of the inputPath - var lastPath string // last directory in the input path - - if strings.HasSuffix(string(inputPath), "/") { - linePath = filepath.Dir(string(inputPath)) - absPath, _ = expand(string(linePath)) // Get absolute path - - } else if string(inputPath) == "" { - linePath = "." - absPath, _ = expand(string(linePath)) - } else { - linePath = filepath.Dir(string(inputPath)) - absPath, _ = expand(string(linePath)) // Get absolute path - lastPath = filepath.Base(string(inputPath)) // Save filter - } - - // 2) We take the absolute path we found, and get all dirs in it. - var dirs []string - files, _ := ioutil.ReadDir(absPath) - for _, file := range files { - if file.IsDir() { - dirs = append(dirs, file.Name()) - } - } - - switch lastPath { - case "": - for _, dir := range dirs { - if strings.HasPrefix(dir, lastPath) || lastPath == dir { - tokenized := addSpaceTokens(dir) - suggestions = append(suggestions, tokenized+"/") - } - } - default: - filtered := []string{} - for _, dir := range dirs { - if strings.HasPrefix(dir, lastPath) { - filtered = append(filtered, dir) - } - } - - for _, dir := range filtered { - if !hasPrefix([]rune(lastPath), []rune(dir)) || lastPath == dir { - tokenized := addSpaceTokens(dir) - suggestions = append(suggestions, tokenized+"/") - } - } - - } - - completion.Suggestions = suggestions - return string(lastPath), completion -} - -func addSpaceTokens(in string) (path string) { - items := strings.Split(in, " ") - for i := range items { - if len(items) == i+1 { // If last one, no char, add and return - path += items[i] - return - } - path += items[i] + "\\ " // By default add space char and roll - } - return -} - -func completeLocalPathAndFiles(last string) (string, *readline.CompletionGroup) { - - // Completions - completion := &readline.CompletionGroup{ - Name: "(console) local directory/files", - MaxLength: 10, // The grid system is not yet able to roll on comps if > MaxLength - DisplayType: readline.TabDisplayGrid, - TrimSlash: true, - } - var suggestions []string - - // Any parsing error is silently ignored, for not messing the prompt - processedPath, _ := ParseEnvironmentVariables([]string{last}) - - // Check if processed input is empty - var inputPath string - if len(processedPath) == 1 { - inputPath = processedPath[0] - } - - // Add a slash if the raw input has one but not the processed input - if len(last) > 0 && last[len(last)-1] == '/' { - inputPath += "/" - } - - var linePath string // curated version of the inputPath - var absPath string // absolute path (excluding suffix) of the inputPath - var lastPath string // last directory in the input path - - if strings.HasSuffix(string(inputPath), "/") { - linePath = filepath.Dir(string(inputPath)) // Trim the non needed slash - absPath, _ = expand(string(linePath)) // Get absolute path - - } else if string(inputPath) == "" { - linePath = "." - absPath, _ = expand(string(linePath)) - } else { - linePath = filepath.Dir(string(inputPath)) - absPath, _ = expand(string(linePath)) // Get absolute path - lastPath = filepath.Base(string(inputPath)) // Save filter - } - - // 2) We take the absolute path we found, and get all dirs in it. - var dirs []string - files, _ := ioutil.ReadDir(absPath) - for _, file := range files { - if file.IsDir() { - dirs = append(dirs, file.Name()) - } - } - - switch lastPath { - case "": - for _, file := range files { - if strings.HasPrefix(file.Name(), lastPath) || lastPath == file.Name() { - if file.IsDir() { - suggestions = append(suggestions, file.Name()+"/") - } else { - suggestions = append(suggestions, file.Name()) - } - } - } - default: - filtered := []os.FileInfo{} - for _, file := range files { - if strings.HasPrefix(file.Name(), lastPath) { - filtered = append(filtered, file) - } - } - - for _, file := range filtered { - if !hasPrefix([]rune(lastPath), []rune(file.Name())) || lastPath == file.Name() { - if file.IsDir() { - suggestions = append(suggestions, file.Name()+"/") - } else { - suggestions = append(suggestions, file.Name()) - } - } - } - - } - - completion.Suggestions = suggestions - return string(lastPath), completion -} - -// expand will expand a path with ~ to the $HOME of the current user. -func expand(path string) (string, error) { - if path == "" { - return path, nil - } - home := os.Getenv("HOME") - if home == "" { - usr, err := user.Current() - if err != nil { - return "", err - } - home = usr.HomeDir - } - return filepath.Abs(strings.Replace(path, "~", home, 1)) -} diff --git a/readline/completers/option-arguments.go b/readline/completers/option-arguments.go deleted file mode 100644 index 472c480..0000000 --- a/readline/completers/option-arguments.go +++ /dev/null @@ -1,77 +0,0 @@ -package completers - -import ( - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// completeOptionArguments - Completes all values for arguments to a command. Arguments here are different from command options (--option). -// Many categories, from multiple sources in multiple contexts -func completeOptionArguments(cmd *flags.Command, opt *flags.Option, lastWord string) (prefix string, completions []*readline.CompletionGroup) { - - // By default the last word is the prefix - prefix = lastWord - - var comp *readline.CompletionGroup // This group is used as a buffer, to add groups to final completions - - // First of all: some options, no matter their contexts and subject, have default values. - // When we have such an option, we don't bother analyzing context, we just build completions and return. - if len(opt.Choices) > 0 { - comp = &readline.CompletionGroup{ - Name: opt.ValueName, // Value names are specified in struct metadata fields - DisplayType: readline.TabDisplayGrid, - } - for _, choice := range opt.Choices { - if strings.HasPrefix(choice, lastWord) { - comp.Suggestions = append(comp.Suggestions, choice) - } - } - completions = append(completions, comp) - return - } - - // EXAMPLE OF COMPLETING ARGUMENTS BASED ON THEIR NAMES ----------------------------------------------------------------------- - // We have 3 words, potentially different, with which we can filter: - // - // 1) '--option-name' is the string typed as input. - // 2) 'OptionName' is the name of the struct/type for this option. - // 3) 'ValueName' is the name of the value we expect. - // var match = func(name string) bool { - // if strings.Contains(opt.Field().Name, name) { - // return true - // } - // return false - // } - // - // // Sessions - // if match("ImplantID") || match("SessionID") { - // completions = append(completions, sessionIDs(lastWord)) - // } - // - // // Any arguments with a path name. Often we "save" files that need paths, certificates, etc - // if match("Path") || match("Save") || match("Certificate") || match("PrivateKey") { - // switch cmd.Name { - // case constants.WebContentTypeStr, constants.WebUpdateStr, constants.AddWebContentStr, constants.RmWebContentStr: - // // Make an exception for WebPath option in websites commands. - // default: - // switch opt.ValueName { - // case "local-path", "path": - // prefix, comp = completeLocalPath(lastWord) - // completions = append(completions, comp) - // case "local-file", "file": - // prefix, comp = completeLocalPathAndFiles(lastWord) - // completions = append(completions, comp) - // default: - // // We always have a default searching for files, locally - // prefix, comp = completeLocalPathAndFiles(lastWord) - // completions = append(completions, comp) - // } - // - // } - // } - // - return -} diff --git a/readline/completers/patterns.go b/readline/completers/patterns.go deleted file mode 100644 index 6de587a..0000000 --- a/readline/completers/patterns.go +++ /dev/null @@ -1,548 +0,0 @@ -package completers - -import ( - "os/exec" - "reflect" - "strings" - "unicode" - - "github.com/jessevdk/go-flags" -) - -// These functions are just shorthands for checking various conditions on the input line. -// They make the main function more readable, which might be useful, should a logic error pop somewhere. - -// [ Parser Commands & Options ] -------------------------------------------------------------------------- -// ArgumentByName Get the name of a detected command's argument -func argumentByName(command *flags.Command, name string) *flags.Arg { - args := command.Args() - for _, arg := range args { - if arg.Name == name { - return arg - } - } - return nil -} - -// optionByName - Returns an option for a command or a subcommand, identified by name -func optionByName(cmd *flags.Command, option string) *flags.Option { - - if cmd == nil { - return nil - } - // Get all (root) option groups. - groups := cmd.Groups() - - // For each group, build completions - for _, grp := range groups { - // Add each option to completion group - for _, opt := range grp.Options() { - if opt.LongName == option { - return opt - } - } - } - return nil -} - -// [ Menus ] -------------------------------------------------------------------------------------------- -// Is the input line is either empty, or without any detected command ? -func noCommandOrEmpty(args []string, last []rune, command *flags.Command) bool { - if len(args) == 0 || len(args) == 1 && command == nil { - return true - } - return false -} - -// [ Commands ] ------------------------------------------------------------------------------------- -// detectedCommand - Returns the base command from parser if detected, depending on context -func (c *CommandCompleter) detectedCommand(args []string) (command *flags.Command) { - arg := strings.TrimSpace(args[0]) - command = c.parser.Find(arg) - return -} - -// is the command a special command, usually not handled by parser ? -func isSpecialCommand(args []string, command *flags.Command) bool { - - // If command is not nil, return - if command == nil { - // Shell - if args[0] == "!" { - return true - } - // Exit - if args[0] == "exit" { - return true - } - return false - } - return false -} - -// The commmand has been found -func commandFound(command *flags.Command) bool { - if command != nil { - return true - } - return false -} - -// Search for input in $PATH -func commandFoundInPath(input string) bool { - _, err := exec.LookPath(input) - if err != nil { - return false - } - return true -} - -// [ SubCommands ]------------------------------------------------------------------------------------- -// Does the command have subcommands ? -func hasSubCommands(command *flags.Command, args []string) bool { - if len(args) < 2 || command == nil { - return false - } - - if len(command.Commands()) != 0 { - return true - } - - return false -} - -// Does the input has a subcommand in it ? -func subCommandFound(lastWord string, raw []string, command *flags.Command) (sub *flags.Command, ok bool) { - // First, filter redundant spaces. This does not modify the actual line - args := ignoreRedundantSpaces(raw) - - if len(args) <= 1 || command == nil { - return nil, false - } - - sub = command.Find(args[1]) - if sub != nil { - return sub, true - } - - return nil, false -} - -// Is the last input PRECISELY a subcommand. This is used as a brief hint for the subcommand -func lastIsSubCommand(lastWord string, command *flags.Command) bool { - if sub := command.Find(lastWord); sub != nil { - return true - } - return false -} - -// [ Arguments ]------------------------------------------------------------------------------------- -// Does the command have arguments ? -func hasArgs(command *flags.Command) bool { - if len(command.Args()) != 0 { - return true - } - return false -} - -// commandArgumentRequired - Analyses input and sends back the next argument name to provide completion for -func commandArgumentRequired(lastWord string, raw []string, command *flags.Command) (name string, yes bool) { - - // First, filter redundant spaces. This does not modify the actual line - args := ignoreRedundantSpaces(raw) - - // Trim command and subcommand args - var remain []string - if args[0] == command.Name { - remain = args[1:] - } - if len(args) > 1 && args[1] == command.Name { - remain = args[2:] - } - - // The remain may include a "" as a last element, - // which we don't consider as a real remain, so we move it away - switch lastWord { - case "": - case command.Name: - return "", false - } - - // Trim all --option flags and their arguments if they have - remain = filterOptions(remain, command) - - // For each argument, check if needs completion. If not continue, if yes return. - // The arguments remainder is popped according to the number of values expected. - for i, arg := range command.Args() { - - // If it's required and has one argument, check filled. - if arg.Required == 1 && arg.RequiredMaximum == 1 { - - // If last word is the argument, and we are - // last arg in: line keep completing. - if len(remain) < 1 { - return arg.Name, true - } - - // If the we are still writing the argument - if len(remain) == 1 { - if lastWord != "" { - return arg.Name, true - } - } - - // If filed and we are not last arg, continue - if len(remain) > 1 && i < (len(command.Args())-1) { - remain = remain[1:] - continue - } - - continue - } - - // If we need more than one value and we knwo the maximum, - // either return or pop the remain. - if arg.Required > 0 && arg.RequiredMaximum > 1 { - // Pop the corresponding amount of arguments. - var found int - for i := 0; i < len(remain) && i < arg.RequiredMaximum; i++ { - remain = remain[1:] - found++ - } - - // If we still need values: - if len(remain) == 0 && found <= arg.RequiredMaximum { - if lastWord == "" { // We are done, no more completions. - break - } else { - return arg.Name, true - } - } - // Else go on with the next argument - continue - } - - // If has required arguments, with no limit of needs, return true - if arg.Required > 0 && arg.RequiredMaximum == -1 { - return arg.Name, true - } - - // Else, if no requirements and the command has subcommands, - // return so that we complete subcommands - if arg.Required == -1 && len(command.Commands()) > 0 { - continue - } - - // Else, return this argument - // NOTE: This block is after because we always use []type arguments - // AFTER individual argument fields. Thus blocks any args that have - // not been processed. - if arg.Required == -1 { - return arg.Name, true - } - } - - // Once we exited the loop, it means that none of the arguments require completion: - // They are all either optional, or fullfiled according to their required numbers. - // Thus we return none - return "", false -} - -// getRemainingArgs - Filters the input slice from commands and detected option:value pairs, and returns args -func getRemainingArgs(args []string, last []rune, command *flags.Command) (remain []string) { - - var input []string - // Clean subcommand name - if args[0] == command.Name && len(args) >= 2 { - input = args[1:] - } else if len(args) == 1 { - input = args - } - - // For each each argument - for i := 0; i < len(input); i++ { - // Check option prefix - if strings.HasPrefix(input[i], "-") || strings.HasPrefix(input[i], "--") { - // Clean it - cur := strings.TrimPrefix(input[i], "--") - cur = strings.TrimPrefix(cur, "-") - - // Check if option matches any command option - if opt := command.FindOptionByLongName(cur); opt != nil { - boolean := true - if opt.Field().Type == reflect.TypeOf(boolean) { - continue // If option is boolean, don't skip an argument - } - i++ // Else skip next arg in input - continue - } - } - - // Safety check - if input[i] == "" || input[i] == " " { - continue - } - - remain = append(remain, input[i]) - } - - return -} - -// [ Options ]------------------------------------------------------------------------------------- -// commandOptionsAsked - Does the user asks for options in a root command ? -func commandOptionsAsked(args []string, lastWord string, command *flags.Command) bool { - if len(args) >= 2 && (strings.HasPrefix(lastWord, "-") || strings.HasPrefix(lastWord, "--")) { - return true - } - return false -} - -// commandOptionsAsked - Does the user asks for options in a subcommand ? -func subCommandOptionsAsked(args []string, lastWord string, command *flags.Command) bool { - if len(args) > 2 && (strings.HasPrefix(lastWord, "-") || strings.HasPrefix(lastWord, "--")) { - return true - } - return false -} - -// Is the last input argument is a dash ? -func isOptionDash(args []string, last []rune) bool { - if len(args) > 2 && (strings.HasPrefix(string(last), "-") || strings.HasPrefix(string(last), "--")) { - return true - } - return false -} - -// optionIsAlreadySet - Detects in input if an option is already set -func optionIsAlreadySet(args []string, lastWord string, opt *flags.Option) bool { - return false -} - -// Check if option type allows for repetition -func optionNotRepeatable(opt *flags.Option) bool { - return true -} - -// [ Option Values ]------------------------------------------------------------------------------------- -// Is the last input word an option name (--option) ? -func optionArgRequired(args []string, last []rune, group *flags.Group) (opt *flags.Option, yes bool) { - - var lastItem string - var lastOption string - var option *flags.Option - - // If there is argument required we must have 1) command 2) --option inputs at least. - if len(args) <= 2 { - return nil, false - } - - // Check for last two arguments in input - if strings.HasPrefix(args[len(args)-2], "-") || strings.HasPrefix(args[len(args)-2], "--") { - - // Long opts - if strings.HasPrefix(args[len(args)-2], "--") { - lastOption = strings.TrimPrefix(args[len(args)-2], "--") - if opt := group.FindOptionByLongName(lastOption); opt != nil { - option = opt - } - - // Short opts - } else if strings.HasPrefix(args[len(args)-2], "-") { - lastOption = strings.TrimPrefix(args[len(args)-2], "-") - if len(lastOption) > 0 { - if opt := group.FindOptionByShortName(rune(lastOption[0])); opt != nil { - option = opt - } - } - } - - } - - // If option is found, and we still are in writing the argument - if (lastItem == "" && option != nil) || option != nil { - // Check if option is a boolean, if yes return false - boolean := true - if option.Field().Type == reflect.TypeOf(boolean) { - return nil, false - } - - return option, true - } - - // Check for previous argument - if lastItem != "" && option == nil { - if strings.HasPrefix(args[len(args)-2], "-") || strings.HasPrefix(args[len(args)-2], "--") { - - // Long opts - if strings.HasPrefix(args[len(args)-2], "--") { - lastOption = strings.TrimPrefix(args[len(args)-2], "--") - if opt := group.FindOptionByLongName(lastOption); opt != nil { - option = opt - return option, true - } - - // Short opts - } else if strings.HasPrefix(args[len(args)-2], "-") { - lastOption = strings.TrimPrefix(args[len(args)-2], "-") - if opt := group.FindOptionByShortName(rune(lastOption[0])); opt != nil { - option = opt - return option, true - } - } - } - } - - return nil, false -} - -// [ Other ]------------------------------------------------------------------------------------- -// Does the user asks for Environment variables ? -func envVarAsked(args []string, lastWord string) bool { - - // Check if the current word is an environment variable, or if the last part of it is a variable - if len(lastWord) > 1 && strings.HasPrefix(lastWord, "$") { - if strings.LastIndex(lastWord, "/") < strings.LastIndex(lastWord, "$") { - return true - } - return false - } - - // Check if env var is asked in a path or something - if len(lastWord) > 1 { - // If last is a path, it cannot be an env var anymore - if lastWord[len(lastWord)-1] == '/' { - return false - } - - if lastWord[len(lastWord)-1] == '$' { - return true - } - } - - // If we are at the beginning of an env var - if len(lastWord) > 0 && lastWord[len(lastWord)-1] == '$' { - return true - } - - return false -} - -// filterOptions - Check various elements of an option and return a list -func filterOptions(args []string, command *flags.Command) (processed []string) { - - for i := 0; i < len(args); i++ { - arg := args[i] - // --long-name options - if strings.HasPrefix(arg, "--") { - name := strings.TrimPrefix(arg, "--") - if opt := optionByName(command, name); opt != nil { - var boolean = true - if opt.Field().Type == reflect.TypeOf(boolean) { - continue - } - // Else skip the option argument (next item) - i++ - } - continue - } - // -s short options - if strings.HasPrefix(arg, "-") { - name := strings.TrimPrefix(arg, "-") - if opt := optionByName(command, name); opt != nil { - var boolean = true - if opt.Field().Type == reflect.TypeOf(boolean) { - continue - } - // Else skip the option argument (next item) - i++ - } - continue - } - processed = append(processed, arg) - } - - return -} - -// Other Functions -------------------------------------------------------------------------------------------------------------// - -// formatInput - Formats & sanitize the command line input -func formatInput(line []rune) (args []string, last []rune, lastWord string) { - args = strings.Split(string(line), " ") // The readline input as a []string - last = trimSpaceLeft([]rune(args[len(args)-1])) // The last char in input - lastWord = string(last) - return -} - -// FormatInput - Formats & sanitize the command line input -func formatInputHighlighter(line []rune) (args []string, last []rune, lastWord string) { - args = strings.SplitN(string(line), " ", -1) - last = trimSpaceLeft([]rune(args[len(args)-1])) // The last char in input - lastWord = string(last) - return -} - -// ignoreRedundantSpaces - We might have several spaces between each real arguments. -// However these indivual spaces are counted as args themselves. -// For each space arg found, verify that no space args follow, -// and if some are found, delete them. -func ignoreRedundantSpaces(raw []string) (args []string) { - - for i := 0; i < len(raw); i++ { - // Catch a space argument. - if raw[i] == "" { - // The arg evaulated is always kept, because we just adjusted - // the indexing to avoid the ones we don't need - // args = append(args, raw[i]) - - for y, next := range raw[i:] { - if next != "" { - i += y - 1 - break - } - // If we come to the end while not breaking - // we push the outer loop straight to the end. - if y == len(raw[i:])-1 { - i += y - } - } - } else { - // The arg evaulated is always kept, because we just adjusted - // the indexing to avoid the ones we don't need - args = append(args, raw[i]) - } - } - - return -} - -func trimSpaceLeft(in []rune) []rune { - firstIndex := len(in) - for i, r := range in { - if unicode.IsSpace(r) == false { - firstIndex = i - break - } - } - return in[firstIndex:] -} - -func equal(a, b []rune) bool { - if len(a) != len(b) { - return false - } - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - -func hasPrefix(r, prefix []rune) bool { - if len(r) < len(prefix) { - return false - } - return equal(r[:len(prefix)], prefix) -} diff --git a/readline/completers/syntax-highlighter.go b/readline/completers/syntax-highlighter.go deleted file mode 100644 index 8bce99f..0000000 --- a/readline/completers/syntax-highlighter.go +++ /dev/null @@ -1,151 +0,0 @@ -package completers - -import ( - "fmt" - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// SyntaxHighlighter - Entrypoint to all input syntax highlighting in the Wiregost console -func (c *CommandCompleter) SyntaxHighlighter(input []rune) (line string) { - - // Format and sanitize input - args, last, lastWord := formatInputHighlighter(input) - - // Remain is all arguments that have not been highlighted, we need it for completing long commands - var remain = args - - // Detect base command automatically - var command = c.detectedCommand(args) - - // Return input as is - if noCommandOrEmpty(remain, last, command) { - return string(input) - } - - // Base command - if commandFound(command) { - line, remain = highlightCommand(remain, command) - - // SubCommand - if sub, ok := subCommandFound(lastWord, args, command); ok { - line, remain = highlightSubCommand(line, remain, sub) - } - - } - - line = processRemain(line, remain) - - return -} - -func highlightCommand(args []string, command *flags.Command) (line string, remain []string) { - line = readline.BOLD + args[0] + readline.RESET + " " - remain = args[1:] - return -} - -func highlightSubCommand(input string, args []string, command *flags.Command) (line string, remain []string) { - line = input - line += readline.BOLD + args[0] + readline.RESET + " " - remain = args[1:] - return -} - -func processRemain(input string, remain []string) (line string) { - - // Check the last is not the last space in input - if len(remain) == 1 && remain[0] == " " { - return input - } - - line = input + strings.Join(remain, " ") - // line = processEnvVars(input, remain) - return -} - -// processEnvVars - Highlights environment variables. NOTE: Rewrite with logic from console/env.go -func processEnvVars(input string, remain []string) (line string) { - - var processed []string - - inputSlice := strings.Split(input, " ") - - // Check already processed input - for _, arg := range inputSlice { - if arg == "" || arg == " " { - continue - } - if strings.HasPrefix(arg, "$") { // It is an env var. - if args := strings.Split(arg, "/"); len(args) > 1 { - for _, a := range args { - fmt.Println(a) - if strings.HasPrefix(a, "$") && a != " " { // It is an env var. - processed = append(processed, "\033[38;5;108m"+readline.DIM+a+readline.RESET) - continue - } - } - } - processed = append(processed, "\033[38;5;108m"+readline.DIM+arg+readline.RESET) - continue - } - processed = append(processed, arg) - } - - // Check remaining args (non-processed) - for _, arg := range remain { - if arg == "" { - continue - } - if strings.HasPrefix(arg, "$") && arg != "$" { // It is an env var. - var full string - args := strings.Split(arg, "/") - if len(args) == 1 { - if strings.HasPrefix(args[0], "$") && args[0] != "" && args[0] != "$" { // It is an env var. - full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET - continue - } - } - if len(args) > 1 { - var counter int - for _, arg := range args { - // If var is an env var - if strings.HasPrefix(arg, "$") && arg != "" && arg != "$" { - if counter < len(args)-1 { - full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET + "/" - counter++ - continue - } - if counter == len(args)-1 { - full += "\033[38;5;108m" + readline.DIM + args[0] + readline.RESET - counter++ - continue - } - } - - // Else, if we are not at the end of array - if counter < len(args)-1 && arg != "" { - full += arg + "/" - counter++ - } - if counter == len(args)-1 { - full += arg - counter++ - } - } - } - // Else add first var - processed = append(processed, full) - } - } - - line = strings.Join(processed, " ") - - // Very important, keeps the line clear when erasing - // line += " " - - return -} diff --git a/readline/completers/tab-completer.go b/readline/completers/tab-completer.go deleted file mode 100644 index 1c9a942..0000000 --- a/readline/completers/tab-completer.go +++ /dev/null @@ -1,289 +0,0 @@ -package completers - -import ( - "errors" - "fmt" - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// CommandCompleter - A completer using a github.com/jessevdk/go-flags Command Parser, in order -// to build completions for commands, arguments, options and their arguments as well. -// This completer needs to be instantiated with its constructor, in order to ensure the parser is not nil. -type CommandCompleter struct { - parser *flags.Parser -} - -// NewCommandCompleter - Instantiate a new tab completer using a github.com/jessevdk/go-flags Command Parser. -func NewCommandCompleter(parser *flags.Parser) (completer *CommandCompleter, err error) { - if parser == nil { - return nil, errors.New("command completer was instantiated with a nil parser") - } - return &CommandCompleter{parser: parser}, nil -} - -// TabCompleter - A default tab completer working with a github.com/jessevdk/go-flags parser. -func (c *CommandCompleter) TabCompleter(line []rune, pos int, dtc readline.DelayedTabContext) (lastWord string, completions []*readline.CompletionGroup) { - - // Format and sanitize input - // @args => All items of the input line - // @last => The last word detected in input line as []rune - // @lastWord => The last word detected in input as string - args, last, lastWord := formatInput(line) - - // Detect base command automatically - var command = c.detectedCommand(args) - - // Propose commands - if noCommandOrEmpty(args, last, command) { - return c.completeMenuCommands(lastWord, pos) - } - - // Check environment variables - if envVarAsked(args, lastWord) { - completeEnvironmentVariables(lastWord) - } - - // Base command has been identified - if commandFound(command) { - // Check environment variables again - if envVarAsked(args, lastWord) { - return completeEnvironmentVariables(lastWord) - } - - // If options are asked for root command, return commpletions. - if len(command.Groups()) > 0 { - for _, grp := range command.Groups() { - if opt, yes := optionArgRequired(args, last, grp); yes { - return completeOptionArguments(command, opt, lastWord) - } - } - } - - // Then propose subcommands. We don't return from here, otherwise it always skips the next steps. - if hasSubCommands(command, args) { - completions = completeSubCommands(args, lastWord, command) - } - - // Handle subcommand if found (maybe we should rewrite this function and use it also for base command) - if sub, ok := subCommandFound(lastWord, args, command); ok { - return handleSubCommand(line, pos, sub) - } - - // If user asks for completions with "-" / "--", show command options. - // We ask this here, after having ensured there is no subcommand invoked. - // This prevails over command arguments, even if they are required. - if commandOptionsAsked(args, lastWord, command) { - return completeCommandOptions(args, lastWord, command) - } - - // Propose argument completion before anything, and if needed - if arg, yes := commandArgumentRequired(lastWord, args, command); yes { - return completeCommandArguments(command, arg, lastWord) - } - - } - - return -} - -// [ Main Completion Functions ] ----------------------------------------------------------------------------------------------------------------- - -// completeMenuCommands - Selects all commands available in a given context and returns them as suggestions -// Many categories, all from command parsers. -func (c *CommandCompleter) completeMenuCommands(lastWord string, pos int) (prefix string, completions []*readline.CompletionGroup) { - - prefix = lastWord // We only return the PREFIX for readline to correctly show suggestions. - - // Check their namespace (which should be their "group" (like utils, core, Jobs, etc)) - for _, cmd := range c.parser.Commands() { - // If command matches readline input - if strings.HasPrefix(cmd.Name, lastWord) { - // Check command group: add to existing group if found - var found bool - for _, grp := range completions { - if grp.Name == cmd.Aliases[0] { - found = true - grp.Suggestions = append(grp.Suggestions, cmd.Name) - grp.Descriptions[cmd.Name] = readline.Dim(cmd.ShortDescription) - } - } - // Add a new group if not found - if !found { - grp := &readline.CompletionGroup{ - Name: cmd.Aliases[0], - Suggestions: []string{cmd.Name}, - Descriptions: map[string]string{ - cmd.Name: readline.Dim(cmd.ShortDescription), - }, - } - completions = append(completions, grp) - } - } - } - - // Make adjustments to the CompletionGroup list: set maxlength depending on items, check descriptions, etc. - for _, grp := range completions { - // If the length of suggestions is too long and we have - // many groups, use grid display. - if len(completions) >= 10 && len(grp.Suggestions) >= 7 { - grp.DisplayType = readline.TabDisplayGrid - } else { - // By default, we use a map of command to descriptions - grp.DisplayType = readline.TabDisplayList - } - } - - return -} - -// completeSubCommands - Takes subcommands and gives them as suggestions -// One category, from one source (a parent command). -func completeSubCommands(args []string, lastWord string, command *flags.Command) (completions []*readline.CompletionGroup) { - - group := &readline.CompletionGroup{ - Name: command.Name, - Suggestions: []string{}, - Descriptions: map[string]string{}, - DisplayType: readline.TabDisplayList, - } - - for _, sub := range command.Commands() { - if strings.HasPrefix(sub.Name, lastWord) { - group.Suggestions = append(group.Suggestions, sub.Name) - group.Descriptions[sub.Name] = readline.DIM + sub.ShortDescription + readline.RESET - } - } - - completions = append(completions, group) - - return -} - -// handleSubCommand - Handles completion for subcommand options and arguments, + any option value related completion -// Many categories, from many sources: this function calls the same functions as the ones previously called for completing its parent command. -func handleSubCommand(line []rune, pos int, command *flags.Command) (lastWord string, completions []*readline.CompletionGroup) { - - args, last, lastWord := formatInput(line) - - // Check environment variables - if envVarAsked(args, lastWord) { - completeEnvironmentVariables(lastWord) - } - - // Check argument options - if len(command.Groups()) > 0 { - for _, grp := range command.Groups() { - if opt, yes := optionArgRequired(args, last, grp); yes { - return completeOptionArguments(command, opt, lastWord) - } - } - } - - // If user asks for completions with "-" or "--". This must take precedence on arguments. - if subCommandOptionsAsked(args, lastWord, command) { - return completeCommandOptions(args, lastWord, command) - } - - // If command has non-filled arguments, propose them first - if arg, yes := commandArgumentRequired(lastWord, args, command); yes { - return completeCommandArguments(command, arg, lastWord) - } - - return -} - -// completeCommandOptions - Yields completion for options of a command, with various decorators -// Many categories, from one source (a command) -func completeCommandOptions(args []string, lastWord string, cmd *flags.Command) (prefix string, completions []*readline.CompletionGroup) { - - prefix = lastWord // We only return the PREFIX for readline to correctly show suggestions. - - // Get all (root) option groups. - groups := cmd.Groups() - - // Append command options not gathered in groups - groups = append(groups, cmd.Group) - - // For each group, build completions - for _, grp := range groups { - - _, comp := completeOptionGroup(lastWord, grp, "") - - // No need to add empty groups, will screw the completion system. - if len(comp.Suggestions) > 0 { - completions = append(completions, comp) - } - } - - // Do the same for global options, which are not part of any group "per-se" - _, gcomp := completeOptionGroup(lastWord, cmd.Group, "global options") - if len(gcomp.Suggestions) > 0 { - completions = append(completions, gcomp) - } - - return -} - -// completeOptionGroup - make completions for a single group of options. Title is optional, not used if empty. -func completeOptionGroup(lastWord string, grp *flags.Group, title string) (prefix string, compGrp *readline.CompletionGroup) { - - compGrp = &readline.CompletionGroup{ - Name: grp.ShortDescription, - Descriptions: map[string]string{}, - DisplayType: readline.TabDisplayList, - Aliases: map[string]string{}, - } - - // An optional title for this comp group. - // Used by global flag options, added to all commands. - if title != "" { - compGrp.Name = title - } - - // Add each option to completion group - for _, opt := range grp.Options() { - - // Check if option is already set, next option if yes - // if optionNotRepeatable(opt) && optionIsAlreadySet(args, lastWord, opt) { - // continue - // } - - // Depending on the current last word, either build a group with option longs only, or with shorts - if strings.HasPrefix("--"+opt.LongName, lastWord) { - optName := "--" + opt.LongName - compGrp.Suggestions = append(compGrp.Suggestions, optName) - - // Add short if there is, and that the prefix is only one dash - if strings.HasPrefix("-", lastWord) { - if opt.ShortName != 0 { - compGrp.Aliases[optName] = "-" + string(opt.ShortName) - } - } - - // Option default value if any - var def string - if len(opt.Default) > 0 { - def = " (default:" - for _, d := range opt.Default { - def += " " + d + "," - } - def = strings.TrimSuffix(def, ",") - def += ")" - } - - desc := fmt.Sprintf(" -- %s%s%s", opt.Description, def, readline.RESET) - compGrp.Descriptions[optName] = desc - } - } - return -} - -// RecursiveGroupCompletion - Handles recursive completion for nested option groups -// Many categories, one source (a command's root option group). Called by the function just above. -func RecursiveGroupCompletion(args []string, last []rune, group *flags.Group) (lastWord string, completions []*readline.CompletionGroup) { - return -} diff --git a/readline/examples/arguments.go b/readline/examples/arguments.go deleted file mode 100644 index d976888..0000000 --- a/readline/examples/arguments.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -// This file defines a few argument choices for commands - -import ( - "github.com/jessevdk/go-flags" -) - -// Command/option argument choices -var ( - // Logs & components - logLevels = []string{"trace", "debug", "info", "warning", "error"} - loggers = []string{"client", "comm"} - - // Stages / Stagers - implantOS = []string{"windows", "linux", "darwin"} - implantArch = []string{"amd64", "x86"} - implantFmt = []string{"exe", "shared", "service", "shellcode"} - - stageListenerProtocols = []string{"tcp", "http", "https"} - - // MSF - msfStagerProtocols = []string{"tcp", "http", "https"} - msfTransformFormats = []string{ - "bash", - "c", - "csharp", - "dw", - "dword", - "hex", - "java", - "js_be", - "js_le", - "num", - "perl", - "pl", - "powershell", - "ps1", - "py", - "python", - "raw", - "rb", - "ruby", - "sh", - "vbapplication", - "vbscript", - } - - msfEncoders = []string{ - "x86/shikata_ga_nai", - "x64/xor_dynamic", - } - - msfPayloads = map[string][]string{ - "windows": windowsMsfPayloads, - "linux": linuxMsfPayloads, - "osx": osxMsfPayloads, - } - - // ValidPayloads - Valid payloads and OS combos - windowsMsfPayloads = []string{ - "meterpreter_reverse_http", - "meterpreter_reverse_https", - "meterpreter_reverse_tcp", - "meterpreter/reverse_tcp", - "meterpreter/reverse_http", - "meterpreter/reverse_https", - } - linuxMsfPayloads = []string{ - "meterpreter_reverse_http", - "meterpreter_reverse_https", - "meterpreter_reverse_tcp", - } - osxMsfPayloads = []string{ - "meterpreter_reverse_http", - "meterpreter_reverse_https", - "meterpreter_reverse_tcp", - } - - // Comm network protocols - portfwdProtocols = []string{"tcp", "udp"} - transportProtocols = []string{"tcp", "udp", "ip"} - applicationProtocols = []string{"http", "https", "mtls", "quic", "http3", "dns", "named_pipe"} -) - -// loadArgumentCompletions - Adds a bunch of choices for command arguments (and their completions.) -func loadArgumentCompletions(parser *flags.Parser) { - if parser == nil { - return - } - serverCompsAddtional(parser) -} - -// Additional completion mappings for command in the server context -func serverCompsAddtional(parser *flags.Parser) { - - // Stage options - g := parser.Find("generate") - g.FindOptionByLongName("os").Choices = implantOS - g.FindOptionByLongName("arch").Choices = implantArch - g.FindOptionByLongName("format").Choices = implantFmt - - // Stager options (mostly MSF) - gs := g.Find("stager") - gs.FindOptionByLongName("os").Choices = implantOS - gs.FindOptionByLongName("arch").Choices = implantArch - gs.FindOptionByLongName("protocol").Choices = msfStagerProtocols - gs.FindOptionByLongName("msf-format").Choices = msfTransformFormats -} diff --git a/readline/examples/commands.go b/readline/examples/commands.go deleted file mode 100644 index fcd9271..0000000 --- a/readline/examples/commands.go +++ /dev/null @@ -1,315 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "os/user" - "path/filepath" - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" -) - -// This file declares a go-flags parser and a few commands. - -var ( - // commandParser - The command parser used by the example console. - commandParser = flags.NewNamedParser("example", flags.IgnoreUnknown) -) - -func bindCommands() (err error) { - - // core console - // ---------------------------------------------------------------------------------------- - ex, err := commandParser.AddCommand("exit", // Command string - "Exit from the client/server console", // Description (completions, help usage) - "", // Long description - &Exit{}) // Command implementation - ex.Aliases = []string{"core"} - - cd, err := commandParser.AddCommand("cd", - "Change client working directory", - "", - &ChangeClientDirectory{}) - cd.Aliases = []string{"core"} - - ls, err := commandParser.AddCommand("ls", - "List directory contents", - "", - &ListClientDirectories{}) - ls.Aliases = []string{"core"} - - // Log - log, err := commandParser.AddCommand("log", - "Manage log levels of one or more components", - "", - &Log{}) - log.Aliases = []string{"core"} - - // Implant generation - // ---------------------------------------------------------------------------------------- - g, err := commandParser.AddCommand("generate", - "Configure and compile an implant (staged or stager)", - "", - &Generate{}) - g.Aliases = []string{"builds"} - g.SubcommandsOptional = true - - _, err = g.AddCommand("stager", - "Generate a stager shellcode payload using MSFVenom, (to file: --save, to stdout: --format", - "", - &GenerateStager{}) - - r, err := commandParser.AddCommand("regenerate", - "Recompile an implant by name, passed as argument (completed)", - "", - &Regenerate{}) - r.Aliases = []string{"builds"} - - // Add choices completions (and therefore completions) to some of these commands. - loadArgumentCompletions(commandParser) - - return -} - -// Exit - Kill the current client console -type Exit struct{} - -// Execute - Run -func (e *Exit) Execute(args []string) (err error) { - - reader := bufio.NewReader(os.Stdin) - fmt.Print("Confirm exit (Y/y): ") - text, _ := reader.ReadString('\n') - answer := strings.TrimSpace(text) - - if (answer == "Y") || (answer == "y") { - os.Exit(0) - } - - fmt.Println() - return -} - -// ChangeClientDirectory - Change the working directory of the client console -type ChangeClientDirectory struct { - Positional struct { - Path string `description:"local path" required:"1-1"` - } `positional-args:"yes" required:"yes"` -} - -// Execute - Handler for ChangeDirectory -func (cd *ChangeClientDirectory) Execute(args []string) (err error) { - - dir, err := expand(cd.Positional.Path) - - err = os.Chdir(dir) - if err != nil { - fmt.Printf(CommandError+"%s \n", err) - } else { - fmt.Printf(Info+"Changed directory to %s \n", dir) - } - - return -} - -// ListClientDirectories - List directory contents -type ListClientDirectories struct { - Positional struct { - Path []string `description:"local directory/file"` - } `positional-args:"yes"` -} - -// Execute - Command -func (ls *ListClientDirectories) Execute(args []string) error { - - base := []string{"ls", "--color", "-l"} - - if len(ls.Positional.Path) == 0 { - ls.Positional.Path = []string{"."} - } - - fullPaths := []string{} - for _, path := range ls.Positional.Path { - full, _ := expand(path) - fullPaths = append(fullPaths, full) - } - base = append(base, fullPaths...) - - // Print output - out, err := shellExec(base[0], base[1:]) - if err != nil { - fmt.Printf(CommandError+"%s \n", err.Error()) - return nil - } - - // Print output - fmt.Println(out) - - return nil -} - -// shellExec - Execute a program -func shellExec(executable string, args []string) (string, error) { - path, err := exec.LookPath(executable) - if err != nil { - return "", err - } - - cmd := exec.Command(path, args...) - - // Load OS environment - cmd.Env = os.Environ() - - out, err := cmd.CombinedOutput() - - if err != nil { - return "", err - } - return strings.Trim(string(out), "/"), nil -} - -// Generate - Configure and compile an implant -type Generate struct { - StageOptions // Command makes use of full stage options -} - -// StageOptions - All these options, regrouped by area, are used by any command that needs full -// configuration information for a stage Sliver implant. -type StageOptions struct { - // CoreOptions - All options about OS/arch, files to save, debugs, etc. - CoreOptions struct { - OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"` - Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"` - Format string `long:"format" short:"f" description:"output formats (exe, shared (DLL), service (see 'psexec' for info), shellcode (Windows only)" default:"exe" value-name:"stage formats"` - Profile string `long:"profile-name" description:"implant profile name to use (use with generate-profile)"` - Name string `long:"name" short:"N" description:"implant name to use (overrides random name generation)"` - Save string `long:"save" short:"s" description:"directory/file where to save binary"` - Debug bool `long:"debug" short:"d" description:"enable debug features (incompatible with obfuscation, and prevailing)"` - } `group:"core options"` - - // TransportOptions - All options pertaining to transport/RPC matters - TransportOptions struct { - MTLS []string `long:"mtls" short:"m" description:"mTLS C2 domain(s), comma-separated (ex: mtls://host:port)" env-delim:","` - DNS []string `long:"dns" short:"n" description:"DNS C2 domain(s), comma-separated (ex: dns://mydomain.com)" env-delim:","` - HTTP []string `long:"http" short:"h" description:"HTTP(S) C2 domain(s)" env-delim:","` - NamedPipe []string `long:"named-pipe" short:"p" description:"Named pipe transport strings, comma-separated" env-delim:","` - TCPPivot []string `long:"tcp-pivot" short:"i" description:"TCP pivot transport strings, comma-separated" env-delim:","` - Reconnect int `long:"reconnect" short:"j" description:"attempt to reconnect every n second(s)" default:"60"` - MaxErrors int `long:"max-errors" short:"k" description:"max number of transport errors" default:"10"` - } `group:"transport options"` - - // SecurityOptions - All security-oriented options like restrictions. - SecurityOptions struct { - LimitDatetime string `long:"limit-datetime" short:"w" description:"limit execution to before datetime"` - LimitDomain bool `long:"limit-domain-joined" short:"D" description:"limit execution to domain joined machines"` - LimitUsername string `long:"limit-username" short:"U" description:"limit execution to specified username"` - LimitHosname string `long:"limit-hostname" short:"H" description:"limit execution to specified hostname"` - LimitFileExits string `long:"limit-file-exists" short:"F" description:"limit execution to hosts with this file in the filesystem"` - } `group:"security options"` - - // EvasionOptions - All proactive security options (obfuscation, evasion, etc) - EvasionOptions struct { - Canary []string `long:"canary" short:"c" description:"DNS canary domain strings, comma-separated" env-delim:","` - SkipSymbols bool `long:"skip-obfuscation" short:"b" description:"skip binary/symbol obfuscation"` - Evasion bool `long:"evasion" short:"e" description:"enable evasion features"` - } `group:"evasion options"` -} - -// Execute - Configure and compile an implant -func (g *Generate) Execute(args []string) (err error) { - save := g.CoreOptions.Save - if save == "" { - save, _ = os.Getwd() - } - - fmt.Println("Executed 'generate' command. ") - return -} - -// Regenerate - Recompile an implant by name, passed as argument (completed) -type Regenerate struct { - Positional struct { - ImplantName string `description:"Name of Sliver implant to recompile" required:"1-1"` - } `positional-args:"yes" required:"yes"` - Save string `long:"save" short:"s" description:"Directory/file where to save binary"` -} - -// Execute - Recompile an implant with a given profile -func (r *Regenerate) Execute(args []string) (err error) { - fmt.Println("Executed 'regenerate' command. ") - return -} - -// GenerateStager - Generate a stager payload using MSFVenom -type GenerateStager struct { - PayloadOptions struct { - OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"` - Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"` - Format string `long:"msf-format" short:"f" description:"output format (MSF Venom formats). List is auto-completed" default:"raw" value-name:"MSF Venom transform formats"` - BadChars string `long:"badchars" short:"b" description:"bytes to exclude from stage shellcode"` - Save string `long:"save" short:"s" description:"directory to save the generated stager to"` - } `group:"payload options"` - TransportOptions struct { - LHost string `long:"lhost" short:"l" description:"listening host address" required:"true"` - LPort int `long:"lport" short:"p" description:"listening host port" default:"8443"` - Protocol string `long:"protocol" short:"P" description:"staging protocol (tcp/http/https)" default:"tcp" value-name:"stager protocol"` - } `group:"transport options"` -} - -// Execute - Generate a stager payload using MSFVenom -func (g *GenerateStager) Execute(args []string) (err error) { - fmt.Println("Executed 'generate stager' subcommand. ") - return -} - -// Log - Log management commands. Sets log level by default. -type Log struct { - Positional struct { - Level string `description:"log level to filter by" required:"1-1"` - Components []string `description:"components on which to apply log filter" required:"1"` - } `positional-args:"yes" required:"true"` -} - -// Execute - Set the log level of one or more components -func (l *Log) Execute(args []string) (err error) { - fmt.Println("Executed 'log' command. ") - return -} - -var ( - Info = fmt.Sprintf("%s[-]%s ", readline.BLUE, readline.RESET) - Warn = fmt.Sprintf("%s[!]%s ", readline.YELLOW, readline.RESET) - Error = fmt.Sprintf("%s[!]%s ", readline.RED, readline.RESET) - Success = fmt.Sprintf("%s[*]%s ", readline.GREEN, readline.RESET) - - Infof = fmt.Sprintf("%s[-] ", readline.BLUE) // Infof - formatted - Warnf = fmt.Sprintf("%s[!] ", readline.YELLOW) // Warnf - formatted - Errorf = fmt.Sprintf("%s[!] ", readline.RED) // Errorf - formatted - Sucessf = fmt.Sprintf("%s[*] ", readline.GREEN) // Sucessf - formatted - - RPCError = fmt.Sprintf("%s[RPC Error]%s ", readline.RED, readline.RESET) - CommandError = fmt.Sprintf("%s[Command Error]%s ", readline.RED, readline.RESET) - ParserError = fmt.Sprintf("%s[Parser Error]%s ", readline.RED, readline.RESET) - DBError = fmt.Sprintf("%s[DB Error]%s ", readline.RED, readline.RESET) -) - -// expand will expand a path with ~ to the $HOME of the current user. -func expand(path string) (string, error) { - if path == "" { - return path, nil - } - home := os.Getenv("HOME") - if home == "" { - usr, err := user.Current() - if err != nil { - return "", err - } - home = usr.HomeDir - } - return filepath.Abs(strings.Replace(path, "~", home, 1)) -} diff --git a/readline/examples/main.go b/readline/examples/main.go deleted file mode 100644 index 16fd4de..0000000 --- a/readline/examples/main.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/jessevdk/go-flags" - - "github.com/maxlandon/readline" - "github.com/maxlandon/readline/completers" -) - -// This file shows a typical way of using readline in a loop. - -func main() { - // Instantiate a console object - console := newConsole() - - // Bind commands to the console - bindCommands() - - // Setup the console completers, prompts, and input modes - console.setup() - - // Start the readline loop (blocking) - console.Start() -} - -// newConsole - Instantiates a new console with some default behavior. -// We modify/add elements of behavior later in setup. -func newConsole() *console { - console := &console{ - shell: readline.NewInstance(), - parser: commandParser, - } - return console -} - -// console - A simple console example. -type console struct { - shell *readline.Instance - parser *flags.Parser -} - -// setup - The console sets up various elements such as the completion system, hints, -// syntax highlighting, prompt system, commands binding, and client environment loading. -func (c *console) setup() (err error) { - - // Input mode & defails - c.shell.InputMode = readline.Vim // Could be readline.Emacs for emacs input mode. - c.shell.ShowVimMode = true - c.shell.VimModeColorize = true - - // Prompt: we want a two-line prompt, with a custom indicator after the Vim status - c.shell.SetPrompt("readline ") - c.shell.Multiline = true - c.shell.MultilinePrompt = " > " - - // Instantiate a default completer associated with the parser - // declared in commands.go, and embedded into the console struct. - // The error is muted, because we don't pass an nil parser, therefore no problems. - defaultCompleter, _ := completers.NewCommandCompleter(c.parser) - - // Register the completer for command/option completions, hints and syntax highlighting. - // The completer can handle all of them. - c.shell.TabCompleter = defaultCompleter.TabCompleter - c.shell.HintText = defaultCompleter.HintCompleter - c.shell.SyntaxHighlighter = defaultCompleter.SyntaxHighlighter - - // History: by default the history is in-memory, use it with Ctrl-R - - return -} - -// Start - The console has a working RPC connection: we setup all -// things pertaining to the console itself, and start the input loop. -func (c *console) Start() (err error) { - - // Setup console elements - err = c.setup() - if err != nil { - return fmt.Errorf("Console setup failed: %s", err) - } - - // Start input loop - for { - // Read input line - line, _ := c.Readline() - - // Split and sanitize input - sanitized, empty := sanitizeInput(line) - if empty { - continue - } - - // Process various tokens on input (environment variables, paths, etc.) - // These tokens will be expaneded by completers anyway, so this is not absolutely required. - envParsed, _ := completers.ParseEnvironmentVariables(sanitized) - - // Other types of tokens, needed by commands who expect a certain type - // of arguments, such as paths with spaces. - tokenParsed := c.parseTokens(envParsed) - - // Execute the command and print any errors - if _, parserErr := c.parser.ParseArgs(tokenParsed); parserErr != nil { - fmt.Println(readline.RED + "[Error] " + readline.RESET + parserErr.Error() + "\n") - } - } -} - -// Readline - Add an empty line between input line and command output. -func (c *console) Readline() (line string, err error) { - line, err = c.shell.Readline() - fmt.Println() - return -} - -// sanitizeInput - Trims spaces and other unwished elements from the input line. -func sanitizeInput(line string) (sanitized []string, empty bool) { - - // Assume the input is not empty - empty = false - - // Trim border spaces - trimmed := strings.TrimSpace(line) - if len(line) < 1 { - empty = true - return - } - unfiltered := strings.Split(trimmed, " ") - - // Catch any eventual empty items - for _, arg := range unfiltered { - if arg != "" { - sanitized = append(sanitized, arg) - } - } - return -} - -// parseTokens - Parse and process any special tokens that are not treated by environment-like parsers. -func (c *console) parseTokens(sanitized []string) (parsed []string) { - - // PATH SPACE TOKENS - // Catch \ tokens, which have been introduced in paths where some directories have spaces in name. - // For each of these splits, we concatenate them with the next string. - // This will also inspect commands/options/arguments, but there is no reason why a backlash should be present in them. - var pathAdjusted []string - var roll bool - var arg string - for i := range sanitized { - if strings.HasSuffix(sanitized[i], "\\") { - // If we find a suffix, replace with a space. Go on with next input - arg += strings.TrimSuffix(sanitized[i], "\\") + " " - roll = true - } else if roll { - // No suffix but part of previous input. Add it and go on. - arg += sanitized[i] - pathAdjusted = append(pathAdjusted, arg) - arg = "" - roll = false - } else { - // Default, we add our path and go on. - pathAdjusted = append(pathAdjusted, sanitized[i]) - } - } - parsed = pathAdjusted - - // Add new function here, act on parsed []string from now on, not sanitized - return -} diff --git a/testplugin/testplugin.go b/testplugin/testplugin.go deleted file mode 100644 index 2d8a41b..0000000 --- a/testplugin/testplugin.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - rt "github.com/arnodel/golua/runtime" -) - -func Loader(rtm *rt.Runtime) rt.Value { - return rt.StringValue("hello world!") -} diff --git a/testplugin/testplugin.so b/testplugin/testplugin.so deleted file mode 100644 index 3c83992..0000000 Binary files a/testplugin/testplugin.so and /dev/null differ