mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af6e11adc | ||
|
|
a3a98858fd | ||
|
|
d0652f2fd3 | ||
|
|
936f067363 | ||
|
|
041c3c7048 | ||
|
|
b48f74af58 | ||
|
|
6ec8849b20 | ||
|
|
db7cf1ed62 | ||
|
|
16e1e77068 | ||
|
|
1cfa694aa2 | ||
|
|
5898877136 | ||
|
|
a7d8cac565 | ||
|
|
0e73afca47 | ||
|
|
432941d694 | ||
|
|
ac521bf53c | ||
|
|
9720c9f14f | ||
|
|
4a224fdb9c | ||
|
|
671eb4d777 | ||
|
|
16cf816610 | ||
|
|
879f576c6e | ||
|
|
2df78cdbf5 | ||
|
|
a05ffc0ae1 | ||
|
|
6b4b218f09 | ||
|
|
4d0d46afd6 | ||
|
|
f24c057503 | ||
|
|
7e3c1f1549 | ||
|
|
1d4c0ec8a5 | ||
|
|
92dda6aa4f | ||
|
|
9a605dfda6 | ||
|
|
e918885556 | ||
|
|
263702594f | ||
|
|
72b8dfb454 | ||
|
|
aa2383ac24 | ||
|
|
b6f0ea013d | ||
|
|
eb82e18a52 | ||
|
|
40ea51b685 | ||
|
|
1285b44c50 | ||
|
|
cb63a3378f | ||
|
|
4f90bfdcda | ||
|
|
5bb1dcc137 | ||
|
|
57c417c48f | ||
|
|
55fc59f6f5 | ||
|
|
332852d095 | ||
|
|
a903d2f596 | ||
|
|
4a4bb683e3 | ||
|
|
016299c756 | ||
|
|
075caaccda |
No files matched your search
@@ -39,10 +39,7 @@ Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](h
|
||||
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
|
||||
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/carapace-sh/carapace"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -76,6 +77,16 @@ type AliasFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// AliasArgument - An argument for an alias command.
|
||||
type AliasArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata.
|
||||
type AliasManifest struct {
|
||||
Name string `json:"name"`
|
||||
@@ -89,6 +100,7 @@ type AliasManifest struct {
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Arguments []*AliasArgument `json:"arguments"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
|
||||
// Build usage string including arguments
|
||||
usage := strings.Builder{}
|
||||
usage.WriteString(aliasManifest.CommandName)
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
usage.WriteString(" ")
|
||||
if arg.Optional {
|
||||
usage.WriteString("[")
|
||||
}
|
||||
usage.WriteString(strings.ToUpper(arg.Name))
|
||||
if arg.Optional {
|
||||
usage.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
// Build long help message
|
||||
longHelp := strings.Builder{}
|
||||
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
|
||||
longHelp.WriteString(usage.String())
|
||||
longHelp.WriteString("\n")
|
||||
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
|
||||
if len(aliasManifest.Help) > 0 {
|
||||
longHelp.WriteString(aliasManifest.Help)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
if len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString(aliasManifest.LongHelp)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if len(aliasManifest.Arguments) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
longHelp.WriteString("\n\t")
|
||||
optStr := ""
|
||||
if arg.Optional {
|
||||
optStr = "[OPTIONAL]"
|
||||
}
|
||||
aType := arg.Type
|
||||
if aType == "wstring" {
|
||||
aType = "string"
|
||||
}
|
||||
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
|
||||
}
|
||||
}
|
||||
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
|
||||
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
|
||||
|
||||
// for each alias command, add a new app command
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
|
||||
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
|
||||
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
|
||||
addAliasCmd := &cobra.Command{
|
||||
Use: aliasManifest.CommandName,
|
||||
Use: usage.String(),
|
||||
Short: helpMsg,
|
||||
Long: longHelpMsg,
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runAliasCommand(cmd, con, args)
|
||||
},
|
||||
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
|
||||
// Setup completions for alias arguments
|
||||
comps := carapace.Gen(addAliasCmd)
|
||||
makeAliasArgCompleter(aliasManifest, comps)
|
||||
|
||||
cmd.AddCommand(addAliasCmd)
|
||||
|
||||
// Have to use a global map here, as passing the aliasCmd
|
||||
@@ -589,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
|
||||
app.CommandFilterKey: strings.Join(all, ","),
|
||||
}
|
||||
}
|
||||
|
||||
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
|
||||
// It provides completion for:
|
||||
// 1. Positional arguments (before --)
|
||||
// 2. Flag-style arguments after -- (e.g., --target, --port)
|
||||
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
|
||||
if len(alias.Arguments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
var action carapace.Action
|
||||
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
action = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("alias data")
|
||||
default:
|
||||
action = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
|
||||
if arg.Optional {
|
||||
usage += " (optional)"
|
||||
}
|
||||
|
||||
actions = append(actions, action.Usage("%s", usage))
|
||||
}
|
||||
|
||||
comps.PositionalCompletion(actions...)
|
||||
|
||||
// Add dash completion for flag-style arguments after --
|
||||
// Pre-build the flag completions at registration time (not in a callback)
|
||||
flagCompletion := makeAliasFlagNameCompletion(alias)
|
||||
|
||||
// Build value completions for each argument type
|
||||
valueCompletions := make(map[string]carapace.Action)
|
||||
for _, arg := range alias.Arguments {
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
|
||||
case "bool":
|
||||
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
|
||||
default:
|
||||
valueCompletions[arg.Name] = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use DashAnyCompletion with a smart action that determines context
|
||||
comps.DashAnyCompletion(
|
||||
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
// If typing a flag (starts with -)
|
||||
if strings.HasPrefix(c.Value, "-") {
|
||||
return flagCompletion
|
||||
}
|
||||
|
||||
// If previous arg was a flag, complete its value
|
||||
if len(c.Args) > 0 {
|
||||
lastArg := c.Args[len(c.Args)-1]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
flagName := strings.TrimLeft(lastArg, "-")
|
||||
if action, ok := valueCompletions[flagName]; ok {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: show flag names
|
||||
return flagCompletion
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// makeAliasFlagNameCompletion creates completion for flag names
|
||||
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
|
||||
var results []string
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
flagName := fmt.Sprintf("--%s", arg.Name)
|
||||
desc := arg.Desc
|
||||
if arg.Optional {
|
||||
desc = fmt.Sprintf("[optional] %s", desc)
|
||||
}
|
||||
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
|
||||
results = append(results, flagName, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
|
||||
}
|
||||
@@ -274,19 +274,22 @@ func getInstalledPackageNames() []string {
|
||||
}
|
||||
|
||||
// This is a convenience function to get the names of the commands in the cache
|
||||
func getCommandsInCache() []string {
|
||||
func getCommandsInCache(armoryPK string) []string {
|
||||
commandNames := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if !slices.Contains(commandNames, command.CommandName) {
|
||||
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
|
||||
commandNames = append(commandNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
@@ -398,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
|
||||
var requestedPackageList []string
|
||||
if name == "all" {
|
||||
requestedPackageList = []string{}
|
||||
allCommands := getCommandsInCache()
|
||||
allCommands := getCommandsInCache(armoryPK)
|
||||
for _, cmdName := range allCommands {
|
||||
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
@@ -458,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(packageInstallList) == 0 && name == "all" {
|
||||
availableCommands := getCommandsInCache(armoryPK)
|
||||
if len(availableCommands) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
} else {
|
||||
con.PrintInfof("All available packages are already installed\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if len(packageInstallList) > 0 {
|
||||
for _, packageID := range packageInstallList {
|
||||
entry := packageCacheLookupByID(packageID)
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
killFlag, _ := cmd.Flags().GetString("kill")
|
||||
killAll, _ := cmd.Flags().GetBool("kill-all")
|
||||
interact, _ := cmd.Flags().GetString("interact")
|
||||
|
||||
// Handle kill
|
||||
if killFlag != "" {
|
||||
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if interact != "" {
|
||||
beacon, err := GetBeacon(con, interact)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.ActiveTarget.Set(nil, beacon)
|
||||
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
|
||||
return
|
||||
}
|
||||
|
||||
filter, _ := cmd.Flags().GetString("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
|
||||
|
||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("kill", "k", "", "kill the designated beacon")
|
||||
f.BoolP("kill-all", "K", false, "kill all beacons")
|
||||
f.StringP("interact", "i", "", "interact with a beacon")
|
||||
f.BoolP("force", "F", false, "force killing the beacon")
|
||||
|
||||
f.StringP("filter", "f", "", "filter beacons by substring")
|
||||
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["kill"] = BeaconIDCompleter(con)
|
||||
(*comp)["interact"] = BeaconIDCompleter(con)
|
||||
})
|
||||
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
|
||||
beaconsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a beacon",
|
||||
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// BeaconIDCompleter completes beacon IDs
|
||||
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("beacons")
|
||||
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(beaconCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func beaconCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -25,12 +25,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", false, "capture command output")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
|
||||
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
|
||||
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
|
||||
|
||||
@@ -49,6 +49,13 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
saveOutput, _ := cmd.Flags().GetBool("save")
|
||||
ppid, _ := cmd.Flags().GetUint32("ppid")
|
||||
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
|
||||
envPairs, _ := cmd.Flags().GetStringArray("env")
|
||||
envVars, err := parseEnvPairs(envPairs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
@@ -59,15 +66,19 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || hidden || ppid != 0 {
|
||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
return
|
||||
}
|
||||
if envInheritance || len(envVars) > 0 {
|
||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||
return
|
||||
}
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
@@ -81,12 +92,14 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
Env: envVars,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
@@ -179,6 +192,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseEnvPairs(envPairs []string) (map[string]string, error) {
|
||||
if len(envPairs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
envVars := make(map[string]string, len(envPairs))
|
||||
for _, pair := range envPairs {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if len(parts) != 2 || parts[0] == "" {
|
||||
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
|
||||
}
|
||||
envVars[parts[0]] = parts[1]
|
||||
}
|
||||
return envVars, nil
|
||||
}
|
||||
|
||||
func determineCommandName(command string) string {
|
||||
commandName := strings.ReplaceAll(command, "\\", "/")
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -43,8 +42,8 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/carapace-sh/carapace"
|
||||
appConsole "github.com/reeflective/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -121,6 +120,7 @@ type extensionArgument struct {
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
|
||||
@@ -689,94 +689,6 @@ func PrintExtOutput(extName string, commandName string, outputSchema *packages.O
|
||||
}
|
||||
}
|
||||
|
||||
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
|
||||
var err error
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// Parse BOF arguments from grumble
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
// If we have an extension that expects a single string, but more than one has been parsed, combine them
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
|
||||
// The loop below will only read the first element of args because ext.Arguments is 1
|
||||
args[0] = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
for _, arg := range ext.Arguments {
|
||||
// If we don't have any positional words left to consume,
|
||||
// add the remaining required extension arguments in the
|
||||
// error message.
|
||||
if len(args) == 0 {
|
||||
if !arg.Optional {
|
||||
missingRequiredArgs = append(missingRequiredArgs, "`"+arg.Name+"`")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Else pop a word from the list
|
||||
word := args[0]
|
||||
args = args[1:]
|
||||
|
||||
switch arg.Type {
|
||||
case "integer":
|
||||
fallthrough
|
||||
case "int":
|
||||
val, err := strconv.Atoi(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddInt(uint32(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "short":
|
||||
val, err := strconv.Atoi(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddShort(uint16(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "string":
|
||||
err = argsBuffer.AddString(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "wstring":
|
||||
err = argsBuffer.AddWString(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Adding support for filepaths so we can
|
||||
// send binary data like shellcodes to BOFs
|
||||
case "file":
|
||||
data, err := os.ReadFile(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if we have missing required arguments
|
||||
if len(missingRequiredArgs) > 0 {
|
||||
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
|
||||
}
|
||||
|
||||
parsedArgs, err := argsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedArgs, nil
|
||||
}
|
||||
|
||||
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := os.ReadFile(binPath)
|
||||
@@ -837,16 +749,27 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
|
||||
// makeExtensionArgCompleter builds the positional and dash arguments completer for the extension.
|
||||
// It provides completion for:
|
||||
// 1. Positional arguments (before --)
|
||||
// 2. Flag-style arguments after -- (e.g., --process, --shellcode)
|
||||
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var action carapace.Action
|
||||
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("extension data")
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
action = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("extension data")
|
||||
default:
|
||||
action = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
|
||||
@@ -858,6 +781,72 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
|
||||
}
|
||||
|
||||
comps.PositionalCompletion(actions...)
|
||||
|
||||
// Add dash completion for flag-style arguments after --
|
||||
// The extension arguments are parsed as flags (--name value) by argparser.go
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
// Pre-build the flag completions at registration time (not in a callback)
|
||||
// This ensures the values are captured correctly
|
||||
flagCompletion := makeExtensionFlagNameCompletion(extCmd)
|
||||
|
||||
// Build value completions for each argument type
|
||||
valueCompletions := make(map[string]carapace.Action)
|
||||
for _, arg := range extCmd.Arguments {
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
|
||||
default:
|
||||
valueCompletions[arg.Name] = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use DashAnyCompletion with a smart action that determines context
|
||||
comps.DashAnyCompletion(
|
||||
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
// If typing a flag (starts with -)
|
||||
if strings.HasPrefix(c.Value, "-") {
|
||||
return flagCompletion
|
||||
}
|
||||
|
||||
// If previous arg was a flag, complete its value
|
||||
if len(c.Args) > 0 {
|
||||
lastArg := c.Args[len(c.Args)-1]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
flagName := strings.TrimLeft(lastArg, "-")
|
||||
if action, ok := valueCompletions[flagName]; ok {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: show flag names
|
||||
return flagCompletion
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// makeExtensionFlagNameCompletion creates completion for flag names
|
||||
func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
|
||||
var results []string
|
||||
|
||||
for _, arg := range extCmd.Arguments {
|
||||
flagName := fmt.Sprintf("--%s", arg.Name)
|
||||
desc := arg.Desc
|
||||
if arg.Optional {
|
||||
desc = fmt.Sprintf("[optional] %s", desc)
|
||||
}
|
||||
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
|
||||
results = append(results, flagName, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("extension arguments")
|
||||
}
|
||||
|
||||
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
|
||||
|
||||
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Sliver Name",
|
||||
"Implant Name",
|
||||
"Domain",
|
||||
"Triggered",
|
||||
"First Trigger",
|
||||
|
||||
@@ -357,6 +357,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
registerImplantTargetFlagCompletions(cmd, con)
|
||||
}
|
||||
|
||||
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
|
||||
|
||||
@@ -22,7 +22,8 @@ var (
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateBeaconForm(cmd, con, args) {
|
||||
result, err := forms.GenerateBeaconForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateBeaconForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
|
||||
@@ -91,7 +91,8 @@ var (
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateForm(cmd, con, args) {
|
||||
result, err := forms.GenerateForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
@@ -118,14 +119,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
switch err {
|
||||
case ErrNoExternalBuilder:
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
case ErrNoValidBuilders:
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
default:
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -3,11 +3,13 @@ package generate
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GetSliverBinary - Get the binary of an implant based on it's profile.
|
||||
@@ -51,71 +53,112 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
return data, err
|
||||
}
|
||||
|
||||
func compilerTargets(con *console.SliverClient) (*clientpb.Compiler, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, fmt.Errorf("no compiler info")
|
||||
}
|
||||
return con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func appendUnique(values []string, value string) []string {
|
||||
for _, existing := range values {
|
||||
if existing == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func compilerArchValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func compilerOSValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func filterByPrefix(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func registerTargetFlagCompletion(cmd *cobra.Command, name string, valuesFn func(*clientpb.Compiler) []string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
values := valuesFn(compiler)
|
||||
return filterByPrefix(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func registerImplantTargetFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
registerTargetFlagCompletion(cmd, "os", compilerOSValues, con)
|
||||
registerTargetFlagCompletion(cmd, "arch", compilerArchValues, con)
|
||||
}
|
||||
|
||||
// FormatCompleter completes builds' architectures.
|
||||
func ArchCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("architectures")
|
||||
return carapace.ActionValues(compilerArchValues(compiler)...).Tag("architectures")
|
||||
})
|
||||
}
|
||||
|
||||
// FormatCompleter completes build operating systems.
|
||||
func OSCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("operating systems")
|
||||
return carapace.ActionValues(compilerOSValues(compiler)...).Tag("operating systems")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf(config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%s", config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
|
||||
fmt.Sprintf("%v", builds.Staged[sliverName]),
|
||||
})
|
||||
|
||||
@@ -68,6 +68,7 @@ var (
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
@@ -124,6 +125,22 @@ var (
|
||||
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
|
||||
}
|
||||
|
||||
executeHelp = `[[.Bold]]Command:[[.Normal]] execute <path to executable> [options] -- [args...]
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
|
||||
[*] Execute: /bin/bash [-c env]
|
||||
[*] Output:
|
||||
FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
[[.Bold]]About:[[.Normal]] Manage jobs/listeners.`
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
@@ -44,6 +45,17 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
|
||||
process, _ := cmd.Flags().GetString("process")
|
||||
config := con.GetActiveSessionConfig()
|
||||
|
||||
/* If the HTTP C2 Config name is not defined, then put in the default value
|
||||
This will have no effect on implants that do not use HTTP C2
|
||||
Also this should be overridden when the build info is pulled from the
|
||||
database, but if there is no build info and we have to create the build
|
||||
from scratch, we need to have something in here.
|
||||
*/
|
||||
if config.HTTPC2ConfigName == "" {
|
||||
config.HTTPC2ConfigName = consts.DefaultC2Profile
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["interact"] = SessionIDCompleter(con)
|
||||
(*comp)["kill"] = SessionIDCompleter(con)
|
||||
})
|
||||
registerSessionIDFlagCompletion(sessionsCmd, "interact", con)
|
||||
|
||||
sessionsPruneCmd := &cobra.Command{
|
||||
Use: consts.PruneStr,
|
||||
@@ -63,26 +64,78 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// SessionIDCompleter completes session IDs.
|
||||
func SessionIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, s := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, s.ID[:8])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("sessions")
|
||||
return carapace.ActionValuesDescribed(sessionCompletionPairs(con)...).Tag("sessions")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerSessionIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(sessionCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func sessionCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, s := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, s.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// SliverCommands returns all session control commands for the active target.
|
||||
func SliverCommands(con *console.SliverClient) []*cobra.Command {
|
||||
backgroundCmd := &cobra.Command{
|
||||
|
||||
@@ -27,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
carapace.Gen(useCmd).PositionalCompletion(BeaconAndSessionIDCompleter(con))
|
||||
registerUseIDCompletion(useCmd, con, true, true)
|
||||
|
||||
useSessionCmd := &cobra.Command{
|
||||
Use: consts.SessionsStr,
|
||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
},
|
||||
}
|
||||
carapace.Gen(useSessionCmd).PositionalCompletion(SessionIDCompleter(con))
|
||||
registerUseIDCompletion(useSessionCmd, con, true, false)
|
||||
useCmd.AddCommand(useSessionCmd)
|
||||
|
||||
useBeaconCmd := &cobra.Command{
|
||||
@@ -48,6 +50,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
},
|
||||
}
|
||||
carapace.Gen(useBeaconCmd).PositionalCompletion(beacons.BeaconIDCompleter(con))
|
||||
registerUseIDCompletion(useBeaconCmd, con, false, true)
|
||||
useCmd.AddCommand(useBeaconCmd)
|
||||
|
||||
return []*cobra.Command{useCmd}
|
||||
|
||||
@@ -39,6 +39,77 @@ import (
|
||||
|
||||
var ErrNoSelection = errors.New("no selection")
|
||||
|
||||
func registerUseIDCompletion(cmd *cobra.Command, con *console.SliverClient, includeSessions, includeBeacons bool) {
|
||||
if cmd == nil || cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) > 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
values := useCompletionValues(con, includeSessions, includeBeacons)
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func useCompletionValues(con *console.SliverClient, includeSessions, includeBeacons bool) []string {
|
||||
results := []string{}
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
if includeSessions {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, session := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", session.ActiveC2, session.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", session.Name, session.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", session.Username, session.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, fmt.Sprintf("%s\t%s", session.ID[:8], desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if includeBeacons {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, beacon := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", beacon.ActiveC2, beacon.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", beacon.Name, beacon.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", beacon.Username, beacon.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, fmt.Sprintf("%s\t%s", beacon.ID[:8], desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// UseCmd - Change the active session
|
||||
func UseCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var session *clientpb.Session
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package websites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -26,6 +30,41 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
carapace.Gen(websitesCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
|
||||
websitesListCmd := &cobra.Command{
|
||||
Use: consts.ListStr,
|
||||
Short: "List configured websites",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.ListStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsites(cmd, con, args)
|
||||
},
|
||||
}
|
||||
websitesCmd.AddCommand(websitesListCmd)
|
||||
|
||||
websitesShowCmd := &cobra.Command{
|
||||
Use: "show [name]",
|
||||
Short: "Show contents of a website",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsiteContent(args[0], con)
|
||||
},
|
||||
}
|
||||
carapace.Gen(websitesShowCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
websitesShowCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
out := []string{}
|
||||
for _, w := range ws.Websites {
|
||||
if strings.HasPrefix(w.Name, toComplete) {
|
||||
out = append(out, w.Name)
|
||||
}
|
||||
}
|
||||
return out, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
websitesCmd.AddCommand(websitesShowCmd)
|
||||
|
||||
websitesRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove an entire website and all of its contents",
|
||||
|
||||
@@ -121,9 +121,10 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
}
|
||||
|
||||
web.Contents[webpath] = &clientpb.WebContent{
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
Content: data,
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
OriginalFile: filepath.Base(contentPath),
|
||||
Content: data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ package websites
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -58,11 +57,14 @@ func ListWebsites(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
con.PrintInfof("No websites\n")
|
||||
return
|
||||
}
|
||||
con.Println("Websites")
|
||||
con.Println(strings.Repeat("=", len("Websites")))
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Objects"})
|
||||
for _, site := range websites.Websites {
|
||||
con.Printf("%s%s%s - %d page(s)\n", console.Bold, site.Name, console.Normal, len(site.Contents))
|
||||
tw.AppendRow(table.Row{site.Name, len(site.Contents)})
|
||||
}
|
||||
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// ListWebsiteContent - List the static contents of a website.
|
||||
@@ -91,6 +93,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
"Path",
|
||||
"Content-type",
|
||||
"Size",
|
||||
"Original File",
|
||||
"SHA256",
|
||||
})
|
||||
sortedContents := []*clientpb.WebContent{}
|
||||
for _, content := range web.Contents {
|
||||
@@ -104,6 +108,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
content.Path,
|
||||
content.ContentType,
|
||||
content.Size,
|
||||
content.OriginalFile,
|
||||
content.Sha256,
|
||||
})
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (con *SliverClient) allowServerRootCommands(args []string) ([]string, error) {
|
||||
con.applyCommandFallback(args)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (con *SliverClient) prepareCompletion(line []rune, cursor int) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommandForCompletion(menu, line, cursor)
|
||||
}
|
||||
|
||||
func (con *SliverClient) applyCommandFallback(args []string) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommand(con.App.ActiveMenu(), args)
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommand(menu *console.Menu, args []string) {
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Name() != consts.ImplantMenu {
|
||||
if menu.Command == nil && con.serverCmds != nil {
|
||||
menu.Command = con.serverCmds()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := firstNonFlagArg(args)
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommand(menu.Command, name) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommand(serverRoot, name) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommandForCompletion(menu *console.Menu, line []rune, cursor int) {
|
||||
if menu == nil || menu.Name() != consts.ImplantMenu {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
token := firstTokenFromLine(line, cursor)
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommandPrefix(menu.Command, token) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommandPrefix(serverRoot, token) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
hideFilteredCommands(menu, menu.Command)
|
||||
}
|
||||
|
||||
func firstNonFlagArg(args []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" {
|
||||
if i+1 < len(args) {
|
||||
return args[i+1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
continue
|
||||
}
|
||||
return arg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstTokenFromLine(line []rune, cursor int) string {
|
||||
if cursor > len(line) {
|
||||
cursor = len(line)
|
||||
}
|
||||
if cursor < 0 {
|
||||
cursor = 0
|
||||
}
|
||||
|
||||
pos := 0
|
||||
for pos < cursor && isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
start := pos
|
||||
for pos < cursor && !isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
if start == pos {
|
||||
return ""
|
||||
}
|
||||
return string(line[start:pos])
|
||||
}
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func rootHasCommand(root *cobra.Command, name string) bool {
|
||||
if root == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Name() == name || cmd.HasAlias(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func rootHasCommandPrefix(root *cobra.Command, prefix string) bool {
|
||||
if root == nil || prefix == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if strings.HasPrefix(cmd.Name(), prefix) {
|
||||
return true
|
||||
}
|
||||
for _, alias := range cmd.Aliases {
|
||||
if strings.HasPrefix(alias, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hideFilteredCommands(menu *console.Menu, root *cobra.Command) {
|
||||
if menu == nil || root == nil {
|
||||
return
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
if filters := menu.ActiveFiltersFor(cmd); len(filters) > 0 {
|
||||
cmd.Hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,8 @@ type SliverClient struct {
|
||||
Settings *assets.ClientSettings
|
||||
IsServer bool
|
||||
IsCLI bool
|
||||
serverCmds console.Commands
|
||||
sliverCmds console.Commands
|
||||
|
||||
jsonHandler slog.Handler
|
||||
printf func(format string, args ...any) (int, error)
|
||||
@@ -166,6 +168,8 @@ func NewConsole(isServer bool) *SliverClient {
|
||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||
con.Rpc = rpc
|
||||
con.IsCLI = !run
|
||||
con.serverCmds = serverCmds
|
||||
con.sliverCmds = sliverCmds
|
||||
|
||||
// The console application needs to query the terminal for cursor positions
|
||||
// when asynchronously printing logs (that is, when no command is running).
|
||||
@@ -184,6 +188,15 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
|
||||
sliver := con.App.Menu(consts.ImplantMenu)
|
||||
sliver.SetCommands(sliverCmds)
|
||||
|
||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
|
||||
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
|
||||
baseCompleter := shell.Completer
|
||||
shell.Completer = func(line []rune, cursor int) readline.Completions {
|
||||
con.prepareCompletion(line, cursor)
|
||||
return baseCompleter(line, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
@@ -250,6 +263,19 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return fmt.Errorf("parse error: %w", err)
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
con.serverCmds = serverCmds
|
||||
}
|
||||
if con.sliverCmds == nil {
|
||||
con.sliverCmds = sliverCmds
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu != nil {
|
||||
// Reset per line to avoid stale roots when rc scripts switch menus.
|
||||
menu.Command = nil
|
||||
}
|
||||
|
||||
for _, hook := range con.App.PreCmdRunLineHooks {
|
||||
args, err = hook(args)
|
||||
if err != nil {
|
||||
@@ -261,11 +287,15 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return nil
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu.Name() == consts.ImplantMenu {
|
||||
menu.Command = sliverCmds()
|
||||
} else {
|
||||
menu.Command = serverCmds()
|
||||
menu = con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return fmt.Errorf("no active menu")
|
||||
}
|
||||
if menu.Command == nil {
|
||||
con.setMenuCommand(menu, args)
|
||||
}
|
||||
if menu.Command == nil {
|
||||
return fmt.Errorf("no commands available")
|
||||
}
|
||||
|
||||
target, _, _ := menu.Command.Find(args)
|
||||
|
||||
+30
-34
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -23,62 +24,57 @@ type GenerateFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateForm prompts for core generate flags and returns the collected values.
|
||||
func GenerateForm() (*GenerateFormResult, error) {
|
||||
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
|
||||
result := &GenerateFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -26,62 +27,57 @@ type GenerateBeaconFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
|
||||
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
|
||||
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
|
||||
result := &GenerateBeaconFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type formatOption struct {
|
||||
format clientpb.OutputFormat
|
||||
label string
|
||||
value string
|
||||
}
|
||||
|
||||
type targetOptionBindings struct {
|
||||
Common *bool
|
||||
OS *string
|
||||
Arch *string
|
||||
}
|
||||
|
||||
var formatOptionOrder = []formatOption{
|
||||
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
|
||||
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
|
||||
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
|
||||
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
|
||||
}
|
||||
|
||||
func commonOSOptions() []huh.Option[string] {
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
}
|
||||
}
|
||||
|
||||
func commonArchOptions(goos string) []huh.Option[string] {
|
||||
switch goos {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
}
|
||||
if goos == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func firstOptionValue(options []huh.Option[string]) (string, bool) {
|
||||
if len(options) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return options[0].Value, true
|
||||
}
|
||||
|
||||
func maybeResetTargets(commonPlatformsOnly bool, lastCommonPlatformsOnly *bool, compilerTargets []*clientpb.CompilerTarget, goos, goarch, format *string) {
|
||||
if lastCommonPlatformsOnly == nil || goos == nil || goarch == nil || format == nil {
|
||||
return
|
||||
}
|
||||
if commonPlatformsOnly == *lastCommonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
*lastCommonPlatformsOnly = commonPlatformsOnly
|
||||
if commonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
|
||||
osOptions := commonOSOptions()
|
||||
if len(compilerTargets) > 0 {
|
||||
osOptions = osOptionsFromTargets(compilerTargets)
|
||||
if len(osOptions) == 0 {
|
||||
osOptions = commonOSOptions()
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(osOptions); ok {
|
||||
*goos = value
|
||||
}
|
||||
|
||||
archOptions := commonArchOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
archOptions = archOptionsFromTargets(compilerTargets, *goos)
|
||||
if len(archOptions) == 0 {
|
||||
archOptions = commonArchOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(archOptions); ok {
|
||||
*goarch = value
|
||||
}
|
||||
|
||||
formatOptions := commonFormatOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
formatOptions = formatOptionsFromTargets(compilerTargets, *goos, *goarch)
|
||||
if len(formatOptions) == 0 {
|
||||
formatOptions = commonFormatOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(formatOptions); ok {
|
||||
*format = value
|
||||
}
|
||||
}
|
||||
|
||||
func compilerTargetList(compiler *clientpb.Compiler) []*clientpb.CompilerTarget {
|
||||
if compiler == nil {
|
||||
return nil
|
||||
}
|
||||
targets := make([]*clientpb.CompilerTarget, 0, len(compiler.Targets)+len(compiler.UnsupportedTargets))
|
||||
targets = append(targets, compiler.Targets...)
|
||||
targets = append(targets, compiler.UnsupportedTargets...)
|
||||
return targets
|
||||
}
|
||||
|
||||
func osOptionsFromTargets(targets []*clientpb.CompilerTarget) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if target.GOOS == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOOS] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(osLabel(value), value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func archOptionsFromTargets(targets []*clientpb.CompilerTarget, goos string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if target.GOARCH == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOARCH] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(value, value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func formatOptionsFromTargets(targets []*clientpb.CompilerTarget, goos, goarch string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
available := map[clientpb.OutputFormat]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if goarch != "" && target.GOARCH != goarch {
|
||||
continue
|
||||
}
|
||||
available[target.Format] = struct{}{}
|
||||
}
|
||||
|
||||
options := make([]huh.Option[string], 0, len(available))
|
||||
for _, option := range formatOptionOrder {
|
||||
if _, ok := available[option.format]; ok {
|
||||
options = append(options, huh.NewOption(option.label, option.value))
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func osLabel(value string) string {
|
||||
switch value {
|
||||
case "windows":
|
||||
return "Windows"
|
||||
case "linux":
|
||||
return "Linux"
|
||||
case "darwin":
|
||||
return "macOS"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export type TutorialCardCardProps = {
|
||||
|
||||
export default function TutorialCard(props: TutorialCardCardProps) {
|
||||
return (
|
||||
<Card isFooterBlurred>
|
||||
<Card isFooterBlurred className="relative z-0">
|
||||
<CardHeader className="absolute z-10 top-1 flex-col items-end">
|
||||
<p className="text-md text-white/70 uppercase font-bold">
|
||||
{props.name}
|
||||
@@ -41,7 +41,7 @@ export default function TutorialCard(props: TutorialCardCardProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardFooter className="absolute bg-black/40 bottom-0 z-100 border-t-1 border-default-600 dark:border-default-100">
|
||||
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
|
||||
<div className="flex flex-row w-full items-center">
|
||||
<p
|
||||
className={
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
__IMPORTANT:__ MCP (Model Context Protocol) support is _experimental_ and NOT ALL FUNCTIONALITY IS SUPPORTED YET. Pull requests and testing feedback are welcome!
|
||||
|
||||
There are two main ways to connect an AI model to sliver using MCP: locally over stdio, or over a network using HTTP/SSE transports. The `sliver-client` supports both stdio and http/sse, but the `sliver-server` binary only supports HTTP/SSE. We recommend using the `sliver-client` and [Multiplayer Mode](/docs?name=Multi-player+Mode) to connect to a remote server with the stdio transport.
|
||||
|
||||
|
||||
## MCP Over STDIO
|
||||
|
||||
Example configurations for various AIs, you'll need to configure [Multiplayer Mode](/docs?name=Multi-player+Mode) first:
|
||||
|
||||
### OpenAI Codex
|
||||
|
||||
```
|
||||
[mcp_servers.sliver]
|
||||
args = ["mcp", "--config", "/path/to/multiplayer.cfg"]
|
||||
command = "/path/to/sliver-client"
|
||||
```
|
||||
|
||||
### Anthropic Claude Code
|
||||
|
||||
```
|
||||
claude mcp add sliver -- /path/to/sliver-client mcp --config /path/to/multiplayer.cfg
|
||||
```
|
||||
|
||||
## MCP Over HTTP/SSE
|
||||
|
||||
The MCP HTTP/SSE server can be started from either the `sliver-client` or `sliver-server`, however _where you run the command is where the server will start_ i.e., starting the HTTP/SSE MCP using `sliver-client` will turn that `sliver-client` binary into the MCP server (connected to whatever Sliver server you're currently connected to). Running the command from the `sliver-server` console will start a HTTP/SSE MCP server locally bound on the server.
|
||||
|
||||
```
|
||||
.------..------..------..------..------..------.
|
||||
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
|
||||
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
|
||||
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
|
||||
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
|
||||
`------'`------'`------'`------'`------'`------'
|
||||
|
||||
All hackers gain skulk
|
||||
[*] Server v1.6.4 - 4d0d46afd6e1cbcaafde92687144cb28694a68ce
|
||||
[*] Welcome to the sliver shell, please type 'help' for options
|
||||
sliver > mcp
|
||||
|
||||
Status: stopped
|
||||
Transport: sse
|
||||
Listen: 127.0.0.1:8080
|
||||
Endpoint: http://127.0.0.1:8080/sse
|
||||
|
||||
sliver > mcp start --transport http
|
||||
|
||||
[*] Starting MCP server (http) on 127.0.0.1:8080
|
||||
[*] Endpoint: http://127.0.0.1:8080/mcp
|
||||
```
|
||||
|
||||
You can use `mcp stop` to stop the server, see `mcp start --help` for additional options when starting the MCP server. Authentication is not yet supported for MCP over HTTP/SSE.
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Sliver implants support two types of connections, sessions and beacons.
|
||||
|
||||
Sessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
|
||||
|
||||
For example if configured to use `/admin` and `/demo` as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
When using Sliver during a live engagement, you’re going to need to use custom stagers, which are essentially a first binary or commandline that will retrieve and/or load Sliver into memory on your target system. Sliver can generate shellcode for your stager to execute by using the `profiles` command.
|
||||
|
||||
For this exercise, we will create a new beacon profile and prepare to stage it.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Pivots allow routing implant traffic through other implants. This can be useful in environments that don’t have any outbound access, but are reachable from other parts of the network that you have access to.
|
||||
|
||||
Sliver supports two types of pivots: TCP, which can be used on all operating systems, and named pipes, which are Windows-only.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Reactions are a basic way to automate tasks in the sliver console, they allow you to specify sliver commands to run on a list of events.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
The Sliver armory is used to install and maintain third party extensions and aliases within sliver. The full list of available extensions can be found at https://github.com/sliverarmory/armory, keep in mind this is community maintained so not all modules are necessarily up to date.
|
||||
|
||||
You can download all configured extensions/aliases using the armory command.
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\nSliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n Line truncated
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-Line truncated
|
||||
@@ -1,16 +1,18 @@
|
||||
module github.com/bishopfox/sliver
|
||||
|
||||
go 1.25.3
|
||||
go 1.25.5
|
||||
|
||||
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989
|
||||
|
||||
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260128035037-680800942770
|
||||
|
||||
require (
|
||||
filippo.io/age v1.3.1
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
@@ -33,13 +35,13 @@ require (
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/miekg/dns v1.1.69
|
||||
github.com/miekg/dns v1.1.70
|
||||
github.com/moloch--/asciicast v0.1.1
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
|
||||
github.com/moloch--/sgn v0.0.4
|
||||
github.com/ncruces/go-sqlite3 v0.30.4
|
||||
github.com/reeflective/console v0.1.25
|
||||
github.com/reeflective/readline v1.1.3
|
||||
github.com/reeflective/readline v1.1.4
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
@@ -53,29 +55,31 @@ require (
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
|
||||
golang.org/x/mod v0.31.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/term v0.38.0
|
||||
golang.org/x/text v0.32.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.0
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
||||
modernc.org/sqlite v1.39.0
|
||||
tailscale.com v1.90.6
|
||||
tailscale.com v1.92.5
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/hpke v0.4.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/creachadair/msync v0.7.1 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
@@ -97,8 +101,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/carapace-sh/carapace v1.9.0 // indirect
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
|
||||
github.com/carapace-sh/carapace v1.11.0
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
@@ -183,7 +187,7 @@ require (
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
|
||||
|
||||
@@ -19,8 +19,8 @@ github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qd
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw=
|
||||
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c h1:cFMQKxryHZ3Sddca8nqJ1RDjaUTW/+79lRKs+7508nk=
|
||||
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e h1:ytVmxGQuS7ELO/WpvH6iuY1hVcJ6iOTw3VLOOIFlo8o=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
|
||||
@@ -91,10 +91,10 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
|
||||
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
|
||||
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -148,6 +148,8 @@ github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNs
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
|
||||
github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008=
|
||||
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
||||
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
@@ -340,20 +342,22 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
|
||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
|
||||
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
|
||||
github.com/moloch--/console v0.0.0-20260128035037-680800942770 h1:JtfE7Od0O5ISD4b9r9AX7FTlAf8sT6O4qr5HV6Ey5U0=
|
||||
github.com/moloch--/console v0.0.0-20260128035037-680800942770/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
|
||||
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
|
||||
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d h1:9RbKockJl6ZdgoEh2QqU3An6Ij/NWZtRWS3J6yG0+qo=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989 h1:g0EfxAWzDGYarN0RAPibsjz2M/f0pAqYNiEpCPWRDok=
|
||||
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
|
||||
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
@@ -379,6 +383,8 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
@@ -389,12 +395,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
||||
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
|
||||
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
@@ -415,11 +419,9 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -534,8 +536,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
@@ -564,8 +566,8 @@ golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -584,8 +586,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -614,8 +616,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -648,8 +650,8 @@ gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgI
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
|
||||
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
|
||||
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
|
||||
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
@@ -682,5 +684,5 @@ mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.90.6 h1:EhYPiZP/xcLeinikaLA0kn4CQT3+z9SZ13IB/kzWhd4=
|
||||
tailscale.com v1.90.6/go.mod h1:+9EX6pOGCNa6pxCVRhhlJLy/qnkDzOplFYpeZyYlCT0=
|
||||
tailscale.com v1.92.5 h1:h88HqtGk8jd9umF7l6m/8+QK69dY9IVMIlkNK9XYpvQ=
|
||||
tailscale.com v1.92.5/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0=
|
||||
@@ -27,10 +27,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
@@ -164,10 +166,14 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dirList.Exists = true
|
||||
} else {
|
||||
dirList.Exists = false
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil && filter == "" {
|
||||
var rootDirInfo fs.FileInfo
|
||||
if rootDirEntry != nil {
|
||||
rootDirInfo, err = rootDirEntry.Info()
|
||||
}
|
||||
if err == nil && rootDirInfo != nil && filter == "" {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
@@ -191,6 +197,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
if err != nil {
|
||||
// Then this is a bad filter, and it will be a bad filter
|
||||
// on every iteration of the loop, so we might as well break now
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -247,22 +254,26 @@ func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
|
||||
// {{end}}
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
rootInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, err
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
@@ -1146,6 +1157,29 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exePath, execReq.Args...)
|
||||
if execReq.EnvInheritance || len(execReq.Env) > 0 {
|
||||
envVars := make(map[string]string)
|
||||
if execReq.EnvInheritance {
|
||||
for _, entry := range os.Environ() {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
envVars[parts[0]] = parts[1]
|
||||
} else if len(parts) == 1 {
|
||||
envVars[parts[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
maps.Copy(envVars, execReq.Env)
|
||||
keys := make([]string, 0, len(envVars))
|
||||
for key := range envVars {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
cmd.Env = make([]string, 0, len(envVars))
|
||||
for _, key := range keys {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, envVars[key]))
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
|
||||
+1027
-1007
File diff suppressed because it is too large.
Load diff
@@ -586,6 +586,9 @@ message WebContent {
|
||||
string ContentType = 4;
|
||||
uint64 Size = 5 [ jstype = JS_STRING ];
|
||||
|
||||
string OriginalFile = 6;
|
||||
string Sha256 = 7;
|
||||
|
||||
bytes Content = 9;
|
||||
}
|
||||
|
||||
@@ -1316,4 +1319,4 @@ message CertificateData {
|
||||
|
||||
message CertificateInfo {
|
||||
repeated CertificateData info = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
|
||||
+1090
-1063
File diff suppressed because it is too large.
Load diff
@@ -515,6 +515,9 @@ message ExecuteReq {
|
||||
bool Output = 3;
|
||||
string Stdout = 4;
|
||||
string Stderr = 5;
|
||||
bool EnvInheritance = 6;
|
||||
map<string, string> Env = 7;
|
||||
|
||||
uint32 PPid = 10;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
@@ -47,6 +48,13 @@ var (
|
||||
ErrInvalidDialect = errors.New("invalid SQL Dialect")
|
||||
|
||||
databaseConfigLog = log.NamedLogger("config", "database")
|
||||
|
||||
defaultSQLitePragmas = map[string]string{
|
||||
"journal_mode": "WAL", // reduce writer blocking, better concurrency
|
||||
"busy_timeout": "5000", // wait for locks instead of failing fast (ms)
|
||||
"synchronous": "NORMAL", // faster WAL syncs while retaining durability
|
||||
"temp_store": "MEMORY", // keep temp structures off disk for quicker startup
|
||||
}
|
||||
)
|
||||
|
||||
// GetDatabaseConfigPath - File path to config.json
|
||||
@@ -66,7 +74,8 @@ type DatabaseConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
|
||||
Params map[string]string `json:"params"`
|
||||
Params map[string]string `json:"params"`
|
||||
Pragmas map[string]string `json:"pragmas"`
|
||||
|
||||
MaxIdleConns int `json:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns"`
|
||||
@@ -80,7 +89,7 @@ func (c *DatabaseConfig) DSN() (string, error) {
|
||||
switch c.Dialect {
|
||||
case Sqlite:
|
||||
filePath := filepath.Join(assets.GetRootAppDir(), "sliver.db")
|
||||
params := encodeParams(c.Params)
|
||||
params := encodeSQLiteParams(c.Params, c.Pragmas)
|
||||
return fmt.Sprintf("file:%s?%s", filePath, params), nil
|
||||
case MySQL:
|
||||
user := url.QueryEscape(c.Username)
|
||||
@@ -112,6 +121,44 @@ func encodeParams(rawParams map[string]string) string {
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
func encodeSQLiteParams(rawParams map[string]string, pragmas map[string]string) string {
|
||||
if rawParams == nil {
|
||||
rawParams = map[string]string{}
|
||||
}
|
||||
params := url.Values{}
|
||||
|
||||
// Preserve any user-provided parameters first.
|
||||
for key, value := range rawParams {
|
||||
params.Add(key, value)
|
||||
}
|
||||
|
||||
// Apply safer defaults only when the user has not set any custom pragma.
|
||||
if _, ok := rawParams["_pragma"]; !ok {
|
||||
selectedPragmas := pragmas
|
||||
if len(selectedPragmas) == 0 {
|
||||
selectedPragmas = defaultSQLitePragmas
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(selectedPragmas))
|
||||
for key := range selectedPragmas {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
value := selectedPragmas[key]
|
||||
params.Add("_pragma", fmt.Sprintf("%s(%s)", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
// Encourage shared cache to play nicer with WAL and multiple connections.
|
||||
if _, ok := rawParams["cache"]; !ok {
|
||||
params.Add("cache", "shared")
|
||||
}
|
||||
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
func (c *DatabaseConfig) Save() error {
|
||||
configPath := GetDatabaseConfigPath()
|
||||
@@ -161,6 +208,8 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
config.MaxOpenConns = 1
|
||||
}
|
||||
|
||||
ensureSQLiteDefaults(config)
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -171,9 +220,29 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
func getDefaultDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
Dialect: Sqlite,
|
||||
Pragmas: defaultSQLitePragmas,
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
|
||||
LogLevel: "warn",
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSQLiteDefaults(c *DatabaseConfig) {
|
||||
if c.Dialect != Sqlite {
|
||||
return
|
||||
}
|
||||
if c.Params == nil {
|
||||
c.Params = map[string]string{}
|
||||
}
|
||||
if _, ok := c.Params["_pragma"]; ok {
|
||||
// User provided explicit pragmas; leave Pragmas untouched to avoid confusion.
|
||||
return
|
||||
}
|
||||
if len(c.Pragmas) == 0 {
|
||||
c.Pragmas = defaultSQLitePragmas
|
||||
}
|
||||
if _, ok := c.Params["cache"]; !ok {
|
||||
c.Params["cache"] = "shared"
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -789,8 +789,18 @@ func AddContent(pbWebContent *clientpb.WebContent, webContentDir string) (*clien
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
if pbWebContent.ContentType != "" {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
}
|
||||
if pbWebContent.Size != 0 {
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
}
|
||||
if pbWebContent.OriginalFile != "" {
|
||||
dbWebContent.OriginalFile = pbWebContent.OriginalFile
|
||||
}
|
||||
if pbWebContent.Sha256 != "" {
|
||||
dbWebContent.Sha256 = pbWebContent.Sha256
|
||||
}
|
||||
|
||||
dbModelWebContent := models.WebContentFromProtobuf(dbWebContent)
|
||||
err = Session().Save(&dbModelWebContent).Error
|
||||
|
||||
+20
-14
@@ -67,9 +67,11 @@ type WebContent struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
WebsiteID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
OriginalFile string
|
||||
Sha256 string
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook to automatically set values
|
||||
@@ -81,12 +83,14 @@ func (wc *WebContent) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
// ToProtobuf - Converts to protobuf object
|
||||
func (wc *WebContent) ToProtobuf(content *[]byte) *clientpb.WebContent {
|
||||
return &clientpb.WebContent{
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
Content: *content,
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
OriginalFile: wc.OriginalFile,
|
||||
Sha256: wc.Sha256,
|
||||
Content: *content,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +99,12 @@ func WebContentFromProtobuf(pbWebContent *clientpb.WebContent) WebContent {
|
||||
websiteUUID, _ := uuid.FromString(pbWebContent.WebsiteID)
|
||||
|
||||
return WebContent{
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
OriginalFile: pbWebContent.OriginalFile,
|
||||
Sha256: pbWebContent.Sha256,
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,12 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
|
||||
if err != nil {
|
||||
req.Config.Format = clientpb.OutputFormat_SHELLCODE
|
||||
req.Config.ObfuscateSymbols = false
|
||||
req.Config.IsShellcode = true
|
||||
req.Config.IsSharedLib = false
|
||||
req.Config.TemplateName = "sliver"
|
||||
if len(req.Config.Exports) == 0 {
|
||||
req.Config.Exports = []string{"StartW"}
|
||||
}
|
||||
build, err := generate.GenerateConfig(name, req.Config)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
|
||||
@@ -19,6 +19,8 @@ package website
|
||||
*/
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -87,14 +89,30 @@ func AddContent(name string, pbWebContent *clientpb.WebContent) error {
|
||||
pbWebContent.WebsiteID = website.ID
|
||||
}
|
||||
|
||||
if pbWebContent.Size == 0 && len(pbWebContent.Content) > 0 {
|
||||
pbWebContent.Size = uint64(len(pbWebContent.Content))
|
||||
}
|
||||
if pbWebContent.OriginalFile == "" {
|
||||
pbWebContent.OriginalFile = filepath.Base(pbWebContent.Path)
|
||||
}
|
||||
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
sha := sha256.Sum256(pbWebContent.Content)
|
||||
pbWebContent.Sha256 = hex.EncodeToString(sha[:])
|
||||
}
|
||||
|
||||
webContent, err := db.AddContent(pbWebContent, webContentDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write content to disk
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
// Write content to disk when provided (metadata-only updates skip disk writes)
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContent - Remove website content for a path
|
||||
|
||||
+879
-1850
File diff suppressed because it is too large.
Load diff
+796
-1572
File diff suppressed because it is too large.
Load diff
+5
-1
@@ -111,8 +111,12 @@ func (t TokenSlice) WordbreakPrefix() string {
|
||||
break
|
||||
}
|
||||
|
||||
if token.Type == WORDBREAK_TOKEN {
|
||||
if !found && token.Type == WORDBREAK_TOKEN {
|
||||
found = true
|
||||
if token.Value == "@" {
|
||||
// Seems although `@` is a wordbreak, it weirdly is not part of the prefix.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package shlex
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const BASH_WORDBREAKS = " \t\r\n" + `"'><=;|&(:`
|
||||
const BASH_WORDBREAKS = " \t\r\n" + `"'@><=;|&(:`
|
||||
|
||||
type WordbreakType int
|
||||
|
||||
|
||||
+27
-3
@@ -437,6 +437,15 @@ func (a Action) Timeout(d time.Duration, alternative Action) Action {
|
||||
})
|
||||
}
|
||||
|
||||
// Unique ensures the Action only contains unique values.
|
||||
func (a Action) Unique() Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
invoked := a.Invoke(c)
|
||||
invoked.action.rawValues = invoked.action.rawValues.Unique()
|
||||
return invoked.ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// UniqueList wraps the Action in an ActionMultiParts with given divider.
|
||||
func (a Action) UniqueList(divider string) Action {
|
||||
return ActionMultiParts(divider, func(c Context) Action {
|
||||
@@ -513,9 +522,14 @@ func (a Action) UidF(f func(s string, uc uid.Context) (*url.URL, error)) Action
|
||||
}
|
||||
|
||||
// Usage sets the usage.
|
||||
func (a Action) Usage(usage string, args ...interface{}) Action {
|
||||
func (a Action) Usage(usage string, args ...any) Action {
|
||||
return a.UsageF(func() string {
|
||||
return fmt.Sprintf(usage, args...)
|
||||
if len(args) == 0 {
|
||||
return usage
|
||||
}
|
||||
return func(u string, a ...any) string { // avoids failed inferred printf check: non-constant format string
|
||||
return fmt.Sprintf(u, a...)
|
||||
}(usage, args...)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -548,6 +562,9 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
|
||||
values.Set(opts[i], opts[i+1])
|
||||
}
|
||||
}
|
||||
if c.Value != "" {
|
||||
values.Set("C_VALUE", c.Value)
|
||||
}
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
a.meta.Queries.Add(query.String())
|
||||
@@ -558,10 +575,17 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
|
||||
// QueryF TODO experimental
|
||||
func (a Action) QueryF(f func(s string, uc uid.Context) (*url.URL, error)) Action { // TODO remove the string
|
||||
return ActionCallback(func(c Context) Action {
|
||||
query, err := f(c.Value, c)
|
||||
query, err := f("", c) // TODO string parameter from uid isn't really needed here
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
|
||||
if c.Value != "" {
|
||||
values := query.Query()
|
||||
values.Set("C_VALUE", c.Value)
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
|
||||
a.meta.Queries.Add(query.String())
|
||||
return a
|
||||
})
|
||||
|
||||
+3
-4
@@ -2,6 +2,7 @@
|
||||
package carapace
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/shell"
|
||||
@@ -79,9 +80,7 @@ func (c Carapace) FlagCompletion(actions ActionMap) {
|
||||
if e.flag == nil {
|
||||
e.flag = actions
|
||||
} else {
|
||||
for name, action := range actions {
|
||||
e.flag[name] = action
|
||||
}
|
||||
maps.Copy(e.flag, actions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +125,7 @@ func IsCallback() bool {
|
||||
// func TestCarapace(t *testing.T) {
|
||||
// carapace.Test(t)
|
||||
// }
|
||||
func Test(t interface{ Error(args ...interface{}) }) {
|
||||
func Test(t interface{ Error(args ...any) }) {
|
||||
for _, e := range storage.check() {
|
||||
t.Error(e)
|
||||
}
|
||||
|
||||
+20
-32
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/common"
|
||||
@@ -135,6 +136,10 @@ func ActionExecute(cmd *cobra.Command) Action {
|
||||
// ActionDirectories completes directories.
|
||||
func ActionDirectories() Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
cDir, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
return actionPath([]string{""}, true).
|
||||
MultiParts("/").
|
||||
StyleF(style.ForPath).
|
||||
@@ -145,19 +150,17 @@ func ActionDirectories() Action {
|
||||
}
|
||||
return url.Parse("file://" + uid.PathEscape(abs))
|
||||
}).
|
||||
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
abs, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return url.Parse(fmt.Sprintf("file://%v?directories=true", uid.PathEscape(abs)))
|
||||
})
|
||||
Query("file", "directories", "", "C_DIR", cDir)
|
||||
}).Tag("directories")
|
||||
}
|
||||
|
||||
// ActionFiles completes files with optional suffix filtering.
|
||||
func ActionFiles(suffix ...string) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
cDir, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
return actionPath(suffix, false).
|
||||
MultiParts("/").
|
||||
StyleF(style.ForPath).
|
||||
@@ -168,23 +171,9 @@ func ActionFiles(suffix ...string) Action {
|
||||
}
|
||||
return url.Parse("file://" + uid.PathEscape(abs))
|
||||
}).
|
||||
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
abs, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := &url.URL{
|
||||
Scheme: "file",
|
||||
Path: abs,
|
||||
}
|
||||
if len(suffix) > 0 {
|
||||
values := query.Query()
|
||||
values.Set("suffix", strings.Join(suffix, ","))
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
return query, nil
|
||||
})
|
||||
// Query("file", "", c.Dir, "suffix", strings.Join(suffix, ","))
|
||||
Query("file", "files", "",
|
||||
"C_DIR", cDir,
|
||||
"suffix", strings.Join(suffix, ","))
|
||||
}).Tag("files")
|
||||
}
|
||||
|
||||
@@ -247,7 +236,7 @@ func ActionStyledValuesDescribed(values ...string) Action {
|
||||
}
|
||||
|
||||
// ActionMessage displays a help messages in places where no completions can be generated.
|
||||
func ActionMessage(msg string, args ...interface{}) Action {
|
||||
func ActionMessage(msg string, args ...any) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
if len(args) > 0 {
|
||||
msg = fmt.Sprintf(msg, args...)
|
||||
@@ -477,14 +466,15 @@ func ActionExecutables(dirs ...string) Action {
|
||||
}
|
||||
manDescriptions := man.Descriptions(c.Value) // TODO allow additional descriptions to be registered somewhere for carapace-bin (key, value,...)
|
||||
batch := Batch()
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
batch = append(batch, actionDirectoryExecutables(dirs[i], c.Value, manDescriptions))
|
||||
for _, dir := range dirs {
|
||||
batch = append(batch, actionDirectoryExecutables(dir, c.Value, manDescriptions))
|
||||
}
|
||||
return batch.ToA().
|
||||
UidF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
return &url.URL{Scheme: "cmd", Host: uid.PathEscape(s)}, nil
|
||||
}).
|
||||
Query("cmd", "", "", "directories", strings.Join(dirs, ", "))
|
||||
Query("cmd", "", "", "directories", strings.Join(dirs, ", ")).
|
||||
Unique()
|
||||
}).Tag("executables")
|
||||
}
|
||||
|
||||
@@ -553,10 +543,8 @@ func ActionCommands(cmd *cobra.Command) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
if len(c.Args) > 0 {
|
||||
for _, subCommand := range cmd.Commands() {
|
||||
for _, name := range append(subCommand.Aliases, subCommand.Name()) {
|
||||
if name == c.Args[0] { // cmd.Find is too lenient
|
||||
return ActionCommands(subCommand).Shift(1)
|
||||
}
|
||||
if slices.Contains(append(subCommand.Aliases, subCommand.Name()), c.Args[0]) { // cmd.Find is too lenient
|
||||
return ActionCommands(subCommand).Shift(1)
|
||||
}
|
||||
}
|
||||
return ActionMessage("unknown subcommand %#v for %#v", c.Args[0], cmd.Name())
|
||||
|
||||
+26
@@ -1,6 +1,8 @@
|
||||
package carapace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/common"
|
||||
"github.com/carapace-sh/carapace/pkg/style"
|
||||
)
|
||||
@@ -42,3 +44,27 @@ func Diff(original, new Action) Action {
|
||||
return mergedBatch.ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO experimental - needs different signature
|
||||
// TODO return patch format
|
||||
func DiffPatch(original, new Action, c Context) []string {
|
||||
diff := Diff(original, new).Invoke(c)
|
||||
patch := make([]string, 0)
|
||||
for _, v := range diff.action.rawValues {
|
||||
s := v.Value
|
||||
if v.Description != "" {
|
||||
s += fmt.Sprintf(" (%v)", v.Description)
|
||||
}
|
||||
|
||||
switch v.Style {
|
||||
case style.Red:
|
||||
s = "- " + s
|
||||
case style.Green:
|
||||
s = "+ " + s
|
||||
default:
|
||||
s = " " + s
|
||||
}
|
||||
patch = append(patch, s)
|
||||
}
|
||||
return patch
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
go 1.19
|
||||
go 1.24
|
||||
|
||||
use (
|
||||
.
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
github.com/carapace-sh/carapace-shlex v1.0.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/carapace-sh/carapace-pflag v1.1.0 h1:uB33wch8ADjP878NadQnbZ5vyROcj/Ac+wUQKMcIrQM=
|
||||
github.com/carapace-sh/carapace-pflag v1.1.0/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
+1
-1
@@ -20,7 +20,7 @@ func (m Mock) WorkDir() string {
|
||||
|
||||
type t interface {
|
||||
Name() string
|
||||
Fatal(...interface{})
|
||||
Fatal(...any)
|
||||
}
|
||||
|
||||
func NewMock(t t) *Mock {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// FromInvokedAction provides access to RawValues within an InvokedAction.
|
||||
// It is intended for testing purposes in Sandbox (circumventing dependency issues).
|
||||
var FromInvokedAction func(action interface{}) (Meta, RawValues)
|
||||
var FromInvokedAction func(action any) (Meta, RawValues)
|
||||
|
||||
// RawValue represents a completion candidate.
|
||||
type RawValue struct {
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/carapace-sh/carapace/pkg/xdg"
|
||||
)
|
||||
|
||||
type configMap map[string]interface{}
|
||||
type configMap map[string]any
|
||||
|
||||
func (c configMap) Keys() []string {
|
||||
keys := make([]string, 0)
|
||||
@@ -52,7 +52,7 @@ var config = struct {
|
||||
Styles: make(configMap),
|
||||
}
|
||||
|
||||
func RegisterStyle(name string, i interface{}) {
|
||||
func RegisterStyle(name string, i any) {
|
||||
config.Styles[name] = i
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -81,7 +81,11 @@ func CoverDir() string {
|
||||
return os.Getenv(CARAPACE_COVERDIR) // custom env for GOCOVERDIR so that it works together with `-coverprofile`
|
||||
}
|
||||
|
||||
func isGoRun() bool { return strings.HasPrefix(os.Args[0], os.TempDir()+"/go-build") }
|
||||
func isGoRun() bool {
|
||||
// go 1.24+: /home/rsteube/.cache/go-build/a7/a7883331b606dc3250005f1abdf4d17dea05c9eccf4ece0df5518311a118d211-d/example
|
||||
// go 1.23-: /tmp/go-build1035720725/b001/exe/example
|
||||
return strings.Contains(os.Args[0], "/go-build")
|
||||
}
|
||||
|
||||
func Match() string { // see match.Match
|
||||
return os.Getenv(CARAPACE_MATCH)
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ func Descriptions(s string) (descriptions map[string]string) {
|
||||
}
|
||||
|
||||
r := regexp.MustCompile(`^(?P<name>[^ ]+) [^-]+- (?P<description>.*)$`)
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(string(output), "\n")
|
||||
for line := range lines {
|
||||
if matches := r.FindStringSubmatch(line); len(matches) > 2 {
|
||||
descriptions[matches[1]] = matches[2]
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func (f FlagSet) IsShorthandSeries(arg string) bool {
|
||||
func (f FlagSet) IsMutuallyExclusive(flag *pflag.Flag) bool {
|
||||
if groups, ok := flag.Annotations["cobra_annotation_mutually_exclusive"]; ok {
|
||||
for _, group := range groups {
|
||||
for _, name := range strings.Split(group, " ") {
|
||||
for name := range strings.SplitSeq(group, " ") {
|
||||
if other := f.Lookup(name); other != nil && other.Changed && other != flag {
|
||||
return true
|
||||
}
|
||||
|
||||
+9
-9
@@ -11,7 +11,7 @@ import (
|
||||
// Snippet creates the bash completion script.
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
result := fmt.Sprintf(`#!/bin/bash
|
||||
_%v_completion() {
|
||||
_%[1]v_completion() {
|
||||
export COMP_LINE
|
||||
export COMP_POINT
|
||||
export COMP_TYPE
|
||||
@@ -19,12 +19,12 @@ _%v_completion() {
|
||||
|
||||
local nospace data compline="${COMP_LINE:0:${COMP_POINT}}"
|
||||
|
||||
if echo ${compline}"''" | xargs echo 2>/dev/null > /dev/null; then
|
||||
data=$(echo ${compline}"''" | xargs %v _carapace bash)
|
||||
elif echo ${compline} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
|
||||
data=$(echo ${compline} | sed "s/\$/'/" | xargs %v _carapace bash)
|
||||
else
|
||||
data=$(echo ${compline} | sed 's/$/"/' | xargs %v _carapace bash)
|
||||
data=$(echo "${compline}''" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
if [ $? -eq 1 ]; then
|
||||
data=$(echo "${compline}'" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
if [ $? -eq 1 ]; then
|
||||
data=$(echo "${compline}\"" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
IFS=$'\001' read -r -d '' nospace data <<<"${data}"
|
||||
@@ -36,8 +36,8 @@ _%v_completion() {
|
||||
[[ "${COMPREPLY[*]}" == "" ]] && COMPREPLY=() # fix for mapfile creating a non-empty array from empty command output
|
||||
}
|
||||
|
||||
complete -o noquote -F _%v_completion %v
|
||||
`, cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name())
|
||||
complete -o noquote -F _%[1]v_completion %[1]v
|
||||
`, cmd.Name(), uid.Executable())
|
||||
|
||||
return result
|
||||
}
|
||||
+32
-19
@@ -8,28 +8,41 @@ import (
|
||||
)
|
||||
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
result := fmt.Sprintf(`local function %v_completion(word, word_index, line_state, match_builder)
|
||||
local compline = string.sub(line_state:getline(), 1, line_state:getcursor())
|
||||
result := fmt.Sprintf(`local function %[1]s_completion(word, word_index, line_state, match_builder)
|
||||
match_builder:setnosort()
|
||||
match_builder:setvolatile()
|
||||
os.setenv('CARAPACE_COMPLINE', line_state:getline():sub(1, line_state:getcursor()))
|
||||
|
||||
local output = io.popen("env CARAPACE_COMPLINE=" .. string.format("%%q", compline) .. " %v _carapace cmd-clink \"\""):read("*a")
|
||||
for line in string.gmatch(output, '[^\r\n]+') do
|
||||
local matches = {}
|
||||
for m in string.gmatch(line, '[^\t]+') do
|
||||
table.insert(matches, m)
|
||||
local file, pclose = io.popenyield('%[2]s _carapace cmd-clink %[1]s')
|
||||
|
||||
if not file then
|
||||
return false
|
||||
end
|
||||
match_builder:addmatch({
|
||||
match = matches[1],
|
||||
display = matches[2],
|
||||
description = matches[3],
|
||||
type = "word",
|
||||
appendchar = matches[4],
|
||||
suppressappend = false
|
||||
})
|
||||
end
|
||||
return true
|
||||
|
||||
for line in file:lines() do
|
||||
local matches = string.explode(line, '\t')
|
||||
|
||||
if matches[1] then
|
||||
match_builder:addmatch({
|
||||
match = matches[1],
|
||||
display = matches[2],
|
||||
description = matches[3],
|
||||
type = 'word',
|
||||
appendchar = matches[4] or ''
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if pclose then
|
||||
pclose()
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
|
||||
return not match_builder:isempty()
|
||||
end
|
||||
|
||||
clink.argmatcher("%v"):addarg({nowordbreakchars="'‵+;,", %v_completion}):loop(1)
|
||||
`, cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name())
|
||||
clink.argmatcher(50, '%[1]s', '%[1]s.exe'):addarg({nowordbreakchars="'`+"`"+`=+;,", %[1]s_completion}):loop(1)
|
||||
`, cmd.Name(), uid.Executable())
|
||||
return result
|
||||
}
|
||||
+11
-15
@@ -10,23 +10,19 @@ import (
|
||||
|
||||
// Snippet creates the fish completion script.
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
return fmt.Sprintf(`function _%v_quote_suffix
|
||||
if not commandline -cp | xargs echo 2>/dev/null >/dev/null
|
||||
if commandline -cp | sed 's/$/"/'| xargs echo 2>/dev/null >/dev/null
|
||||
echo '"'
|
||||
else if commandline -cp | sed "s/\$/'/"| xargs echo 2>/dev/null >/dev/null
|
||||
echo "'"
|
||||
return fmt.Sprintf(`function _%[1]v_completion
|
||||
set --local data
|
||||
IFS='' set data (echo (commandline -cp)'' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
if [ $status -eq 1 ]
|
||||
IFS='' set data (echo (commandline -cp)"'" | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
if [ $status -eq 1 ]
|
||||
IFS='' set data (echo (commandline -cp)'"' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
end
|
||||
else
|
||||
echo ""
|
||||
end
|
||||
echo $data
|
||||
end
|
||||
|
||||
function _%v_callback
|
||||
commandline -cp | sed "s/\$/"(_%v_quote_suffix)"/" | sed "s/ \$/ ''/" | xargs %v _carapace fish
|
||||
end
|
||||
|
||||
complete -e '%v'
|
||||
complete -c '%v' -f -a '(_%v_callback)' -r
|
||||
`, cmd.Name(), cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
|
||||
complete -e '%[1]v'
|
||||
complete -c '%[1]v' -f -a '(_%[1]v_completion)' -r
|
||||
`, cmd.Name(), uid.Executable())
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ func (nd *namedDirectories) Replace(s string) string {
|
||||
|
||||
func init() {
|
||||
if hashDirs := env.Hashdirs(); hashDirs != "" {
|
||||
for _, line := range strings.Split(hashDirs, "\n") {
|
||||
for line := range strings.SplitSeq(hashDirs, "\n") {
|
||||
if splitted := strings.SplitN(line, "=", 2); len(splitted) == 2 {
|
||||
NamedDirectories[splitted[0]] = splitted[1]
|
||||
}
|
||||
|
||||
+12
-11
@@ -11,17 +11,18 @@ import (
|
||||
// Snippet creates the zsh completion script
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
return fmt.Sprintf(`#compdef %v
|
||||
function _%v_completion {
|
||||
local words=${words[@]:0:$CURRENT}
|
||||
function _%[1]v_completion {
|
||||
local compline=${words[@]:0:$CURRENT}
|
||||
local IFS=$'\n'
|
||||
local lines
|
||||
|
||||
# shellcheck disable=SC2086,SC2154,SC2155
|
||||
if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
|
||||
local lines="$(echo ${words}"''" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh )"
|
||||
elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
|
||||
local lines="$(echo ${words} | sed "s/\$/'/" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
|
||||
else
|
||||
local lines="$(echo ${words} | sed 's/$/"/' | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
|
||||
lines="$(echo "${compline}''" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
if [ $? -eq 1 ]; then
|
||||
lines="$(echo "${compline}'" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
if [ $? -eq 1 ]; then
|
||||
lines="$(echo "${compline}\"" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
fi
|
||||
fi
|
||||
|
||||
local zstyle message data
|
||||
@@ -41,7 +42,7 @@ function _%v_completion {
|
||||
[[ ${#valuesArr[@]} -gt 1 ]] && _describe -t "${tag}" "${tag}" displaysArr valuesArr -Q -S ''
|
||||
done <<<"${data}"
|
||||
}
|
||||
compquote '' 2>/dev/null && _%v_completion
|
||||
compdef _%v_completion %v
|
||||
`, cmd.Name(), cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
|
||||
compquote '' 2>/dev/null && _%[1]v_completion
|
||||
compdef _%[1]v_completion %[1]v
|
||||
`, cmd.Name(), uid.Executable())
|
||||
}
|
||||
+2
-2
@@ -172,7 +172,7 @@ func (ia InvokedAction) ToMultiPartsA(dividers ...string) Action {
|
||||
})
|
||||
}
|
||||
|
||||
func (ia InvokedAction) Query(q string) InvokedAction {
|
||||
func (ia InvokedAction) QueryS(q string) InvokedAction {
|
||||
ia.action.meta.Queries.Add(q)
|
||||
return ia
|
||||
}
|
||||
@@ -182,7 +182,7 @@ func (ia InvokedAction) value(shell string, value string) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.FromInvokedAction = func(i interface{}) (common.Meta, common.RawValues) {
|
||||
common.FromInvokedAction = func(i any) (common.Meta, common.RawValues) {
|
||||
if invoked, ok := i.(InvokedAction); ok {
|
||||
return invoked.action.meta, invoked.action.rawValues
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
// func init() {
|
||||
// Register("carapace", &Carapace)
|
||||
// }
|
||||
func Register(name string, i interface{}) { config.RegisterStyle(name, i) }
|
||||
func Register(name string, i any) { config.RegisterStyle(name, i) }
|
||||
|
||||
// Set a style
|
||||
//
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ func SGR(s string) string { return Parse(s).SGR() }
|
||||
|
||||
func Parse(s string) ui.Style {
|
||||
stylings := make([]ui.Styling, 0)
|
||||
for _, word := range strings.Split(s, " ") {
|
||||
for word := range strings.SplitSeq(s, " ") {
|
||||
if styling := ui.ParseStyling(word); styling != nil {
|
||||
stylings = append(stylings, styling)
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ func ConfigDirs() (dirs []string, err error) {
|
||||
|
||||
if v, ok := os.LookupEnv("XDG_CONFIG_DIRS"); ok {
|
||||
xdgDirs := make([]string, 0)
|
||||
for _, dir := range strings.Split(v, string(os.PathSeparator)) {
|
||||
for dir := range strings.SplitSeq(v, string(os.PathSeparator)) {
|
||||
if filepath.IsAbs(dir) {
|
||||
xdgDirs = append(xdgDirs, dir)
|
||||
}
|
||||
|
||||
Generated
Vendored
+8
-11
@@ -76,13 +76,10 @@ func toSubstr(s string, args ...string) string {
|
||||
if pos < 0 {
|
||||
// if pos is negative (counts from the end) add it
|
||||
// to length to get first character offset
|
||||
pos = len(s) + pos
|
||||
|
||||
// if negative offset exceeds the length of the string
|
||||
// start from 0
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
pos = max(
|
||||
// if negative offset exceeds the length of the string
|
||||
// start from 0
|
||||
len(s)+pos, 0)
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
@@ -122,9 +119,9 @@ func replaceAll(s string, args ...string) string {
|
||||
case 0:
|
||||
return s
|
||||
case 1:
|
||||
return strings.Replace(s, args[0], "", -1)
|
||||
return strings.ReplaceAll(s, args[0], "")
|
||||
default:
|
||||
return strings.Replace(s, args[0], args[1], -1)
|
||||
return strings.ReplaceAll(s, args[0], args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +156,8 @@ func replaceSuffix(s string, args ...string) string {
|
||||
if len(args) != 2 {
|
||||
return s
|
||||
}
|
||||
if strings.HasSuffix(s, args[0]) {
|
||||
s = strings.TrimSuffix(s, args[0])
|
||||
if before, ok := strings.CutSuffix(s, args[0]); ok {
|
||||
s = before
|
||||
s = s + args[1]
|
||||
}
|
||||
return s
|
||||
|
||||
Generated
Vendored
-4
@@ -235,10 +235,6 @@ func acceptOneHash(r rune, i int) bool {
|
||||
return r == '#' && i == 1
|
||||
}
|
||||
|
||||
func acceptNone(r rune, i int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func acceptNotClosing(r rune, i int) bool {
|
||||
return r != '}'
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -3,7 +3,7 @@ package envsubst
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/carapace-sh/carapace/third_party/github.com/drone/envsubst/parse"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ func Parse(s string) (t *Template, err error) {
|
||||
// ParseFile creates a new shell format template and parses the template
|
||||
// definition from the named file.
|
||||
func ParseFile(path string) (*Template, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -79,14 +79,14 @@ var featureForName = map[string]feature{
|
||||
// fields are silently ignored.
|
||||
func parseLsColor(s string) *colorist {
|
||||
lc := &colorist{make(map[feature]string), make(map[string]string)}
|
||||
for _, spec := range strings.Split(s, ":") {
|
||||
for spec := range strings.SplitSeq(s, ":") {
|
||||
words := strings.Split(spec, "=")
|
||||
if len(words) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value := words[0], words[1]
|
||||
filterValues := []string{}
|
||||
for _, splitValue := range strings.Split(value, ";") {
|
||||
for splitValue := range strings.SplitSeq(value, ";") {
|
||||
if strings.Count(splitValue, "0") == len(splitValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,11 +1,10 @@
|
||||
//go:build !solaris
|
||||
// +build !solaris
|
||||
|
||||
package lscolors
|
||||
|
||||
import "os"
|
||||
|
||||
func isDoor(info os.FileInfo) bool {
|
||||
func isDoor(_ os.FileInfo) bool {
|
||||
// Doors are only supported on Solaris.
|
||||
return false
|
||||
}
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9
|
||||
// +build !windows,!plan9
|
||||
|
||||
package lscolors
|
||||
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -2,7 +2,7 @@ package lscolors
|
||||
|
||||
import "os"
|
||||
|
||||
func isMultiHardlink(info os.FileInfo) bool {
|
||||
func isMultiHardlink(_ os.FileInfo) bool {
|
||||
// Windows supports hardlinks, but it is not exposed directly. We omit the
|
||||
// implementation for now.
|
||||
// TODO: Maybe implement it?
|
||||
|
||||
Generated
Vendored
+1
-3
@@ -5,8 +5,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const sgrPrefix = "\033["
|
||||
|
||||
var sgrStyling = map[int]Styling{
|
||||
0: Reset,
|
||||
1: Bold,
|
||||
@@ -73,7 +71,7 @@ func StylingFromSGR(s string) Styling {
|
||||
|
||||
func getSGRCodes(s string) []int {
|
||||
var codes []int
|
||||
for _, part := range strings.Split(s, ";") {
|
||||
for part := range strings.SplitSeq(s, ";") {
|
||||
if part == "" {
|
||||
codes = append(codes, 0)
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+3
-3
@@ -44,8 +44,8 @@ func (s Style) SGR() string {
|
||||
|
||||
// MergeFromOptions merges all recognized values from a map to the current
|
||||
// Style.
|
||||
func (s *Style) MergeFromOptions(options map[string]interface{}) error {
|
||||
assignColor := func(val interface{}, colorField *Color) string {
|
||||
func (s *Style) MergeFromOptions(options map[string]any) error {
|
||||
assignColor := func(val any, colorField *Color) string {
|
||||
if val == "default" {
|
||||
*colorField = nil
|
||||
return ""
|
||||
@@ -58,7 +58,7 @@ func (s *Style) MergeFromOptions(options map[string]interface{}) error {
|
||||
}
|
||||
return "valid color string"
|
||||
}
|
||||
assignBool := func(val interface{}, attrField *bool) string {
|
||||
assignBool := func(val any, attrField *bool) string {
|
||||
if b, ok := val.(bool); ok {
|
||||
*attrField = b
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -144,7 +144,7 @@ func ParseStyling(s string) Styling {
|
||||
return parseOneStyling(s)
|
||||
}
|
||||
var joint jointStyling
|
||||
for _, subs := range strings.Split(s, " ") {
|
||||
for subs := range strings.SplitSeq(s, " ") {
|
||||
parsed := parseOneStyling(subs)
|
||||
if parsed == nil {
|
||||
return nil
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package ps
|
||||
|
||||
@@ -188,7 +187,7 @@ func processes() ([]Process, error) {
|
||||
count := int(length / uint64(procinfo_len))
|
||||
|
||||
// parse buf to procs
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
|
||||
k, err := parse_kinfo_proc(b)
|
||||
if err != nil {
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package ps
|
||||
|
||||
@@ -88,7 +87,7 @@ func (p *UnixProcess) Refresh() error {
|
||||
}
|
||||
|
||||
func toString(array []byte, len int) string {
|
||||
for i := 0; i < len; i++ {
|
||||
for i := range len {
|
||||
if array[i] == 0 {
|
||||
return string(array[:i])
|
||||
}
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build linux || solaris
|
||||
// +build linux solaris
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
Copyright (C) 2022, Michael J. Fromberger
|
||||
All Rights Reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
(1) Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
(2) Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
(3) The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Package trigger implements channel-based condition variable.
|
||||
package trigger
|
||||
|
||||
import "sync"
|
||||
|
||||
// A Cond is a condition shared by multiple goroutines. The [Cond.Ready]
|
||||
// method returns a channel that is closed when the condition is activated.
|
||||
//
|
||||
// When a condition is first created it is inactive. While inactive, reads on
|
||||
// the ready channel will block. The condition remains inactive until
|
||||
// [Cond.Set] or [Cond.Signal] is called, either of which causes the current
|
||||
// ready channel to be closed (and thus deliver a zero value). Once a condition
|
||||
// has been activated, it remains active until it is reset. Use [Cond.Reset] to
|
||||
// make it inactive again.
|
||||
//
|
||||
// The [Cond.Signal] method activates and then immediately resets the
|
||||
// condition, acting as Set and Reset done in a single step.
|
||||
//
|
||||
// A zero Cond is ready for use, and is inactive, but must not be copied after
|
||||
// any of its methods have been called.
|
||||
type Cond struct {
|
||||
μ sync.Mutex
|
||||
ch chan struct{}
|
||||
closed bool
|
||||
|
||||
// The signal channel is lazily initialized by the first waiter.
|
||||
}
|
||||
|
||||
// New constructs a new inactive [Cond].
|
||||
func New() *Cond { return new(Cond) }
|
||||
|
||||
// Signal activates and immediately resets the condition. If the condition was
|
||||
// already active, this is equivalent to [Cond.Reset].
|
||||
func (c *Cond) Signal() {
|
||||
c.μ.Lock()
|
||||
defer c.μ.Unlock()
|
||||
|
||||
if c.ch != nil && !c.closed {
|
||||
close(c.ch) // wake any pending waiters
|
||||
}
|
||||
c.ch = nil
|
||||
c.closed = false
|
||||
}
|
||||
|
||||
// Set activates the condition. If it was already active, Set has no effect.
|
||||
func (c *Cond) Set() {
|
||||
c.μ.Lock()
|
||||
defer c.μ.Unlock()
|
||||
|
||||
if c.ch == nil {
|
||||
c.ch = make(chan struct{})
|
||||
}
|
||||
if !c.closed {
|
||||
close(c.ch)
|
||||
c.closed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the condition. If it was already inactive, Reset has no effect.
|
||||
func (c *Cond) Reset() {
|
||||
c.μ.Lock()
|
||||
defer c.μ.Unlock()
|
||||
|
||||
if c.closed {
|
||||
c.ch = nil
|
||||
c.closed = false
|
||||
}
|
||||
}
|
||||
|
||||
// Ready returns a channel that is closed when c is activated. If c is active
|
||||
// when Ready is called, the returned channel will already be closed.
|
||||
func (c *Cond) Ready() <-chan struct{} {
|
||||
c.μ.Lock()
|
||||
defer c.μ.Unlock()
|
||||
|
||||
if c.ch == nil {
|
||||
c.ch = make(chan struct{})
|
||||
c.closed = false
|
||||
}
|
||||
return c.ch
|
||||
}
|
||||
+7
-2
@@ -6,7 +6,12 @@
|
||||
DNS version 2 is now available at <https://codeberg.org/miekg/dns>, check it out if you want to
|
||||
help shape the next 15 years of the Go DNS package.
|
||||
|
||||
The version here will see no new features and less and less development.
|
||||
The version here will see no new features and less and less development, and my time (if any) will be fully
|
||||
devoted towards v2.
|
||||
|
||||
**December 2025**: v2 should be (already) a good replacement, the coming months would be a good time to
|
||||
migrate, see [this file describing the
|
||||
differences](https://codeberg.org/miekg/dns/src/branch/main/README-diff-with-v1.md), to help you get started.
|
||||
|
||||
# Alternative (more granular) approach to a DNS library
|
||||
|
||||
@@ -62,7 +67,7 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
||||
- https://www.dnsperf.com/
|
||||
- https://dnssectest.net/
|
||||
- https://github.com/oif/apex
|
||||
- https://github.com/jedisct1/dnscrypt-proxy
|
||||
- https://github.com/jedisct1/dnscrypt-proxy (migrated to v2)
|
||||
- https://github.com/jedisct1/rpdns
|
||||
- https://github.com/xor-gate/sshfp
|
||||
- https://github.com/rs/dnstrace
|
||||
|
||||
+7
-3
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -1231,7 +1232,7 @@ func typeToInt(token string) (uint16, bool) {
|
||||
|
||||
// stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.
|
||||
func stringToTTL(token string) (uint32, bool) {
|
||||
var s, i uint32
|
||||
var s, i uint
|
||||
for _, c := range token {
|
||||
switch c {
|
||||
case 's', 'S':
|
||||
@@ -1251,12 +1252,15 @@ func stringToTTL(token string) (uint32, bool) {
|
||||
i = 0
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
i *= 10
|
||||
i += uint32(c) - '0'
|
||||
i += uint(c) - '0'
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
return s + i, true
|
||||
if s+i > math.MaxUint32 {
|
||||
return 0, false
|
||||
}
|
||||
return uint32(s + i), true
|
||||
}
|
||||
|
||||
// Parse LOC records' <digits>[.<digits>][mM] into a
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package dns
|
||||
import "fmt"
|
||||
|
||||
// Version is current version of this library.
|
||||
var Version = v{1, 1, 69}
|
||||
var Version = v{1, 1, 70}
|
||||
|
||||
// v holds the version of this library.
|
||||
type v struct {
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
.idea
|
||||
bin
|
||||
pkg
|
||||
|
||||
*.out
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016 Paulo Pires
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
# go-proxyproto
|
||||
|
||||
[](https://github.com/pires/go-proxyproto/actions)
|
||||
[](https://coveralls.io/github/pires/go-proxyproto?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/pires/go-proxyproto)
|
||||
[](https://pkg.go.dev/github.com/pires/go-proxyproto?tab=doc)
|
||||
|
||||
|
||||
A Go library implementation of the [PROXY protocol, versions 1 and 2](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt),
|
||||
which provides, as per specification:
|
||||
> (...) a convenient way to safely transport connection
|
||||
> information such as a client's address across multiple layers of NAT or TCP
|
||||
> proxies. It is designed to require little changes to existing components and
|
||||
> to limit the performance impact caused by the processing of the transported
|
||||
> information.
|
||||
|
||||
This library is to be used in one of or both proxy clients and proxy servers that need to support said protocol.
|
||||
Both protocol versions, 1 (text-based) and 2 (binary-based) are supported.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ go get -u github.com/pires/go-proxyproto
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Client
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
proxyproto "github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
func chkErr(err error) {
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
|
||||
target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
|
||||
chkErr(err)
|
||||
|
||||
conn, err := net.DialTCP("tcp", nil, target)
|
||||
chkErr(err)
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
// Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
|
||||
// have two conn's
|
||||
header := &proxyproto.Header{
|
||||
Version: 1,
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: proxyproto.TCPv4,
|
||||
SourceAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("10.1.1.1"),
|
||||
Port: 1000,
|
||||
},
|
||||
DestinationAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("20.2.2.2"),
|
||||
Port: 2000,
|
||||
},
|
||||
}
|
||||
// After the connection was created write the proxy headers first
|
||||
_, err = header.WriteTo(conn)
|
||||
chkErr(err)
|
||||
// Then your data... e.g.:
|
||||
_, err = io.WriteString(conn, "HELO")
|
||||
chkErr(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
proxyproto "github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a listener
|
||||
addr := "localhost:9876"
|
||||
list, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error())
|
||||
}
|
||||
|
||||
// Wrap listener in a proxyproto listener
|
||||
proxyListener := &proxyproto.Listener{Listener: list}
|
||||
defer proxyListener.Close()
|
||||
|
||||
// Wait for a connection and accept it
|
||||
conn, err := proxyListener.Accept()
|
||||
defer conn.Close()
|
||||
|
||||
// Print connection details
|
||||
if conn.LocalAddr() == nil {
|
||||
log.Fatal("couldn't retrieve local address")
|
||||
}
|
||||
log.Printf("local address: %q", conn.LocalAddr().String())
|
||||
|
||||
if conn.RemoteAddr() == nil {
|
||||
log.Fatal("couldn't retrieve remote address")
|
||||
}
|
||||
log.Printf("remote address: %q", conn.RemoteAddr().String())
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Server
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server := http.Server{
|
||||
Addr: ":8080",
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", server.Addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
proxyListener := &proxyproto.Listener{
|
||||
Listener: ln,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
defer proxyListener.Close()
|
||||
|
||||
server.Serve(proxyListener)
|
||||
}
|
||||
```
|
||||
|
||||
## Special notes
|
||||
|
||||
### AWS
|
||||
|
||||
AWS Network Load Balancer (NLB) does not push the PPV2 header until the client starts sending the data. This is a problem if your server speaks first. e.g. SMTP, FTP, SSH etc.
|
||||
|
||||
By default, NLB target group attribute `proxy_protocol_v2.client_to_server.header_placement` has the value `on_first_ack_with_payload`. You need to contact AWS support to change it to `on_first_ack`, instead.
|
||||
|
||||
Just to be clear, you need this fix only if your server is designed to speak first.
|
||||
Loaded 100 of 307 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user