mirror of
https://github.com/Hilbis/Hilbish
synced 2025-04-14 17:43:22 +00:00
chore: delete some unneeded code and files
This commit is contained in:
parent
6cd294373c
commit
60edfc00ee
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
)
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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!")
|
||||
}
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user